Add prototype implementation of the Spring Cloud Bindings BingindsPropertyProcessor interface.

Resolves gh-100.
This commit is contained in:
John Blum
2020-12-10 11:07:41 -08:00
parent 6dc0bbc02e
commit e2609d066c
5 changed files with 401 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2020 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
*
* https://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.geode.cloud.bindings;
import org.springframework.core.env.Environment;
/**
* Abstract utility class to assess the Spring {@link Environment}
*
* @author John Blum
* @see org.springframework.core.env.Environment
* @since 1.4.1
*/
@SuppressWarnings("unused")
public abstract class Guards {
public static final String SPRING_CLOUD_BOOT_BINDINGS_ENABLED_PROPERTY =
"org.springframework.cloud.bindings.boot.enable";
public static final String SPRING_CLOUD_BOOT_BINDINGS_TYPE_ENABLED_PROPERTY =
"org.springframework.cloud.bindings.boot.%s.enable";
public static boolean isGlobalEnabled(Environment environment) {
return environment.getProperty(SPRING_CLOUD_BOOT_BINDINGS_ENABLED_PROPERTY, Boolean.class, false);
}
public static boolean isTypeEnabled(Environment environment, String type) {
String propertyName = String.format(SPRING_CLOUD_BOOT_BINDINGS_TYPE_ENABLED_PROPERTY, type);
return environment.getProperty(propertyName, Boolean.class, true);
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2020 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
*
* https://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.geode.cloud.bindings;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Maps a source set of properties to a target set of properties.
*
* @author John Blum
* @see java.util.Map
* @see java.util.function.Function
* @since 1.4.1
*/
@SuppressWarnings("unused")
public class MapMapper {
private final Map<String, String> source;
private final Map<String, Object> target;
public MapMapper(@NonNull Map<String, String> source, @NonNull Map<String, Object> target) {
Assert.notNull(source, "Source Map must not be null");
Assert.notNull(target, "Target Map must not be null");
this.source = source;
this.target = target;
}
protected @NonNull Map<String, String> getSource() {
return source;
}
protected @NonNull Map<String, Object> getTarget() {
return target;
}
@SuppressWarnings("all")
public Source from(@NonNull String... keys) {
String[] resolvedKeys = Arrays.stream(ArrayUtils.nullSafeArray(keys, String.class))
.filter(StringUtils::hasText)
.collect(Collectors.toList())
.toArray(new String[0]);
return new Source(resolvedKeys);
}
protected interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
public class Source {
private final String[] keys;
private Source(@NonNull String[] keys) {
Assert.notNull(keys, "The String array of keys must not be null");
this.keys = keys;
}
public void to(String key) {
to(key, v -> v);
}
public void to(@NonNull String key, @NonNull Function<String, Object> function) {
String[] keys = this.keys;
Assert.state(keys.length == 1,
String.format("Source size [%d] cannot be transformed as one argument", keys.length));
Map<String, String> source = getSource();
if (Arrays.stream(keys).allMatch(source::containsKey)) {
getTarget().put(key, function.apply(source.get(keys[0])));
}
}
public void to(String key, TriFunction<String, String, String, Object> function) {
String[] keys = this.keys;
Assert.state(keys.length == 3,
String.format("Source size [%d] cannot be consumed as three arguments", keys.length));
Map<String, String> source = getSource();
if (Arrays.stream(keys).allMatch(source::containsKey)) {
getTarget().put(key, function.apply(source.get(keys[0]), source.get(keys[1]), source.get(keys[2])));
}
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2020 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
*
* https://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.geode.cloud.bindings.boot;
import java.util.Map;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor;
import org.springframework.core.env.Environment;
import org.springframework.geode.cloud.bindings.Guards;
import org.springframework.geode.cloud.bindings.MapMapper;
import org.springframework.lang.NonNull;
/**
* A Spring Cloud Bindings {@link BindingsPropertiesProcessor} for Apache Geode.
*
* @author John Blum
* @see java.util.Map
* @see org.springframework.cloud.bindings.Bindings
* @see org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor
* @see org.springframework.core.env.Environment
* @see org.springframework.geode.cloud.bindings.Guards
* @see org.springframework.geode.cloud.bindings.MapMapper
* @see Guards
* @since 1.4.1
*/
@SuppressWarnings("unused")
public class GeodeBindingsPropertiesProcessor implements BindingsPropertiesProcessor {
public static final String TYPE = "gemfire";
/**
* @inheritDoc
*/
@Override
public void process(@NonNull Environment environment, @NonNull Bindings bindings,
@NonNull Map<String, Object> properties) {
if (Guards.isTypeEnabled(environment, TYPE)) {
bindings.filterBindings(TYPE).forEach(binding -> {
// TODO - Change! Current mappings and property configuration is based on VMware Tanzu GemFire for VMS
// (i.e. PCC in PCF).
MapMapper mapMapper = new MapMapper(binding.getSecret(), properties);
mapMapper.from("gemfire.security-username").to("spring.data.gemfire.security.username");
mapMapper.from("gemfire.security-password").to("spring.data.gemfire.security.password");
mapMapper.from("gemfire.locators").to("spring.data.gemfire.pool.locators");
mapMapper.from("gemfire.http-service-bind-address").to("spring.data.gemfire.management.http.host");
mapMapper.from("gemfire.http-service-port").to("spring.data.gemfire.management.http.port");
properties.put("spring.data.gemfire.management.require-https", true);
properties.put("spring.data.gemfire.management.use-http", true);
properties.put("spring.data.gemfire.security.ssl.use-default-context", true);
});
}
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.cloud.bindings.boot.BindingsPropertiesProcessor=\
org.springframework.geode.cloud.bindings.boot.GeodeBindingsPropertiesProcessor

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2020 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
*
* https://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.geode.cloud.bindings.boot;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.cloud.bindings.Binding;
import org.springframework.cloud.bindings.Bindings;
import org.springframework.core.env.Environment;
import org.springframework.data.gemfire.tests.support.MapBuilder;
import org.springframework.geode.cloud.bindings.Guards;
/**
* Unit Tests for {@link GeodeBindingsPropertiesProcessor}.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mockito
* @see org.springframework.geode.cloud.bindings.boot.GeodeBindingsPropertiesProcessor
* @since 1.4.1
*/
public class GeodeBindingsPropertiesProcessorUnitTests {
@Test
public void processMapsCloudPropertiesToFrameworkPropertiesCorrectly() {
String propertyName = String.format(Guards.SPRING_CLOUD_BOOT_BINDINGS_TYPE_ENABLED_PROPERTY,
GeodeBindingsPropertiesProcessor.TYPE);
Environment mockEnvironment = mock(Environment.class);
doReturn(true)
.when(mockEnvironment).getProperty(eq(propertyName), eq(Boolean.class), eq(true));
Path mockPath= mock(Path.class);
Map<String, String> secret = MapBuilder.<String, String>newMapBuilder()
.put(Binding.TYPE, GeodeBindingsPropertiesProcessor.TYPE)
.put("gemfire.security-username", "DarthVader")
.put("gemfire.security-password", "s5!thL0rd")
.put("gemfire.locators", "mustafar[61616]")
.put("gemfire.http-service-bind-address", "10.100.101.69")
.put("gemfire.http-service-port", "7070")
.build();
Binding testBinding = new Binding("gemfire-test-binding-name", mockPath, secret);
Bindings testBindings = new Bindings(testBinding);
GeodeBindingsPropertiesProcessor bindingsPropertiesProcessor = new GeodeBindingsPropertiesProcessor();
Map<String, Object> properties = new HashMap<>();
bindingsPropertiesProcessor.process(mockEnvironment, testBindings, properties);
assertThat(properties)
.containsEntry("spring.data.gemfire.security.username", secret.get("gemfire.security-username"))
.containsEntry("spring.data.gemfire.security.password", secret.get("gemfire.security-password"))
.containsEntry("spring.data.gemfire.pool.locators", secret.get("gemfire.locators"))
.containsEntry("spring.data.gemfire.management.http.host", secret.get("gemfire.http-service-bind-address"))
.containsEntry("spring.data.gemfire.management.http.port", secret.get("gemfire.http-service-port"));
assertThat(Boolean.TRUE.equals(properties.get("spring.data.gemfire.management.require-https"))).isTrue();
assertThat(Boolean.TRUE.equals(properties.get("spring.data.gemfire.management.use-http"))).isTrue();
assertThat(Boolean.TRUE.equals(properties.get("spring.data.gemfire.security.ssl.use-default-context"))).isTrue();
verify(mockEnvironment, times(1))
.getProperty(eq(propertyName), eq(Boolean.class), eq(true));
verifyNoMoreInteractions(mockEnvironment);
}
@Test
public void processDoesNotMapPropertiesWhenGemFireTypeIsDisabled() {
String propertyName = String.format(Guards.SPRING_CLOUD_BOOT_BINDINGS_TYPE_ENABLED_PROPERTY,
GeodeBindingsPropertiesProcessor.TYPE);
Environment mockEnvironment = mock(Environment.class);
doReturn(false)
.when(mockEnvironment).getProperty(eq(propertyName), eq(Boolean.class), eq(true));
Path mockPath= mock(Path.class);
Binding testBinding = new Binding("gemfire-test-binding-name", mockPath,
Collections.singletonMap(Binding.TYPE, GeodeBindingsPropertiesProcessor.TYPE));
Bindings testBindings = new Bindings(testBinding);
GeodeBindingsPropertiesProcessor bindingsPropertiesProcessor = new GeodeBindingsPropertiesProcessor();
Map<String, Object> properties = new HashMap<>();
bindingsPropertiesProcessor.process(mockEnvironment, testBindings, properties);
assertThat(properties).isEmpty();
verify(mockEnvironment, times(1))
.getProperty(eq(propertyName), eq(Boolean.class), eq(true));
verifyNoMoreInteractions(mockEnvironment);
}
@Test
public void processDoesNotMapPropertiesWhenGemFireTypeIsNotPresent() {
String propertyName = String.format(Guards.SPRING_CLOUD_BOOT_BINDINGS_TYPE_ENABLED_PROPERTY,
GeodeBindingsPropertiesProcessor.TYPE);
Environment mockEnvironment = mock(Environment.class);
doReturn(true)
.when(mockEnvironment).getProperty(eq(propertyName), eq(Boolean.class), eq(true));
Path mockPath= mock(Path.class);
Binding testBinding = new Binding("mock-test-binding-name", mockPath,
Collections.singletonMap(Binding.TYPE, "mock"));
Bindings testBindings = new Bindings(testBinding);
GeodeBindingsPropertiesProcessor bindingsPropertiesProcessor = new GeodeBindingsPropertiesProcessor();
Map<String, Object> properties = new HashMap<>();
bindingsPropertiesProcessor.process(mockEnvironment, testBindings, properties);
assertThat(properties).isEmpty();
verify(mockEnvironment, times(1))
.getProperty(eq(propertyName), eq(Boolean.class), eq(true));
verifyNoMoreInteractions(mockEnvironment);
}
}