diff --git a/src/main/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplate.java b/src/main/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplate.java index 5e6a3696..fd301522 100644 --- a/src/main/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplate.java +++ b/src/main/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplate.java @@ -13,25 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.gemfire.config.admin.remote; +import java.io.IOException; +import java.net.HttpURLConnection; import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.execute.Function; import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; import org.springframework.data.gemfire.config.schema.definitions.IndexDefinition; import org.springframework.data.gemfire.config.schema.definitions.RegionDefinition; +import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.data.gemfire.util.CollectionUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; @@ -44,70 +57,125 @@ import org.springframework.web.client.RestTemplate; * is not supported or has not been implemented against the Management REST API interface over HTTP. * * @author John Blum + * @see java.net.HttpURLConnection + * @see java.net.URI * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.execute.Function * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations * @see org.springframework.data.gemfire.config.admin.remote.FunctionGemfireAdminTemplate + * @see org.springframework.http.HttpHeaders + * @see org.springframework.http.HttpMethod + * @see org.springframework.http.HttpStatus + * @see org.springframework.http.RequestEntity + * @see org.springframework.http.ResponseEntity + * @see org.springframework.http.client.ClientHttpRequestFactory + * @see org.springframework.http.client.ClientHttpRequestInterceptor + * @see org.springframework.http.client.SimpleClientHttpRequestFactory + * @see org.springframework.web.client.RestOperations + * @see org.springframework.web.client.RestTemplate * @since 2.0.0 */ public class RestHttpGemfireAdminTemplate extends FunctionGemfireAdminTemplate { - protected static final boolean CREATE_REGION_SKIP_IF_EXISTS_DEFAULT = true; + protected static final boolean DEFAULT_CREATE_REGION_SKIP_IF_EXISTS = true; + protected static final boolean DEFAULT_HTTP_FOLLOW_REDIRECTS = true; protected static final int DEFAULT_PORT = 7070; protected static final String DEFAULT_HOST = "localhost"; + protected static final String DEFAULT_SCHEME = "https"; + protected static final String HTTP_SCHEME = "http"; + protected static final String HTTPS_SCHEME = "https"; + protected static final String MANAGEMENT_REST_API_URL_TEMPLATE = "%1$s://%2$s:%3$d/gemfire/v1"; - // TODO: allow https next to http - protected static final String MANAGEMENT_REST_API_URL_TEMPLATE = "http://%1$s:%2$d/gemfire/v1/"; + protected static final List VALID_SCHEMES = Arrays.asList(HTTP_SCHEME, HTTPS_SCHEME); private final RestOperations restTemplate; private final String managementRestApiUrl; /** - * Constructs an instance of the {@link RestHttpGemfireAdminTemplate} initialized with - * the given {@link ClientCache} and configured with the default host and port when accessing - * the GemFire or Geode Management REST API interface. + * Constructs a new instance of {@link RestHttpGemfireAdminTemplate} initialized with the given {@link ClientCache} + * and configured with the default host and port when accessing the Apache Geode or Pivotal GemFire + * Management REST API interface. * - * @param clientCache reference to the {@link ClientCache} - * @throws IllegalArgumentException if the {@link ClientCache} reference is {@literal null}. + * @param clientCache reference to the {@link ClientCache}. + * @throws IllegalArgumentException if {@link ClientCache} is {@literal null}. + * @see #RestHttpGemfireAdminTemplate(ClientCache, String, String, int, boolean, List) * @see org.apache.geode.cache.client.ClientCache */ public RestHttpGemfireAdminTemplate(ClientCache clientCache) { - this(clientCache, DEFAULT_HOST, DEFAULT_PORT); + this(clientCache, DEFAULT_SCHEME, DEFAULT_HOST, DEFAULT_PORT, DEFAULT_HTTP_FOLLOW_REDIRECTS, + Collections.emptyList()); } /** - * Constructs an instance of the {@link RestHttpGemfireAdminTemplate} initialized with - * the given {@link ClientCache} and configured with the specified host and port when accessing - * the GemFire or Geode Management REST API interface. + * Constructs a new instance of {@link RestHttpGemfireAdminTemplate} initialized with the given {@link ClientCache} + * and configured with the specified HTTP scheme, host, port, redirects and + * {@link ClientHttpRequestInterceptor ClientHttpRequestInterceptors} when + * accessing the Apache Geode or Pivotal GemFire Management REST API interface. * * @param clientCache reference to the {@link ClientCache} + * @param scheme {@link String} specifying the HTTP scheme to use (e.g. HTTP or HTTPS). * @param host {@link String} containing the hostname of the GemFire/Geode Manager. * @param port integer value specifying the port on which the GemFire/Geode Manager HTTP Service is listening * for HTTP clients. + * @param followRedirects boolean indicating whether HTTP Redirects (with HTTP Status Code 3xx) should be followed. + * @param clientHttpRequestInterceptors {@link List} of {@link ClientHttpRequestInterceptor} used to intercept + * and decorate the HTTP request and HTTP response. * @throws IllegalArgumentException if the {@link ClientCache} reference is {@literal null}. + * @see org.springframework.http.client.ClientHttpRequestInterceptor * @see org.apache.geode.cache.client.ClientCache + * @see #newClientHttpRequestFactory(boolean) + * @see #newRestOperations(ClientHttpRequestFactory, List) + * @see #resolveManagementRestApiUrl(String, String, int) */ - public RestHttpGemfireAdminTemplate(ClientCache clientCache, String host, int port) { + public RestHttpGemfireAdminTemplate(ClientCache clientCache, String scheme, String host, int port, + boolean followRedirects, List clientHttpRequestInterceptors) { super(clientCache); - this.restTemplate = newRestOperations(); - this.managementRestApiUrl = resolveManagementRestApiUrl(host, port); + ClientHttpRequestFactory clientHttpRequestFactory = newClientHttpRequestFactory(followRedirects); + + this.managementRestApiUrl = resolveManagementRestApiUrl(scheme, host, port); + this.restTemplate = newRestOperations(clientHttpRequestFactory, clientHttpRequestInterceptors); + } + + /** + * Constructs a new instance of {@link ClientHttpRequestFactory} to make HTTP client requests. + * + * @param followRedirects boolean value indicating whether HTTP redirects (with HTTP Status Code 3xx) + * should be followed. + * @return a new {@link ClientHttpRequestFactory}. + * @see org.springframework.http.client.ClientHttpRequestFactory + */ + @SuppressWarnings("unchecked") + protected T newClientHttpRequestFactory(boolean followRedirects) { + return (T) new FollowRedirectsSimpleClientHttpRequestFactory(followRedirects); } /** * Constructs a new instance of the Spring {@link RestTemplate} to perform REST API operations over HTTP. * + * @param clientHttpRequestFactory {@link ClientHttpRequestFactory} used to construct HTTP request objects. + * @param clientHttpRequestInterceptors {@link List} of {@link ClientHttpRequestInterceptor} used to intercept + * and decorate the HTTP request and HTTP response. * @return a new instance of Spring's {@link RestTemplate}. + * @see org.springframework.http.client.ClientHttpRequestInterceptor * @see org.springframework.http.client.SimpleClientHttpRequestFactory * @see org.springframework.web.client.RestOperations * @see org.springframework.web.client.RestTemplate */ - RestOperations newRestOperations() { - return new RestTemplate(new SimpleClientHttpRequestFactory()); + @SuppressWarnings("unchecked") + protected T newRestOperations(ClientHttpRequestFactory clientHttpRequestFactory, + List clientHttpRequestInterceptors) { + + RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory); + + Optional.ofNullable(clientHttpRequestInterceptors) + .ifPresent(restTemplate.getInterceptors()::addAll); + + return (T) restTemplate; } /** @@ -119,8 +187,8 @@ public class RestHttpGemfireAdminTemplate extends FunctionGemfireAdminTemplate { * @param port integer specifying the port that the embedded Manager's HTTP service is listening on. * @return the resolved URL. */ - private String resolveManagementRestApiUrl(String host, int port) { - return String.format(MANAGEMENT_REST_API_URL_TEMPLATE, host, port); + String resolveManagementRestApiUrl(String scheme, String host, int port) { + return String.format(MANAGEMENT_REST_API_URL_TEMPLATE, scheme, host, port); } /** @@ -138,8 +206,9 @@ public class RestHttpGemfireAdminTemplate extends FunctionGemfireAdminTemplate { * @return a reference to the {@link RestOperations} used to perform REST API calls. * @see org.springframework.web.client.RestOperations */ - protected RestOperations getRestOperations() { - return this.restTemplate; + @SuppressWarnings("unchecked") + protected T getRestOperations() { + return (T) this.restTemplate; } @Override @@ -150,24 +219,24 @@ public class RestHttpGemfireAdminTemplate extends FunctionGemfireAdminTemplate { httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); // HTTP Message Body - MultiValueMap requestParameters = new LinkedMultiValueMap<>(); + MultiValueMap httpRequestParameters = new LinkedMultiValueMap<>(); - requestParameters.add("name", indexDefinition.getName()); - requestParameters.add("expression", indexDefinition.getExpression()); - requestParameters.add("region", indexDefinition.getFromClause()); - requestParameters.add("type", indexDefinition.getIndexType().toString()); + httpRequestParameters.add("name", indexDefinition.getName()); + httpRequestParameters.add("expression", indexDefinition.getExpression()); + httpRequestParameters.add("region", indexDefinition.getFromClause()); + httpRequestParameters.add("type", indexDefinition.getIndexType().toString()); RequestEntity> requestEntity = - new RequestEntity<>(requestParameters, httpHeaders, HttpMethod.POST, resolveCreateIndexUri()); + new RequestEntity<>(httpRequestParameters, httpHeaders, HttpMethod.POST, resolveCreateIndexUri()); ResponseEntity response = getRestOperations().exchange(requestEntity, String.class); - // TODO do something with result; e.g. log when failure (or when not "OK") + // TODO do something with the result; e.g. log when failure (or when not "OK") HttpStatus.OK.equals(response.getStatusCode()); } protected URI resolveCreateIndexUri() { - return URI.create(getManagementRestApiUrl().concat("indexes")); + return URI.create(getManagementRestApiUrl().concat("/indexes")); } @Override @@ -178,22 +247,113 @@ public class RestHttpGemfireAdminTemplate extends FunctionGemfireAdminTemplate { httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); // HTTP Message Body - MultiValueMap requestParameters = new LinkedMultiValueMap<>(); + MultiValueMap httpRequestParameters = new LinkedMultiValueMap<>(); - requestParameters.add("name", regionDefinition.getName()); - requestParameters.add("type", regionDefinition.getRegionShortcut().toString()); - requestParameters.add("skip-if-exists", String.valueOf(CREATE_REGION_SKIP_IF_EXISTS_DEFAULT)); + httpRequestParameters.add("name", regionDefinition.getName()); + httpRequestParameters.add("type", regionDefinition.getRegionShortcut().toString()); + httpRequestParameters.add("skip-if-exists", String.valueOf(DEFAULT_CREATE_REGION_SKIP_IF_EXISTS)); RequestEntity> requestEntity = - new RequestEntity<>(requestParameters, httpHeaders, HttpMethod.POST, resolveCreateRegionUri()); + new RequestEntity<>(httpRequestParameters, httpHeaders, HttpMethod.POST, resolveCreateRegionUri()); ResponseEntity response = getRestOperations().exchange(requestEntity, String.class); - // TODO do something with result; e.g. log when failure (or when not "OK") + // TODO do something with the result; e.g. log when failure (or when not "OK") HttpStatus.OK.equals(response.getStatusCode()); } protected URI resolveCreateRegionUri() { - return URI.create(getManagementRestApiUrl().concat("regions")); + return URI.create(getManagementRestApiUrl().concat("/regions")); + } + + public static class Builder { + + private boolean followRedirects = DEFAULT_HTTP_FOLLOW_REDIRECTS; + + private int port = DEFAULT_PORT; + + private ClientCache clientCache; + + private final List clientHttpRequestInterceptors = new ArrayList<>(); + + private String hostname = DEFAULT_HOST; + private String scheme = DEFAULT_SCHEME; + + public Builder followRedirects(boolean followRedirects) { + this.followRedirects = followRedirects; + return this; + } + + public Builder listenOn(int port) { + + Assert.isTrue(port > 0 && port < 65536, + String.format("Port [%d] must be greater than 0 and less than 65536", port)); + + this.port = port; + + return this; + } + + public Builder on(String hostname) { + this.hostname = StringUtils.hasText(hostname) ? hostname : DEFAULT_HOST; + return this; + } + + public Builder using(String scheme) { + + scheme = String.valueOf(scheme).trim().toLowerCase(); + + Assert.isTrue(VALID_SCHEMES.contains(scheme), + String.format("Scheme [%s] is not valid; must be 1 of %s", scheme, VALID_SCHEMES)); + + this.scheme = scheme; + + return this; + } + + public Builder with(ClientCache clientCache) { + this.clientCache = clientCache; + return this; + } + + public Builder with(ClientHttpRequestInterceptor... clientHttpRequestInterceptors) { + + clientHttpRequestInterceptors = + ArrayUtils.nullSafeArray(clientHttpRequestInterceptors, ClientHttpRequestInterceptor.class); + + return with(Arrays.stream(clientHttpRequestInterceptors).collect(Collectors.toList())); + } + + public Builder with(List clientHttpRequestInterceptors) { + this.clientHttpRequestInterceptors.addAll(CollectionUtils.nullSafeList(clientHttpRequestInterceptors)); + return this; + } + + public RestHttpGemfireAdminTemplate build() { + + return new RestHttpGemfireAdminTemplate(this.clientCache, this.scheme, this.hostname, this.port, + this.followRedirects, this.clientHttpRequestInterceptors); + } + } + + public static class FollowRedirectsSimpleClientHttpRequestFactory extends SimpleClientHttpRequestFactory { + + private final boolean followRedirects; + + public FollowRedirectsSimpleClientHttpRequestFactory(boolean followRedirects) { + this.followRedirects = followRedirects; + } + + public boolean isFollowRedirects() { + return this.followRedirects; + } + + @Override + protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { + + super.prepareConnection(connection, httpMethod); + + connection.setInstanceFollowRedirects(isFollowRedirects()); + } } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationConfiguration.java index 4cf8406a..5de698db 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationConfiguration.java @@ -13,26 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.gemfire.config.annotation; -import static java.util.stream.StreamSupport.stream; import static org.springframework.data.gemfire.util.CacheUtils.isClient; import static org.springframework.data.gemfire.util.CacheUtils.isPeer; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.apache.geode.cache.GemFireCache; import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionShortcut; import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.query.Index; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; import org.springframework.core.OrderComparator; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; import org.springframework.data.gemfire.config.admin.remote.FunctionGemfireAdminTemplate; @@ -49,7 +57,9 @@ import org.springframework.data.gemfire.config.schema.support.IndexDefiner; import org.springframework.data.gemfire.config.schema.support.RegionDefiner; import org.springframework.data.gemfire.config.support.AbstractSmartLifecycle; import org.springframework.data.gemfire.util.CacheUtils; +import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Spring {@link Configuration @Configuration} class defining Spring beans that will record the creation of @@ -57,32 +67,54 @@ import org.springframework.util.Assert; * as Spring beans in the Spring container. * * @author John Blum + * @see java.lang.annotation.Annotation * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.query.Index + * @see org.springframework.beans.factory.ListableBeanFactory * @see org.springframework.beans.factory.config.BeanPostProcessor * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Configuration * @see org.springframework.context.annotation.ImportAware * @see org.springframework.context.event.EventListener + * @see org.springframework.core.annotation.AnnotationAttributes + * @see org.springframework.core.env.Environment + * @see org.springframework.core.type.AnnotationMetadata + * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations + * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport + * @see org.springframework.http.client.ClientHttpRequestInterceptor * @since 2.0.0 */ @Configuration @SuppressWarnings("unused") public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigSupport implements ImportAware { + protected static final boolean DEFAULT_HTTP_FOLLOW_REDIRECTS = false; protected static final boolean DEFAULT_MANAGEMENT_USE_HTTP = false; + protected static final boolean DEFAULT_MANAGEMENT_REQUIRE_HTTPS = true; protected static final int DEFAULT_MANAGEMENT_HTTP_PORT = HttpServiceConfiguration.DEFAULT_HTTP_SERVICE_PORT; protected static final String DEFAULT_MANAGEMENT_HTTP_HOST = "localhost"; + protected static final String HTTP_FOLLOW_REDIRECTS_PROPERTY = + "spring.data.gemfire.management.http.follow-redirects"; + protected static final String HTTP_SCHEME = "http"; + protected static final String HTTPS_SCHEME = "https"; private static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = RegionDefinition.DEFAULT_REGION_SHORTCUT; + private Boolean requireHttps = DEFAULT_MANAGEMENT_REQUIRE_HTTPS; private Boolean useHttp = DEFAULT_MANAGEMENT_USE_HTTP; private Integer managementHttpPort = DEFAULT_MANAGEMENT_HTTP_PORT; + @Autowired(required = false) + private GemfireAdminOperations gemfireAdminOperations; + + @Autowired(required = false) + private List clientHttpRequestInterceptors; + private RegionShortcut serverRegionShortcut; private String managementHttpHost = DEFAULT_MANAGEMENT_HTTP_HOST; @@ -97,7 +129,7 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS } protected Optional getManagementHttpHost() { - return Optional.ofNullable(this.managementHttpHost); + return Optional.ofNullable(this.managementHttpHost).filter(StringUtils::hasText); } protected String resolveManagementHttpHost() { @@ -116,6 +148,18 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS return getManagementHttpPort().orElse(DEFAULT_MANAGEMENT_HTTP_PORT); } + protected void setManagementRequireHttps(Boolean requireHttps) { + this.requireHttps = requireHttps; + } + + protected Optional getManagementRequireHttps() { + return Optional.ofNullable(this.requireHttps); + } + + protected boolean resolveManagementRequireHttps() { + return getManagementRequireHttps().orElse(DEFAULT_MANAGEMENT_REQUIRE_HTTPS); + } + protected void setManagementUseHttp(Boolean useHttp) { this.useHttp = useHttp; } @@ -153,6 +197,9 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS setManagementHttpPort(resolveProperty(managementProperty("http.port"), enableClusterConfigurationAttributes.getNumber("port"))); + setManagementRequireHttps(resolveProperty(managementProperty("require-https"), + enableClusterConfigurationAttributes.getBoolean("requireHttps"))); + setManagementUseHttp(resolveProperty(managementProperty("use-http"), enableClusterConfigurationAttributes.getBoolean("useHttp"))); @@ -162,14 +209,15 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS } @Bean - public ClusterSchemaObjectInitializer gemfireClusterSchemaObjectInitializer(GemFireCache gemfireCache) { + public ClusterSchemaObjectInitializer gemfireClusterSchemaObjectInitializer(Environment environment, + GemFireCache gemfireCache) { return Optional.ofNullable(gemfireCache) .filter(CacheUtils::isClient) .map(clientCache -> { SchemaObjectContext schemaObjectContext = SchemaObjectContext.from(gemfireCache) - .with(newGemfireAdminOperations((ClientCache) clientCache)) + .with(resolveGemfireAdminOperations(environment, (ClientCache) clientCache)) .with(newSchemaObjectCollector()) .with(newSchemaObjectDefiner()); @@ -180,24 +228,92 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS } /** - * Constructs an instance of {@link GemfireAdminOperations} to perform administrative, schema functions + * Attempts to resolve a {@link List} of {@link ClientHttpRequestInterceptor} beans in the Spring + * {@link ApplicationContext}. + * + * @return a {@link List} of declared and registered {@link ClientHttpRequestInterceptor} beans. + * @see org.springframework.http.client.ClientHttpRequestInterceptor + * @see #getBeanFactory() + * @see java.util.List + */ + protected List resolveClientHttpRequestInterceptors() { + + return Optional.ofNullable(this.clientHttpRequestInterceptors) + .orElseGet(() -> + + Optional.of(getBeanFactory()) + .filter(ListableBeanFactory.class::isInstance) + .map(ListableBeanFactory.class::cast) + .map(beanFactory -> { + + Map beansOfType = beanFactory + .getBeansOfType(ClientHttpRequestInterceptor.class, true, false); + + return nullSafeMap(beansOfType).values().stream().collect(Collectors.toList()); + + }) + .orElseGet(Collections::emptyList)); + } + + /** + * Attempts to resolve the the {@link GemfireAdminOperations} object from the Spring {@link ApplicationContext} + * which is used to create Apache Geode or Pivotal GemFire schema objects. + * + * @param environment reference to the {@link Environment}. + * @param clientCache reference to the {@link ClientCache}. + * @return the resovled {@link GemfireAdminOperations} instance. + * @see org.springframework.core.env.Environment + * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations + * @see org.apache.geode.cache.client.ClientCache + * @see #newGemfireAdminOperations(Environment, ClientCache) + */ + protected GemfireAdminOperations resolveGemfireAdminOperations(Environment environment, ClientCache clientCache) { + + return Optional.ofNullable(this.gemfireAdminOperations) + .orElseGet(() -> newGemfireAdminOperations(environment, clientCache)); + } + + + /** + * Constructs a new instance of {@link GemfireAdminOperations} to perform administrative, schema functions * on a GemFire cache cluster as well as a client cache from a cache client. * + * @param environment reference to the {@link Environment}. * @param clientCache {@link ClientCache} instance used by the {@link GemfireAdminOperations} interface * to access the GemFire system. * @return an implementation of the {@link GemfireAdminOperations} interface to perform administrative functions * on a GemFire system. * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations * @see org.apache.geode.cache.client.ClientCache + * @see #resolveClientHttpRequestInterceptors() + * @see #resolveManagementHttpHost() + * @see #resolveManagementHttpPort() + * @see #resolveManagementRequireHttps() + * @see #resolveManagementUseHttp() */ - private GemfireAdminOperations newGemfireAdminOperations(ClientCache clientCache) { + private GemfireAdminOperations newGemfireAdminOperations(Environment environment, ClientCache clientCache) { if (resolveManagementUseHttp()) { - String host = resolveManagementHttpHost(); + boolean setFollowRedirects = + environment.getProperty(HTTP_FOLLOW_REDIRECTS_PROPERTY, Boolean.class, DEFAULT_HTTP_FOLLOW_REDIRECTS); + + boolean requireHttps = resolveManagementRequireHttps(); + boolean followRedirects = !requireHttps || setFollowRedirects; + int port = resolveManagementHttpPort(); - return new RestHttpGemfireAdminTemplate(clientCache, host, port); + String host = resolveManagementHttpHost(); + String scheme = requireHttps ? HTTPS_SCHEME : HTTP_SCHEME; + + return new RestHttpGemfireAdminTemplate.Builder() + .with(resolveClientHttpRequestInterceptors()) + .with(clientCache) + .using(scheme) + .on(host) + .listenOn(port) + .followRedirects(followRedirects) + .build(); } else { return new FunctionGemfireAdminTemplate(clientCache); @@ -205,7 +321,7 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS } /** - * Constructs an instance of {@link SchemaObjectCollector} to inspect the application's context + * Constructs a new instance of {@link SchemaObjectCollector} to inspect the application's context * and find all the GemFire schema objects declared of a particular type or types. * * @return a new instance of {@link SchemaObjectCollector} to inspect a GemFire system schema @@ -221,7 +337,7 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS } /** - * Constructs an instance of {@link SchemaObjectDefiner} used to reverse engineer a GemFire schema object instance + * Constructs a new instance of {@link SchemaObjectDefiner} used to reverse engineer a GemFire schema object instance * to build a definition. * * @return a new instance of {@link SchemaObjectDefiner}. @@ -240,7 +356,9 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS private final SchemaObjectContext schemaObjectContext; protected ClusterSchemaObjectInitializer(SchemaObjectContext schemaObjectContext) { + Assert.notNull(schemaObjectContext, "SchemaObjectContext is required"); + this.schemaObjectContext = schemaObjectContext; } @@ -254,7 +372,7 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS return Integer.MIN_VALUE; } - protected SchemaObjectContext getSchemaObjectContext() { + public SchemaObjectContext getSchemaObjectContext() { return this.schemaObjectContext; } @@ -268,14 +386,16 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS Iterable schemaObjects = schemaObjectContext.getSchemaObjectCollector() .collectFrom(requireApplicationContext()); - stream(schemaObjects.spliterator(), false) + //Iterable cacheSchemaObjects = schemaObjectContext.getSchemaObjectCollector() + // .collectFrom(schemaObjectContext.getGemfireCache()); + + StreamSupport.stream(schemaObjects.spliterator(), false) .map(schemaObjectContext.getSchemaObjectDefiner()::define) .sorted(OrderComparator.INSTANCE) - .forEach(schemaObjectDefinition -> schemaObjectDefinition - .ifPresent(it -> it.create(schemaObjectContext.getGemfireAdminOperations()))); + .forEach(schemaObjectDefinition -> schemaObjectDefinition.ifPresent(it -> + it.create(schemaObjectContext.getGemfireAdminOperations()))); setRunning(true); - } /* else if (schemaObjectContext.isPeerCache()) { @@ -285,7 +405,6 @@ public class ClusterConfigurationConfiguration extends AbstractAnnotationConfigS GemfireFunctionUtils.registerFunctionForPojoMethod(new CreateIndexFunction(), CreateIndexFunction.CREATE_INDEX_FUNCTION_ID); - } */ } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.java index c22872b0..6d138dae 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfiguration.java @@ -13,7 +13,6 @@ * 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; @@ -26,19 +25,22 @@ import java.lang.annotation.Target; import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientCache; import org.springframework.context.annotation.Import; /** - * The {@link EnableClusterConfiguration} annotation enables Apache Geode / Pivotal GemFire schema-like definitions - * defined in a Spring [Boot], Geode/GemFire cache client application using Spring config to be pushed to - * a Geode/GemFire cluster, similar to how schema commands (e.g. `create region`) in Gfsh are processed by - * an Geode/GemFire Manager. + * The {@link EnableClusterConfiguration} annotation enables Apache Geode / Pivotal GemFire schema object definitions + * defined in a Spring [Boot], Apache Geode / Pivotal GemFire {@link ClientCache} application using Spring config + * to be pushed to an Apache Geode / Pivotal GemFire cluster, similar to how schema commands (e.g. `create region`) + * in Gfsh are processed by an Apache Geode / Pivotal GemFire Manager. * * @author John Blum * @see java.lang.annotation.Documented * @see java.lang.annotation.Inherited * @see java.lang.annotation.Retention * @see java.lang.annotation.Target + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.client.ClientCache * @see org.springframework.context.annotation.Import * @see org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration * @since 2.0.0 @@ -75,6 +77,18 @@ public @interface EnableClusterConfiguration { */ int port() default ClusterConfigurationConfiguration.DEFAULT_MANAGEMENT_HTTP_PORT; + /** + * Configures whether the HTTP connection between Spring and Apache Geode or Pivotal GemFire should be secure. + * That is, whether the HTTP connections uses TLS and results in a secure HTTPS connection rather a plain text + * HTTP connection. + * + * Alternatively, you can configure this setting using the {@literal spring.data.gemfire.management.require-https} + * property in {@literal application.properties}. + * + * Defaults to {@literal true}. + */ + boolean requireHttps() default ClusterConfigurationConfiguration.DEFAULT_MANAGEMENT_REQUIRE_HTTPS; + /** * Configuration setting used to specify the data management policy used when creating {@link Region Regions} * on the servers in the Geode/GemFire cluster. diff --git a/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateBuilderUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateBuilderUnitTests.java new file mode 100644 index 00000000..86881aab --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateBuilderUnitTests.java @@ -0,0 +1,173 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.gemfire.config.admin.remote; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate.FollowRedirectsSimpleClientHttpRequestFactory; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Optional; + +import org.apache.geode.cache.client.ClientCache; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.InterceptingClientHttpRequestFactory; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.client.RestTemplate; + +/** + * Unit Tests for {@link RestHttpGemfireAdminTemplate.Builder}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.mockito.junit.MockitoJUnitRunner + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate.Builder + * @see org.springframework.http.client.ClientHttpRequestFactory + * @see org.springframework.http.client.ClientHttpRequestInterceptor + * @see org.springframework.web.client.RestTemplate + * @since 2.2.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class RestHttpGemfireAdminTemplateBuilderUnitTests { + + @Mock + private ClientCache mockClientCache; + + @SuppressWarnings("unchecked") + private T resolveFieldValue(Object target, String fieldName) throws NoSuchFieldException { + + Field field = ReflectionUtils.findField(target.getClass(), fieldName, ClientHttpRequestFactory.class); + + return Optional.ofNullable(field) + .map(it -> { + ReflectionUtils.makeAccessible(it); + return field; + }) + .map(it -> (T) ReflectionUtils.getField(it, target)) + .orElseThrow(() -> + new NoSuchFieldException(String.format("Field [%s] was not found on Object of type [%s]", + fieldName, target.getClass().getName()))); + } + + @Test + public void buildSuccessfullyBuildsNewRestHttpGemfireAdminTemplate() throws NoSuchFieldException { + + ClientHttpRequestInterceptor mockInterceptorOne = mock(ClientHttpRequestInterceptor.class); + ClientHttpRequestInterceptor mockInterceptorTwo = mock(ClientHttpRequestInterceptor.class); + ClientHttpRequestInterceptor mockInterceptorThree = mock(ClientHttpRequestInterceptor.class); + ClientHttpRequestInterceptor mockInterceptorFour = mock(ClientHttpRequestInterceptor.class); + ClientHttpRequestInterceptor mockInterceptorFive = mock(ClientHttpRequestInterceptor.class); + + RestHttpGemfireAdminTemplate template = new RestHttpGemfireAdminTemplate.Builder() + .with(this.mockClientCache) + .with(mockInterceptorOne, mockInterceptorTwo) + .with(mockInterceptorThree) + .with(Arrays.asList(mockInterceptorFour, mockInterceptorFive)) + .using("Http") + .on("skullbox") + .listenOn(81) + .followRedirects(false) + .build(); + + assertThat(template).isNotNull(); + assertThat(template.getClientCache()).isSameAs(this.mockClientCache); + assertThat(template.getManagementRestApiUrl()).isEqualTo("http://skullbox:81/gemfire/v1"); + assertThat(template.getRestOperations().getInterceptors()) + .containsExactly(mockInterceptorOne, mockInterceptorTwo, mockInterceptorThree, + mockInterceptorFour, mockInterceptorFive); + assertThat(template.getRestOperations().getRequestFactory()) + .isInstanceOf(InterceptingClientHttpRequestFactory.class); + + ClientHttpRequestFactory clientHttpRequestFactory = + resolveFieldValue(template.getRestOperations().getRequestFactory(), "requestFactory"); + + assertThat(clientHttpRequestFactory).isInstanceOf(FollowRedirectsSimpleClientHttpRequestFactory.class); + assertThat(((FollowRedirectsSimpleClientHttpRequestFactory) clientHttpRequestFactory).isFollowRedirects()) + .isFalse(); + } + + private void testInvalidPortThrowsException(int port) { + + try { + new RestHttpGemfireAdminTemplate.Builder().listenOn(port); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Port [%d] must be greater than 0 and less than 65536", port); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test(expected = IllegalArgumentException.class) + public void listenOnOverflowPortThrowsException() { + testInvalidPortThrowsException(65536); + } + + @Test(expected = IllegalArgumentException.class) + public void listenOnUnderflowPortThrowsException() { + testInvalidPortThrowsException(0); + } + + private void testInvalidHostnameDefaultsToLocalhost(String hostname) { + + RestHttpGemfireAdminTemplate template = new RestHttpGemfireAdminTemplate.Builder() + .with(this.mockClientCache) + .on(hostname) + .build(); + + assertThat(template).isNotNull(); + assertThat(template.getManagementRestApiUrl()).isEqualTo("https://localhost:7070/gemfire/v1"); + } + + @Test + public void onEmptyHostnameDefaultsToLocalhost() { + + testInvalidHostnameDefaultsToLocalhost(""); + testInvalidHostnameDefaultsToLocalhost(" "); + } + + @Test + public void onNoHostnameDefaultsToLocalhost() { + testInvalidHostnameDefaultsToLocalhost(null); + } + + @Test(expected = IllegalArgumentException.class) + public void usingInvalidScheme() { + + try { + new RestHttpGemfireAdminTemplate.Builder().using("ftp"); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Scheme [ftp] is not valid; must be 1 of %s", + RestHttpGemfireAdminTemplate.VALID_SCHEMES); + + assertThat(expected).hasNoCause(); + + throw expected; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateFollowRedirectsClientHttpRequestFactoryUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateFollowRedirectsClientHttpRequestFactoryUnitTests.java new file mode 100644 index 00000000..8a2da651 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateFollowRedirectsClientHttpRequestFactoryUnitTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.gemfire.config.admin.remote; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate.FollowRedirectsSimpleClientHttpRequestFactory; + +import java.io.IOException; +import java.net.HttpURLConnection; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +/** + * Unit Tests for {@link RestHttpGemfireAdminTemplate.FollowRedirectsSimpleClientHttpRequestFactory}. + * + * @author John Blum + * @see java.net.HttpURLConnection + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate.FollowRedirectsSimpleClientHttpRequestFactory + * @since 2.2.0 + */ +public class RestHttpGemfireAdminTemplateFollowRedirectsClientHttpRequestFactoryUnitTests { + + @Test + public void doesNotFollowRedirectsClientHttpRequestFactory() throws IOException { + + FollowRedirectsSimpleClientHttpRequestFactory clientHttpRequestFactory = + new FollowRedirectsSimpleClientHttpRequestFactory(false); + + assertThat(clientHttpRequestFactory).isNotNull(); + assertThat(clientHttpRequestFactory.isFollowRedirects()).isFalse(); + + HttpURLConnection mockHttpUrlConnection = mock(HttpURLConnection.class); + + doCallRealMethod().when(mockHttpUrlConnection).setInstanceFollowRedirects(anyBoolean()); + doCallRealMethod().when(mockHttpUrlConnection).getInstanceFollowRedirects(); + + clientHttpRequestFactory.prepareConnection(mockHttpUrlConnection, "GET"); + + assertThat(mockHttpUrlConnection.getInstanceFollowRedirects()).isFalse(); + + InOrder inOrder = Mockito.inOrder(mockHttpUrlConnection); + + inOrder.verify(mockHttpUrlConnection, times(1)) + .setInstanceFollowRedirects(eq(true)); + + inOrder.verify(mockHttpUrlConnection, times(1)) + .setInstanceFollowRedirects(eq(false)); + } + + @Test + public void followsRedirectsClientHttpRequestFactory() throws IOException { + + FollowRedirectsSimpleClientHttpRequestFactory clientHttpRequestFactory = + new FollowRedirectsSimpleClientHttpRequestFactory(true); + + assertThat(clientHttpRequestFactory).isNotNull(); + assertThat(clientHttpRequestFactory.isFollowRedirects()).isTrue(); + + HttpURLConnection mockHttpUrlConnection = mock(HttpURLConnection.class); + + doCallRealMethod().when(mockHttpUrlConnection).setInstanceFollowRedirects(anyBoolean()); + doCallRealMethod().when(mockHttpUrlConnection).getInstanceFollowRedirects(); + + clientHttpRequestFactory.prepareConnection(mockHttpUrlConnection, "POST"); + + assertThat(mockHttpUrlConnection.getInstanceFollowRedirects()).isTrue(); + + InOrder inOrder = Mockito.inOrder(mockHttpUrlConnection); + + inOrder.verify(mockHttpUrlConnection, times(1)) + .setInstanceFollowRedirects(eq(false)); + + inOrder.verify(mockHttpUrlConnection, times(1)) + .setInstanceFollowRedirects(eq(true)); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateUnitTests.java index 687e253b..ffb8c349 100644 --- a/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/admin/remote/RestHttpGemfireAdminTemplateUnitTests.java @@ -13,18 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.gemfire.config.admin.remote; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate.FollowRedirectsSimpleClientHttpRequestFactory; +import java.lang.reflect.Field; import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; import org.apache.geode.cache.Region; import org.apache.geode.cache.client.ClientCache; @@ -43,17 +49,27 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.InterceptingClientHttpRequestFactory; import org.springframework.util.MultiValueMap; +import org.springframework.util.ReflectionUtils; import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; /** * Unit tests for {@link RestHttpGemfireAdminTemplate}. * * @author John Blum + * @see java.net.URI * @see org.junit.Test * @see org.mockito.Mock * @see org.mockito.Mockito * @see org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate + * @see org.springframework.http.client.ClientHttpRequestFactory + * @see org.springframework.http.client.ClientHttpRequestInterceptor + * @see org.springframework.web.client.RestOperations + * @see org.springframework.web.client.RestTemplate * @since 2.0.0 */ @RunWith(MockitoJUnitRunner.class) @@ -77,16 +93,37 @@ public class RestHttpGemfireAdminTemplateUnitTests { public void setup() { this.template = new RestHttpGemfireAdminTemplate(this.mockClientCache) { - @Override protected RestOperations newRestOperations() { - return mockRestOperations; + + @Override + @SuppressWarnings("unchecked") + protected T newRestOperations(ClientHttpRequestFactory clientHttpRequestFactory, + List clientHttpRequestInterceptors) { + + return (T) mockRestOperations; } }; when(this.mockRegion.getName()).thenReturn("MockRegion"); + when(this.mockIndex.getType()).thenReturn(IndexType.FUNCTIONAL.getGemfireIndexType()); when(this.mockIndex.getName()).thenReturn("MockIndex"); when(this.mockIndex.getIndexedExpression()).thenReturn("age"); when(this.mockIndex.getFromClause()).thenReturn("/Customers"); - when(this.mockIndex.getType()).thenReturn(IndexType.FUNCTIONAL.getGemfireIndexType()); + } + + @SuppressWarnings("unchecked") + private T resolveFieldValue(Object target, String fieldName) throws NoSuchFieldException { + + Field field = ReflectionUtils.findField(target.getClass(), fieldName, ClientHttpRequestFactory.class); + + return Optional.ofNullable(field) + .map(it -> { + ReflectionUtils.makeAccessible(it); + return field; + }) + .map(it -> (T) ReflectionUtils.getField(it, target)) + .orElseThrow(() -> + new NoSuchFieldException(String.format("Field [%s] was not found on Object of type [%s]", + fieldName, target.getClass().getName()))); } @Test @@ -98,21 +135,133 @@ public class RestHttpGemfireAdminTemplateUnitTests { assertThat(template.getClientCache()).isSameAs(this.mockClientCache); assertThat(template.getManagementRestApiUrl()) .isEqualTo(String.format(RestHttpGemfireAdminTemplate.MANAGEMENT_REST_API_URL_TEMPLATE, - RestHttpGemfireAdminTemplate.DEFAULT_HOST, RestHttpGemfireAdminTemplate.DEFAULT_PORT)); - assertThat(template.getRestOperations()).isNotNull(); + RestHttpGemfireAdminTemplate.DEFAULT_SCHEME, RestHttpGemfireAdminTemplate.DEFAULT_HOST, + RestHttpGemfireAdminTemplate.DEFAULT_PORT)); + assertThat(template.getRestOperations()).isInstanceOf(RestTemplate.class); + + RestTemplate restTemplate = (RestTemplate) template.getRestOperations(); + + ClientHttpRequestFactory clientHttpRequestFactory = restTemplate.getRequestFactory(); + + assertThat(clientHttpRequestFactory).isInstanceOf(FollowRedirectsSimpleClientHttpRequestFactory.class); + assertThat(((FollowRedirectsSimpleClientHttpRequestFactory) clientHttpRequestFactory).isFollowRedirects()) + .isTrue(); + + List clientHttpRequestInterceptors = restTemplate.getInterceptors(); + + assertThat(clientHttpRequestInterceptors).isNotNull(); + assertThat(clientHttpRequestInterceptors).isEmpty(); } @Test - public void constructCustomRestHttpGemfireAdminTemplate() { + @SuppressWarnings("all") + public void constructCustomRestHttpGemfireAdminTemplate() throws Exception { + + ClientHttpRequestInterceptor mockInterceptor = mock(ClientHttpRequestInterceptor.class); RestHttpGemfireAdminTemplate template = - new RestHttpGemfireAdminTemplate(this.mockClientCache, "skullbox", 8080); + new RestHttpGemfireAdminTemplate(this.mockClientCache, "sftp", "skullbox", 8080, + false, Collections.singletonList(mockInterceptor)); assertThat(template).isNotNull(); assertThat(template.getClientCache()).isSameAs(this.mockClientCache); assertThat(template.getManagementRestApiUrl()) - .isEqualTo(String.format(RestHttpGemfireAdminTemplate.MANAGEMENT_REST_API_URL_TEMPLATE, "skullbox", 8080)); - assertThat(template.getRestOperations()).isNotNull(); + .isEqualTo(String.format(RestHttpGemfireAdminTemplate.MANAGEMENT_REST_API_URL_TEMPLATE, + "sftp", "skullbox", 8080)); + assertThat(template.getRestOperations()).isInstanceOf(RestTemplate.class); + + RestTemplate restTemplate = (RestTemplate) template.getRestOperations(); + + ClientHttpRequestFactory clientHttpRequestFactory = restTemplate.getRequestFactory(); + + assertThat(clientHttpRequestFactory).isInstanceOf(InterceptingClientHttpRequestFactory.class); + + clientHttpRequestFactory = this.resolveFieldValue(clientHttpRequestFactory, "requestFactory"); + + assertThat(clientHttpRequestFactory).isInstanceOf(FollowRedirectsSimpleClientHttpRequestFactory.class); + assertThat(((FollowRedirectsSimpleClientHttpRequestFactory) clientHttpRequestFactory).isFollowRedirects()) + .isFalse(); + + List clientHttpRequestInterceptors = restTemplate.getInterceptors(); + + assertThat(clientHttpRequestInterceptors).isNotNull(); + assertThat(clientHttpRequestInterceptors).contains(mockInterceptor); + } + + @Test + public void newClientHttpRequestFactoryFollowsRedirectsIsTrue() { + + ClientHttpRequestFactory clientHttpRequestFactory = + this.template.newClientHttpRequestFactory(true); + + assertThat(clientHttpRequestFactory).isInstanceOf(FollowRedirectsSimpleClientHttpRequestFactory.class); + assertThat(((FollowRedirectsSimpleClientHttpRequestFactory) clientHttpRequestFactory).isFollowRedirects()) + .isTrue(); + } + + @Test + public void newClientHttpRequestFactoryFollowsRedirectsIsFalse() { + + ClientHttpRequestFactory clientHttpRequestFactory = + this.template.newClientHttpRequestFactory(false); + + assertThat(clientHttpRequestFactory).isInstanceOf(FollowRedirectsSimpleClientHttpRequestFactory.class); + assertThat(((FollowRedirectsSimpleClientHttpRequestFactory) clientHttpRequestFactory).isFollowRedirects()) + .isFalse(); + } + + @Test + public void newRestOperationsWithInterceptors() { + + ClientHttpRequestFactory mockClientHttpRequestFactory = mock(ClientHttpRequestFactory.class); + + ClientHttpRequestInterceptor mockInterceptorOne = mock(ClientHttpRequestInterceptor.class); + ClientHttpRequestInterceptor mockInterceptorTwo = mock(ClientHttpRequestInterceptor.class); + + RestTemplate restTemplate = new RestHttpGemfireAdminTemplate(this.mockClientCache) + .newRestOperations(mockClientHttpRequestFactory, Arrays.asList(mockInterceptorOne, mockInterceptorTwo)); + + assertThat(restTemplate).isNotNull(); + assertThat(restTemplate.getInterceptors()).containsExactly(mockInterceptorOne, mockInterceptorTwo); + assertThat(restTemplate.getRequestFactory()).isNotSameAs(mockClientHttpRequestFactory); + assertThat(restTemplate.getRequestFactory()).isInstanceOf(InterceptingClientHttpRequestFactory.class); + } + + @Test + public void newRestOperationsWithNoInterceptors() { + + ClientHttpRequestFactory mockClientHttpRequestFactory = mock(ClientHttpRequestFactory.class); + + RestTemplate restTemplate = new RestHttpGemfireAdminTemplate(this.mockClientCache) + .newRestOperations(mockClientHttpRequestFactory, Collections.emptyList()); + + assertThat(restTemplate).isNotNull(); + assertThat(restTemplate.getInterceptors()).isEmpty(); + assertThat(restTemplate.getRequestFactory()).isSameAs(mockClientHttpRequestFactory); + } + + @Test + public void resolvesManagementRestApiUrlCorrectly() { + + assertThat(this.template.resolveManagementRestApiUrl("http", "boombox", 80)) + .isEqualTo(String.format(RestHttpGemfireAdminTemplate.MANAGEMENT_REST_API_URL_TEMPLATE, + "http", "boombox", 80)); + + assertThat(this.template.resolveManagementRestApiUrl("https", "cardboardbox", 443)) + .isEqualTo(String.format(RestHttpGemfireAdminTemplate.MANAGEMENT_REST_API_URL_TEMPLATE, + "https", "cardboardbox", 443)); + + assertThat(this.template.resolveManagementRestApiUrl("ftp", "jambox", 21)) + .isEqualTo(String.format(RestHttpGemfireAdminTemplate.MANAGEMENT_REST_API_URL_TEMPLATE, + "ftp", "jambox", 21)); + + assertThat(this.template.resolveManagementRestApiUrl("sftp", "mailbox", 22)) + .isEqualTo(String.format(RestHttpGemfireAdminTemplate.MANAGEMENT_REST_API_URL_TEMPLATE, + "sftp", "mailbox", 22)); + + assertThat(this.template.resolveManagementRestApiUrl("smtp", "skullbox", 25)) + .isEqualTo(String.format(RestHttpGemfireAdminTemplate.MANAGEMENT_REST_API_URL_TEMPLATE, + "smtp", "skullbox", 25)); } @Test @@ -127,7 +276,7 @@ public class RestHttpGemfireAdminTemplateUnitTests { assertThat(requestEntity).isNotNull(); assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST); - assertThat(requestEntity.getUrl()).isEqualTo(URI.create("http://localhost:7070/gemfire/v1/indexes")); + assertThat(requestEntity.getUrl()).isEqualTo(URI.create("https://localhost:7070/gemfire/v1/indexes")); HttpHeaders headers = requestEntity.getHeaders(); @@ -140,6 +289,7 @@ public class RestHttpGemfireAdminTemplateUnitTests { MultiValueMap requestBody = (MultiValueMap) body; + assertThat(requestBody).isNotNull(); assertThat(requestBody.getFirst("name")).isEqualTo(indexDefinition.getName()); assertThat(requestBody.getFirst("expression")).isEqualTo(indexDefinition.getExpression()); assertThat(requestBody.getFirst("region")).isEqualTo(indexDefinition.getFromClause()); @@ -166,7 +316,7 @@ public class RestHttpGemfireAdminTemplateUnitTests { assertThat(requestEntity).isNotNull(); assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST); - assertThat(requestEntity.getUrl()).isEqualTo(URI.create("http://localhost:7070/gemfire/v1/regions")); + assertThat(requestEntity.getUrl()).isEqualTo(URI.create("https://localhost:7070/gemfire/v1/regions")); HttpHeaders headers = requestEntity.getHeaders(); @@ -179,10 +329,11 @@ public class RestHttpGemfireAdminTemplateUnitTests { MultiValueMap requestBody = (MultiValueMap) body; + assertThat(requestBody).isNotNull(); assertThat(requestBody.getFirst("name")).isEqualTo(regionDefinition.getName()); assertThat(requestBody.getFirst("type")).isEqualTo(regionDefinition.getRegionShortcut().toString()); assertThat(requestBody.getFirst("skip-if-exists")) - .isEqualTo(String.valueOf(RestHttpGemfireAdminTemplate.CREATE_REGION_SKIP_IF_EXISTS_DEFAULT)); + .isEqualTo(String.valueOf(RestHttpGemfireAdminTemplate.DEFAULT_CREATE_REGION_SKIP_IF_EXISTS)); return new ResponseEntity(HttpStatus.OK); }); diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationWithClientHttpRequestInterceptorsIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationWithClientHttpRequestInterceptorsIntegrationTests.java new file mode 100644 index 00000000..ef34c641 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationWithClientHttpRequestInterceptorsIntegrationTests.java @@ -0,0 +1,188 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.gemfire.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration.ClusterSchemaObjectInitializer; +import static org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration.SchemaObjectContext; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Optional; + +import org.apache.geode.cache.client.ClientCache; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.core.annotation.Order; +import org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate; +import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.InterceptingClientHttpRequestFactory; +import org.springframework.lang.Nullable; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.client.RestTemplate; + +/** + * Integration Tests for {@link EnableClusterConfiguration} and {@link ClusterConfigurationConfiguration} asserting that + * all user-defined {@link ClientHttpRequestInterceptor} beans get applied. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration + * @see org.springframework.data.gemfire.config.annotation.EnableClusterConfiguration + * @see org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects + * @see org.springframework.http.client.ClientHttpRequestInterceptor + * @see org.springframework.http.client.InterceptingClientHttpRequestFactory + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 2.2.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@SuppressWarnings("unused") +public class ClusterConfigurationWithClientHttpRequestInterceptorsIntegrationTests { + + @Autowired + private ClientCache clientCache; + + @Autowired + @Qualifier("mockClientHttpRequestInterceptorOne") + private ClientHttpRequestInterceptor mockClientHttpRequestInterceptorOne; + + @Autowired + @Qualifier("mockClientHttpRequestInterceptorTwo") + private ClientHttpRequestInterceptor mockClientHttpRequestInterceptorTwo; + + @Autowired + private ClusterConfigurationConfiguration configuration; + + @Autowired + private ClusterSchemaObjectInitializer initializer; + + @Autowired + private List clientHttpRequestInterceptors; + + @SuppressWarnings("unchecked") + private T resolveFieldValue(Object target, String fieldName) throws NoSuchFieldException { + + Field field = ReflectionUtils.findField(target.getClass(), fieldName); + + return Optional.ofNullable(field) + .map(it -> { + ReflectionUtils.makeAccessible(it); + return field; + }) + .map(it -> (T) ReflectionUtils.getField(it, target)) + .orElseThrow(() -> + new NoSuchFieldException(String.format("Field with name [%s] was not found on Object of type [%s]", + fieldName, target.getClass().getName()))); + } + + @Before + public void setup() { + + assertThat(this.clientCache).isNotNull(); + assertThat(this.configuration).isNotNull(); + assertThat(this.initializer).isNotNull(); + assertThat(this.mockClientHttpRequestInterceptorOne).isNotNull(); + assertThat(this.mockClientHttpRequestInterceptorTwo).isNotNull(); + assertThat(this.clientHttpRequestInterceptors).isNotNull(); + assertThat(this.clientHttpRequestInterceptors).hasSize(2); + assertThat(this.clientHttpRequestInterceptors) + .containsExactly(this.mockClientHttpRequestInterceptorTwo, this.mockClientHttpRequestInterceptorOne); + } + + @Test + public void configurationWasAutowiredWithUserDefinedClientHttpRequestInterceptors() { + + assertThat(this.configuration.resolveClientHttpRequestInterceptors()) + .isEqualTo(this.clientHttpRequestInterceptors); + } + + @Test + public void clientHttpRequestInterceptorsRegistered() throws Exception { + + SchemaObjectContext schemaObjectContext = this.initializer.getSchemaObjectContext(); + + assertThat(schemaObjectContext).isNotNull(); + assertThat(schemaObjectContext.getGemfireCache()).isSameAs(this.clientCache); + assertThat(schemaObjectContext.getGemfireAdminOperations()).isInstanceOf(RestHttpGemfireAdminTemplate.class); + + RestHttpGemfireAdminTemplate template = + (RestHttpGemfireAdminTemplate) schemaObjectContext.getGemfireAdminOperations(); + + RestTemplate restTemplate = resolveFieldValue(template, "restTemplate"); + + assertThat(restTemplate).isNotNull(); + assertThat(restTemplate.getInterceptors()) + .containsExactly(this.mockClientHttpRequestInterceptorTwo, this.mockClientHttpRequestInterceptorOne); + assertThat(restTemplate.getRequestFactory()).isInstanceOf(InterceptingClientHttpRequestFactory.class); + } + + @ClientCacheApplication + @EnableGemFireMockObjects + @EnableClusterConfiguration(useHttp = true) + static class TestConfiguration { + + @Bean + BeanPostProcessor clusterSchemaObjectInitializerBeanPostProcessor() { + + return new BeanPostProcessor() { + + @Nullable @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + + if (bean instanceof ClusterSchemaObjectInitializer) { + + ClusterSchemaObjectInitializer initializer = spy((ClusterSchemaObjectInitializer) bean); + + doReturn(false).when(initializer).isAutoStartup(); + + bean = initializer; + } + + return bean; + } + }; + } + + @Bean + @Order(2) + ClientHttpRequestInterceptor mockClientHttpRequestInterceptorOne() { + return mock(ClientHttpRequestInterceptor.class); + } + + @Bean + @Order(1) + ClientHttpRequestInterceptor mockClientHttpRequestInterceptorTwo() { + return mock(ClientHttpRequestInterceptor.class); + } + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationWithCustomGemfireAdminOperationsIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationWithCustomGemfireAdminOperationsIntegrationTests.java new file mode 100644 index 00000000..ce587e1f --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ClusterConfigurationWithCustomGemfireAdminOperationsIntegrationTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.gemfire.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration.ClusterSchemaObjectInitializer; +import static org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration.SchemaObjectContext; + +import org.apache.geode.cache.client.ClientCache; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects; +import org.springframework.lang.Nullable; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration Tests for {@link EnableClusterConfiguration} and {@link ClusterConfigurationConfiguration} asserting that + * SDG support custom registered, user-defined {@link GemfireAdminOperations}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.data.gemfire.config.admin.GemfireAdminOperations + * @see org.springframework.data.gemfire.test.mock.annotation.EnableGemFireMockObjects + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 2.2.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@SuppressWarnings("unused") +public class ClusterConfigurationWithCustomGemfireAdminOperationsIntegrationTests { + + @Autowired + private ClientCache clientCache; + + @Autowired + private ClusterConfigurationConfiguration configuration; + + @Autowired + private ClusterSchemaObjectInitializer initializer; + + @Autowired + private GemfireAdminOperations gemfireAdminOperations; + + @Before + public void setup() { + + assertThat(this.clientCache).isNotNull(); + assertThat(this.configuration).isNotNull(); + assertThat(this.gemfireAdminOperations).isNotNull(); + assertThat(this.initializer).isNotNull(); + assertThat(this.configuration.resolveGemfireAdminOperations(null, this.clientCache)) + .isSameAs(this.gemfireAdminOperations); + } + + @Test + public void customGemfireAdminOperationsRegistered() { + + SchemaObjectContext schemaObjectContext = this.initializer.getSchemaObjectContext(); + + assertThat(schemaObjectContext).isNotNull(); + assertThat(schemaObjectContext.getGemfireAdminOperations()).isSameAs(this.gemfireAdminOperations); + } + + @ClientCacheApplication + @EnableGemFireMockObjects + @EnableClusterConfiguration + static class TestConfiguration { + + @Bean + BeanPostProcessor clusterSchemaObjectInitializerBeanPostProcessor() { + + return new BeanPostProcessor() { + + @Nullable @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + + if (bean instanceof ClusterSchemaObjectInitializer) { + + ClusterSchemaObjectInitializer initializer = spy((ClusterSchemaObjectInitializer) bean); + + doReturn(false).when(initializer).isAutoStartup(); + + bean = initializer; + } + + return bean; + } + }; + } + + @Bean + GemfireAdminOperations mockGemfireAdminOperations() { + return mock(GemfireAdminOperations.class); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationIntegrationTests.java index 517d6311..447c82b1 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationIntegrationTests.java @@ -13,7 +13,6 @@ * 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; @@ -50,11 +49,14 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; /** - * Integration tests for the {@link EnableClusterConfiguration} annotation + * Integration Tests for the {@link EnableClusterConfiguration} annotation * and {@link ClusterConfigurationConfiguration} class. * * @author John Blum * @see org.junit.Test + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.context.annotation.AnnotationConfigApplicationContext * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Configuration * @see org.springframework.context.annotation.Import @@ -74,6 +76,8 @@ import org.springframework.test.context.junit4.SpringRunner; @SuppressWarnings("unused") public class EnableClusterConfigurationIntegrationTests extends ClientServerIntegrationTestsSupport { + private static final String GEMFIRE_LOG_LEVEL = "error"; + private static ProcessWrapper gemfireServer; @Autowired @@ -96,24 +100,31 @@ public class EnableClusterConfigurationIntegrationTests extends ClientServerInte @AfterClass public static void stopGemFireServer() { - System.clearProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY); + stop(gemfireServer); + System.clearProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY); } @Before public void setup() { - this.adminOperations = new RestHttpGemfireAdminTemplate(this.gemfireCache, - "localhost", Integer.getInteger(GEMFIRE_CACHE_SERVER_PORT_PROPERTY, 40404)); + + this.adminOperations = new RestHttpGemfireAdminTemplate.Builder() + .with(this.gemfireCache) + .on("localhost") + .listenOn(Integer.getInteger(GEMFIRE_CACHE_SERVER_PORT_PROPERTY, 40404)) + .build(); } @Test public void serverIndexesAreCorrect() { + assertThat(this.adminOperations.getAvailableServerRegionIndexes()) .containsAll(Arrays.asList("IndexOne", "IndexTwo")); } @Test public void serverRegionsAreCorrect() { + assertThat(this.adminOperations.getAvailableServerRegions()) .containsAll(Arrays.asList("RegionOne", "RegionTwo", "RegionThree", "RegionFour")); } @@ -121,18 +132,17 @@ public class EnableClusterConfigurationIntegrationTests extends ClientServerInte @Configuration @EnableClusterConfiguration @Import(ClientTestConfiguration.class) - static class TestConfiguration { - } + static class TestConfiguration { } - @ClientCacheApplication(logLevel = TEST_GEMFIRE_LOG_LEVEL, subscriptionEnabled = true) + @ClientCacheApplication(logLevel = GEMFIRE_LOG_LEVEL, subscriptionEnabled = true) static class ClientTestConfiguration { @Bean ClientCacheConfigurer clientCachePoolPortConfigurer( @Value("${" + GEMFIRE_CACHE_SERVER_PORT_PROPERTY + ":40404}") int port) { - return (bean, clientCacheFactoryBean) -> clientCacheFactoryBean.setServers( - Collections.singletonList(new ConnectionEndpoint("localhost", port))); + return (bean, clientCacheFactoryBean) -> clientCacheFactoryBean + .setServers(Collections.singletonList(new ConnectionEndpoint("localhost", port))); } @Bean("IndexOne") @@ -186,7 +196,7 @@ public class EnableClusterConfigurationIntegrationTests extends ClientServerInte } } - @CacheServerApplication(name = "EnableClusterConfigurationIntegrationTests", logLevel = TEST_GEMFIRE_LOG_LEVEL) + @CacheServerApplication(name = "EnableClusterConfigurationIntegrationTests", logLevel = GEMFIRE_LOG_LEVEL) static class ServerTestConfiguration { public static void main(String[] args) { @@ -219,19 +229,7 @@ public class EnableClusterConfigurationIntegrationTests extends ClientServerInte } @Bean("RegionTwo") - PartitionedRegionFactoryBean regionTwo(GemFireCache gemfireCache) { - - PartitionedRegionFactoryBean regionFactoryBean = new PartitionedRegionFactoryBean<>(); - - regionFactoryBean.setCache(gemfireCache); - regionFactoryBean.setClose(false); - regionFactoryBean.setPersistent(false); - - return regionFactoryBean; - } - - @Bean("RegionFour") - ReplicatedRegionFactoryBean regionFour(GemFireCache gemfireCache) { + ReplicatedRegionFactoryBean regionTwo(GemFireCache gemfireCache) { ReplicatedRegionFactoryBean regionFactoryBean = new ReplicatedRegionFactoryBean<>(); @@ -241,5 +239,17 @@ public class EnableClusterConfigurationIntegrationTests extends ClientServerInte return regionFactoryBean; } + + @Bean("RegionFour") + PartitionedRegionFactoryBean regionFour(GemFireCache gemfireCache) { + + PartitionedRegionFactoryBean regionFactoryBean = new PartitionedRegionFactoryBean<>(); + + regionFactoryBean.setCache(gemfireCache); + regionFactoryBean.setClose(false); + regionFactoryBean.setPersistent(false); + + return regionFactoryBean; + } } } diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationUnitTests.java index cabc018b..bfd3ac4f 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/EnableClusterConfigurationUnitTests.java @@ -13,7 +13,6 @@ * 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; @@ -22,19 +21,45 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate.FollowRedirectsSimpleClientHttpRequestFactory; +import static org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration.ClusterSchemaObjectInitializer; +import static org.springframework.data.gemfire.config.annotation.ClusterConfigurationConfiguration.SchemaObjectContext; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; +import org.apache.geode.cache.Cache; import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.ClientCache; +import org.junit.After; import org.junit.Test; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.core.env.Environment; +import org.springframework.core.env.StandardEnvironment; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.config.admin.GemfireAdminOperations; +import org.springframework.data.gemfire.config.admin.remote.FunctionGemfireAdminTemplate; +import org.springframework.data.gemfire.config.admin.remote.RestHttpGemfireAdminTemplate; +import org.springframework.data.gemfire.config.schema.support.ComposableSchemaObjectCollector; +import org.springframework.data.gemfire.config.schema.support.ComposableSchemaObjectDefiner; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.InterceptingClientHttpRequestFactory; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.client.RestTemplate; /** * Unit tests for {@link EnableClusterConfiguration} annotation and the {@link ClusterConfigurationConfiguration} class. @@ -51,6 +76,44 @@ import org.springframework.core.type.AnnotationMetadata; */ public class EnableClusterConfigurationUnitTests { + @After + public void tearDown() { + System.clearProperty(ClusterConfigurationConfiguration.HTTP_FOLLOW_REDIRECTS_PROPERTY); + } + + private ClusterConfigurationConfiguration autowire(ClusterConfigurationConfiguration target, + String fieldName, T dependency) throws NoSuchFieldException { + + return Optional.ofNullable(ReflectionUtils.findField(target.getClass(), fieldName)) + .map(field -> { + ReflectionUtils.makeAccessible(field); + return field; + }) + .map(field -> { + ReflectionUtils.setField(field, target, dependency); + return target; + }) + .orElseThrow(() -> + new NoSuchFieldException(String.format("Field [%s] was not found on Object of type [%s]", + fieldName, target.getClass().getName()))); + } + + @SuppressWarnings("unchecked") + private T resolveFieldValue(Object target, String fieldName) throws NoSuchFieldException { + + Field field = ReflectionUtils.findField(target.getClass(), fieldName); + + return Optional.ofNullable(field) + .map(it -> { + ReflectionUtils.makeAccessible(it); + return field; + }) + .map(it -> (T) ReflectionUtils.getField(it, target)) + .orElseThrow(() -> + new NoSuchFieldException(String.format("Field with name [%s] was not found on Object of type [%s]", + fieldName, target.getClass().getName()))); + } + @Test public void setImportMetadataFromAnnotationAttributes() { @@ -60,6 +123,7 @@ public class EnableClusterConfigurationUnitTests { annotationAttributes.put("host", "skullbox"); annotationAttributes.put("port", 12345); + annotationAttributes.put("requireHttps", false); annotationAttributes.put("serverRegionShortcut", RegionShortcut.PARTITION_PERSISTENT); annotationAttributes.put("useHttp", true); @@ -74,6 +138,7 @@ public class EnableClusterConfigurationUnitTests { assertThat(configuration.getManagementHttpHost().orElse(null)).isEqualTo("skullbox"); assertThat(configuration.getManagementHttpPort().orElse(0)).isEqualTo(12345); + assertThat(configuration.getManagementRequireHttps().orElse(true)).isFalse(); assertThat(configuration.getManagementUseHttp().orElse(false)).isTrue(); assertThat(configuration.getServerRegionShortcut().orElse(null)).isEqualTo(RegionShortcut.PARTITION_PERSISTENT); @@ -93,6 +158,7 @@ public class EnableClusterConfigurationUnitTests { annotationAttributes.put("host", "skullbox"); annotationAttributes.put("port", 12345); + annotationAttributes.put("requireHttps", true); annotationAttributes.put("serverRegionShortcut", RegionShortcut.PARTITION_PERSISTENT); annotationAttributes.put("useHttp", false); @@ -106,6 +172,7 @@ public class EnableClusterConfigurationUnitTests { when(mockEnvironment.containsProperty(eq("spring.data.gemfire.cluster.region.type"))).thenReturn(true); when(mockEnvironment.containsProperty(eq("spring.data.gemfire.management.http.host"))).thenReturn(true); when(mockEnvironment.containsProperty(eq("spring.data.gemfire.management.http.port"))).thenReturn(true); + when(mockEnvironment.containsProperty(eq("spring.data.gemfire.management.require-https"))).thenReturn(true); when(mockEnvironment.containsProperty(eq("spring.data.gemfire.management.use-http"))).thenReturn(true); when(mockEnvironment.getProperty(eq("spring.data.gemfire.cluster.region.type"), eq(RegionShortcut.class), any(RegionShortcut.class))) @@ -117,6 +184,9 @@ public class EnableClusterConfigurationUnitTests { when(mockEnvironment.getProperty(eq("spring.data.gemfire.management.http.port"), eq(Integer.class), any(Integer.class))) .thenReturn(11235); + when(mockEnvironment.getProperty(eq("spring.data.gemfire.management.require-https"), eq(Boolean.class), any(Boolean.class))) + .thenReturn(false); + when(mockEnvironment.getProperty(eq("spring.data.gemfire.management.use-http"), eq(Boolean.class), any(Boolean.class))) .thenReturn(true); @@ -130,6 +200,7 @@ public class EnableClusterConfigurationUnitTests { assertThat(configuration.getManagementHttpHost().orElse(null)).isEqualTo("cardboardBox"); assertThat(configuration.getManagementHttpPort().orElse(0)).isEqualTo(11235); + assertThat(configuration.getManagementRequireHttps().orElse(true)).isFalse(); assertThat(configuration.getManagementUseHttp().orElse(false)).isTrue(); assertThat(configuration.getServerRegionShortcut().orElse(null)).isEqualTo(RegionShortcut.LOCAL); @@ -148,6 +219,9 @@ public class EnableClusterConfigurationUnitTests { verify(mockEnvironment, times(1)) .containsProperty(eq("spring.data.gemfire.management.http.port")); + verify(mockEnvironment, times(1)) + .containsProperty(eq("spring.data.gemfire.management.require-https")); + verify(mockEnvironment, times(1)) .containsProperty(eq("spring.data.gemfire.management.use-http")); @@ -161,6 +235,9 @@ public class EnableClusterConfigurationUnitTests { verify(mockEnvironment, times(1)) .getProperty(eq("spring.data.gemfire.management.http.port"), eq(Integer.class), anyInt()); + verify(mockEnvironment, times(1)) + .getProperty(eq("spring.data.gemfire.management.require-https"), eq(Boolean.class), eq(true)); + verify(mockEnvironment, times(1)) .getProperty(eq("spring.data.gemfire.management.use-http"), eq(Boolean.class), eq(false)); } @@ -174,6 +251,7 @@ public class EnableClusterConfigurationUnitTests { annotationAttributes.put("host", "postOfficeBox"); annotationAttributes.put("port", 10101); + annotationAttributes.put("requireHttps", false); annotationAttributes.put("serverRegionShortcut", RegionShortcut.REPLICATE); annotationAttributes.put("useHttp", true); @@ -187,10 +265,11 @@ public class EnableClusterConfigurationUnitTests { when(mockEnvironment.containsProperty(eq("spring.data.gemfire.cluster.region.type"))).thenReturn(false); when(mockEnvironment.containsProperty(eq("spring.data.gemfire.management.http.host"))).thenReturn(true); when(mockEnvironment.containsProperty(eq("spring.data.gemfire.management.http.port"))).thenReturn(true); + when(mockEnvironment.containsProperty(eq("spring.data.gemfire.management.require-https"))).thenReturn(false); when(mockEnvironment.containsProperty(eq("spring.data.gemfire.management.use-http"))).thenReturn(false); when(mockEnvironment.getProperty(eq("spring.data.gemfire.management.http.host"), eq(String.class), any(String.class))) - .thenReturn("shoeBox"); + .thenReturn("shoebox"); when(mockEnvironment.getProperty(eq("spring.data.gemfire.management.http.port"), eq(Integer.class), any(Integer.class))) .thenReturn(12480); @@ -203,8 +282,9 @@ public class EnableClusterConfigurationUnitTests { configuration.setEnvironment(mockEnvironment); configuration.setImportMetadata(mockImportMetadata); - assertThat(configuration.getManagementHttpHost().orElse(null)).isEqualTo("shoeBox"); + assertThat(configuration.getManagementHttpHost().orElse(null)).isEqualTo("shoebox"); assertThat(configuration.getManagementHttpPort().orElse(0)).isEqualTo(12480); + assertThat(configuration.getManagementRequireHttps().orElse(true)).isFalse(); assertThat(configuration.getManagementUseHttp().orElse(false)).isTrue(); assertThat(configuration.getServerRegionShortcut().orElse(null)).isEqualTo(RegionShortcut.REPLICATE); @@ -223,6 +303,9 @@ public class EnableClusterConfigurationUnitTests { verify(mockEnvironment, times(1)) .containsProperty(eq("spring.data.gemfire.management.http.port")); + verify(mockEnvironment, times(1)) + .containsProperty(eq("spring.data.gemfire.management.require-https")); + verify(mockEnvironment, times(1)) .containsProperty(eq("spring.data.gemfire.management.use-http")); @@ -236,7 +319,309 @@ public class EnableClusterConfigurationUnitTests { verify(mockEnvironment, times(1)) .getProperty(eq("spring.data.gemfire.management.http.port"), eq(Integer.class), anyInt()); + verify(mockEnvironment, never()) + .getProperty(eq("spring.data.gemfire.management.require-https"), eq(Boolean.class), anyBoolean()); + verify(mockEnvironment, never()) .getProperty(eq("spring.data.gemfire.management.use-http"), eq(Boolean.class), anyBoolean()); } + + @Test + public void gemfireClusterSchemaObjectInitializerBeanIsCorrect() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + ClientCache mockClientCache = mock(ClientCache.class); + + Environment mockEnvironment = mock(Environment.class); + + GemfireAdminOperations mockGemfireAdminOperations = mock(GemfireAdminOperations.class); + + ClusterConfigurationConfiguration configuration = spy(new ClusterConfigurationConfiguration()); + + doReturn(mockGemfireAdminOperations).when(configuration) + .resolveGemfireAdminOperations(eq(mockEnvironment), eq(mockClientCache)); + + configuration.setBeanFactory(mockBeanFactory); + + ClusterSchemaObjectInitializer initializer = + configuration.gemfireClusterSchemaObjectInitializer(mockEnvironment, mockClientCache); + + assertThat(initializer).isNotNull(); + assertThat(initializer.isAutoStartup()).isTrue(); + + SchemaObjectContext schemaObjectContext = initializer.getSchemaObjectContext(); + + assertThat(schemaObjectContext).isNotNull(); + assertThat(schemaObjectContext.getGemfireAdminOperations()).isEqualTo(mockGemfireAdminOperations); + assertThat(schemaObjectContext.getSchemaObjectCollector()).isInstanceOf(ComposableSchemaObjectCollector.class); + assertThat(schemaObjectContext.getSchemaObjectDefiner()).isInstanceOf(ComposableSchemaObjectDefiner.class); + + verify(configuration, never()).resolveClientHttpRequestInterceptors(); + verify(configuration, times(1)) + .resolveGemfireAdminOperations(eq(mockEnvironment), eq(mockClientCache)); + } + + @Test + public void gemfireClusterSchemaObjectInitializerBeanIsNullWhenGemFireCacheIsNull() { + + ClusterConfigurationConfiguration configuration = new ClusterConfigurationConfiguration(); + + Environment mockEnvironment = mock(Environment.class); + + assertThat(configuration.gemfireClusterSchemaObjectInitializer(mockEnvironment, null)).isNull(); + } + + @Test + public void gemfireClusterSchemaObjectInitializerBeanIsNullWhenGemFireCacheIsNotAClientCache() { + + Cache mockPeerCache = mock(Cache.class); + + ClusterConfigurationConfiguration configuration = new ClusterConfigurationConfiguration(); + + Environment mockEnvironment = mock(Environment.class); + + assertThat(configuration.gemfireClusterSchemaObjectInitializer(mockEnvironment, mockPeerCache)).isNull(); + } + + @Test + public void resolvesAutowiredClientHttpRequestInterceptors() throws Exception { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + ClientHttpRequestInterceptor mockInterceptorOne = mock(ClientHttpRequestInterceptor.class); + ClientHttpRequestInterceptor mockInterceptorTwo = mock(ClientHttpRequestInterceptor.class); + + List clientHttpRequestInterceptors = + Arrays.asList(mockInterceptorOne, mockInterceptorTwo); + + ClusterConfigurationConfiguration configuration = new ClusterConfigurationConfiguration(); + + configuration = autowire(configuration, "clientHttpRequestInterceptors", clientHttpRequestInterceptors); + configuration.setBeanFactory(mockBeanFactory); + + assertThat(configuration.resolveClientHttpRequestInterceptors()).isEqualTo(clientHttpRequestInterceptors); + + verifyZeroInteractions(mockBeanFactory); + } + + @Test + public void resolvesClientHttpRequestInterceptorsFromBeanFactory() { + + ListableBeanFactory mockBeanFactory = mock(ListableBeanFactory.class); + + ClientHttpRequestInterceptor mockInterceptorOne = mock(ClientHttpRequestInterceptor.class); + ClientHttpRequestInterceptor mockInterceptorTwo = mock(ClientHttpRequestInterceptor.class); + + Map expectedClientHttpRequestInterceptors = new HashMap<>(); + + expectedClientHttpRequestInterceptors.put("MockInterceptorOne", mockInterceptorOne); + expectedClientHttpRequestInterceptors.put("MockInterceptorTwo", mockInterceptorTwo); + + when(mockBeanFactory.getBeansOfType(eq(ClientHttpRequestInterceptor.class), anyBoolean(), anyBoolean())) + .thenReturn(expectedClientHttpRequestInterceptors); + + ClusterConfigurationConfiguration configuration = new ClusterConfigurationConfiguration(); + + configuration.setBeanFactory(mockBeanFactory); + + List actualClientHttpRequestInterceptors = + configuration.resolveClientHttpRequestInterceptors(); + + assertThat(actualClientHttpRequestInterceptors).isNotNull(); + assertThat(actualClientHttpRequestInterceptors).hasSize(expectedClientHttpRequestInterceptors.size()); + assertThat(actualClientHttpRequestInterceptors) + .containsExactlyInAnyOrder(expectedClientHttpRequestInterceptors.values() + .toArray(new ClientHttpRequestInterceptor[0])); + + verify(mockBeanFactory, times(1)) + .getBeansOfType(eq(ClientHttpRequestInterceptor.class), eq(true), eq(false)); + } + + @Test + public void resolveClientHttpRequestInterceptorsReturnsNullWhenBeanFactoryIsNotAListableBeanFactory() { + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + ClusterConfigurationConfiguration configuration = new ClusterConfigurationConfiguration(); + + configuration.setBeanFactory(mockBeanFactory); + + List clientHttpRequestInterceptors = + configuration.resolveClientHttpRequestInterceptors(); + + assertThat(clientHttpRequestInterceptors).isNotNull(); + assertThat(clientHttpRequestInterceptors).isEmpty(); + + verifyZeroInteractions(mockBeanFactory); + } + + @Test + public void resolvesAutowiredGemfireAdminOperations() throws Exception { + + GemfireAdminOperations mockGemfireAdminOperations = mock(GemfireAdminOperations.class); + + ClusterConfigurationConfiguration configuration = new ClusterConfigurationConfiguration(); + + configuration = autowire(configuration, "gemfireAdminOperations", mockGemfireAdminOperations); + + assertThat(configuration.resolveGemfireAdminOperations(null, null)) + .isSameAs(mockGemfireAdminOperations); + } + + @Test + public void resolvesNewFunctionGemfireAdminOperations() { + + ClientCache mockClientCache = mock(ClientCache.class); + + ClusterConfigurationConfiguration configuration = new ClusterConfigurationConfiguration(); + + GemfireAdminOperations operations = + configuration.resolveGemfireAdminOperations(null, mockClientCache); + + assertThat(operations).isInstanceOf(FunctionGemfireAdminTemplate.class); + + verifyZeroInteractions(mockClientCache); + } + + @Test + public void resolvesNewRestHttpGemfireAdminOperationsUsingDefaults() throws Exception { + + ClientCache mockClientCache = mock(ClientCache.class); + + Environment mockEnvironment = mock(Environment.class); + + when(mockEnvironment.getProperty(anyString(), eq(Boolean.class), anyBoolean())).thenReturn(false); + + ClusterConfigurationConfiguration configuration = spy(new ClusterConfigurationConfiguration()); + + doReturn(Collections.emptyList()).when(configuration).resolveClientHttpRequestInterceptors(); + + configuration.setManagementUseHttp(true); + + assertThat(configuration.resolveManagementRequireHttps()).isTrue(); + assertThat(configuration.resolveManagementUseHttp()).isTrue(); + + GemfireAdminOperations operations = + configuration.resolveGemfireAdminOperations(mockEnvironment, mockClientCache); + + assertThat(operations).isInstanceOf(RestHttpGemfireAdminTemplate.class); + + RestHttpGemfireAdminTemplate template = (RestHttpGemfireAdminTemplate) operations; + + RestTemplate restTemplate = resolveFieldValue(template, "restTemplate"); + + assertThat(restTemplate).isNotNull(); + assertThat(restTemplate.getInterceptors()).isEmpty(); + assertThat(restTemplate.getRequestFactory()).isInstanceOf(FollowRedirectsSimpleClientHttpRequestFactory.class); + + FollowRedirectsSimpleClientHttpRequestFactory clientHttpRequestFactory = + (FollowRedirectsSimpleClientHttpRequestFactory) restTemplate.getRequestFactory(); + + assertThat(clientHttpRequestFactory.isFollowRedirects()).isFalse(); + + verifyZeroInteractions(mockClientCache); + + verify(configuration, times(1)).resolveClientHttpRequestInterceptors(); + + verify(mockEnvironment, times(1)) + .getProperty(eq(ClusterConfigurationConfiguration.HTTP_FOLLOW_REDIRECTS_PROPERTY), eq(Boolean.class), + eq(ClusterConfigurationConfiguration.DEFAULT_HTTP_FOLLOW_REDIRECTS)); + } + + @Test + public void resolvesNewRestHttpGemfireAdminOperationsSetsFollowRedirectsWithProperty() throws Exception { + + System.setProperty(ClusterConfigurationConfiguration.HTTP_FOLLOW_REDIRECTS_PROPERTY, Boolean.TRUE.toString()); + + ClientCache mockClientCache = mock(ClientCache.class); + + Environment environment = spy(new StandardEnvironment()); + + ClusterConfigurationConfiguration configuration = spy(new ClusterConfigurationConfiguration()); + + doReturn(Collections.emptyList()).when(configuration).resolveClientHttpRequestInterceptors(); + + configuration.setManagementUseHttp(true); + + assertThat(configuration.resolveManagementRequireHttps()).isTrue(); + assertThat(configuration.resolveManagementUseHttp()).isTrue(); + + GemfireAdminOperations operations = + configuration.resolveGemfireAdminOperations(environment, mockClientCache); + + assertThat(operations).isInstanceOf(RestHttpGemfireAdminTemplate.class); + + RestHttpGemfireAdminTemplate template = (RestHttpGemfireAdminTemplate) operations; + + RestTemplate restTemplate = resolveFieldValue(template, "restTemplate"); + + assertThat(restTemplate).isNotNull(); + assertThat(restTemplate.getInterceptors()).isEmpty(); + assertThat(restTemplate.getRequestFactory()).isInstanceOf(FollowRedirectsSimpleClientHttpRequestFactory.class); + + FollowRedirectsSimpleClientHttpRequestFactory clientHttpRequestFactory = + (FollowRedirectsSimpleClientHttpRequestFactory) restTemplate.getRequestFactory(); + + assertThat(clientHttpRequestFactory.isFollowRedirects()).isTrue(); + + verifyZeroInteractions(mockClientCache); + + verify(configuration, times(1)).resolveClientHttpRequestInterceptors(); + + verify(environment, times(1)) + .getProperty(eq(ClusterConfigurationConfiguration.HTTP_FOLLOW_REDIRECTS_PROPERTY), eq(Boolean.class), + eq(ClusterConfigurationConfiguration.DEFAULT_HTTP_FOLLOW_REDIRECTS)); + } + + @Test + public void resolvesNewRestHttpGemfireAdminOperationsUsesClientHttpRequestInterceptorsSetsFollowRedirectsWhenUsingHttp() + throws Exception { + + assertThat(Boolean.getBoolean(ClusterConfigurationConfiguration.HTTP_FOLLOW_REDIRECTS_PROPERTY)).isFalse(); + + ClientCache mockClientCache = mock(ClientCache.class); + + ClientHttpRequestInterceptor mockInterceptorOne = mock(ClientHttpRequestInterceptor.class); + ClientHttpRequestInterceptor mockInterceptorTwo = mock(ClientHttpRequestInterceptor.class); + + Environment environment = spy(new StandardEnvironment()); + + ClusterConfigurationConfiguration configuration = spy(new ClusterConfigurationConfiguration()); + + doReturn(Arrays.asList(mockInterceptorOne, mockInterceptorTwo)) + .when(configuration).resolveClientHttpRequestInterceptors(); + + configuration.setManagementRequireHttps(false); + configuration.setManagementUseHttp(true); + + assertThat(configuration.resolveManagementRequireHttps()).isFalse(); + assertThat(configuration.resolveManagementUseHttp()).isTrue(); + + GemfireAdminOperations operations = + configuration.resolveGemfireAdminOperations(environment, mockClientCache); + + assertThat(operations).isInstanceOf(RestHttpGemfireAdminTemplate.class); + + RestHttpGemfireAdminTemplate template = (RestHttpGemfireAdminTemplate) operations; + + RestTemplate restTemplate = resolveFieldValue(template, "restTemplate"); + + assertThat(restTemplate).isNotNull(); + assertThat(restTemplate.getInterceptors()).containsExactly(mockInterceptorOne, mockInterceptorTwo); + assertThat(restTemplate.getRequestFactory()).isInstanceOf(InterceptingClientHttpRequestFactory.class); + + FollowRedirectsSimpleClientHttpRequestFactory clientHttpRequestFactory = + resolveFieldValue(restTemplate.getRequestFactory(), "requestFactory"); + + assertThat(clientHttpRequestFactory.isFollowRedirects()).isTrue(); + + verifyZeroInteractions(mockClientCache); + + verify(configuration, times(1)).resolveClientHttpRequestInterceptors(); + + verify(environment, times(1)) + .getProperty(eq(ClusterConfigurationConfiguration.HTTP_FOLLOW_REDIRECTS_PROPERTY), eq(Boolean.class), + eq(ClusterConfigurationConfiguration.DEFAULT_HTTP_FOLLOW_REDIRECTS)); + } }