CassandraBindingsPropertiesProcessor

Now tell me you don't love that implementation!

Signed-off-by: Ben Hale <bhale@vmware.com>
This commit is contained in:
Ben Hale
2020-05-09 07:50:15 -07:00
parent cd3ddc2799
commit 970620621c
16 changed files with 127 additions and 980 deletions

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2019 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.cloud.cnb.boot;
import org.springframework.cloud.cnb.Binding;
import java.util.Map;
public class CassandraCnbBindingProcessor implements CnbBindingProcessor {
public static final String CASSANDRA_KIND = "cassandra";
@Override
public boolean accept(Binding binding) {
return binding.getKind().equals(CASSANDRA_KIND);
}
@Override
public void process(Binding binding, Map<String, Object> properties) {
properties.put("spring.data.cassandra.username", binding.getSecret().get("username"));
properties.put("spring.data.cassandra.password", binding.getSecret().get("password"));
properties.put("spring.data.cassandra.contact-points", binding.getSecret().get("node_ips"));
properties.put("spring.data.cassandra.port", binding.getSecret().get("port"));
}
@Override
public CnbBindingProcessorProperties getProperties() {
return null;
}
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright 2019 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.cloud.cnb.boot;
import java.util.Map;
import org.springframework.cloud.cnb.Binding;
public interface CnbBindingProcessor {
boolean accept(Binding binding);
void process(Binding binding, Map<String, Object> properties);
CnbBindingProcessorProperties getProperties();
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright 2019 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.cloud.cnb.boot;
/**
* Properties describing a CfEnvProcessor, mainly used for better logging messages in {@link CnbBindingProcessor}.
*
* @author Mark Pollack
*/
public class CnbBindingProcessorProperties {
private String propertyPrefixes;
private String serviceName;
private CnbBindingProcessorProperties() {
}
public static Builder builder() {
return new CnbBindingProcessorProperties.Builder();
}
/**
* A string containing the values of property prefixes that will be set in the {@code process} method. Used
* for logging purposes.
* @return property prefix values set in the {@code process} method
*/
public String getPropertyPrefixes() {
return propertyPrefixes;
}
/**
* A name that describes the service being processed, eg. 'Redis', 'MongoDB'. Used for logging purposes.
* @return name that describes the service being processed
*/
public String getServiceName() {
return serviceName;
}
public static class Builder {
private CnbBindingProcessorProperties processorProperties = new CnbBindingProcessorProperties();
// Todo: add kind & provider, make serviceName the binding name
public Builder propertyPrefixes(String propertyPrefixes) {
this.processorProperties.propertyPrefixes = propertyPrefixes;
return this;
}
public Builder serviceName(String serviceName) {
this.processorProperties.serviceName = serviceName;
return this;
}
public CnbBindingProcessorProperties build() {
return processorProperties;
}
}
}

View File

@@ -1,147 +0,0 @@
/*
* Copyright 2019 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.cloud.cnb.boot;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.cloud.cnb.core.CNBBindingsSingleton;
import org.springframework.cloud.cnb.Binding;
import org.springframework.cloud.cnb.Bindings;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.stereotype.Component;
/**
* An EnvironmentPostProcessor that iterates over {@link CnbBindingProcessor} implementations to contribute
* Spring Boot properties for bound Cloud Foundry Services.
*
* Implementation of {@link CnbBindingProcessor }should be registered using the
* {@code resources/META-INF/spring.factories} property file.
*
* @author Mark Pollack
* @author David Turanski
*/
@Component
public class CnbBindingsPostProcessor implements EnvironmentPostProcessor, Ordered, ApplicationListener<ApplicationEvent> {
private static DeferredLog DEFERRED_LOG = new DeferredLog();
private static int invocationCount;
// Before ConfigFileApplicationListener so values there can use these ones
private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1;
public CnbBindingsPostProcessor() {
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// TODO: allow users to disable processing of a given binding by name, kind, or processor
increaseInvocationCount();
Bindings bindings = CNBBindingsSingleton.getCnbBindingsInstance();
if (bindings.hasBindings()) {
List<Binding> allBindings = bindings.findAllBindings();
List<CnbBindingProcessor> cnbBindingProcessors = SpringFactoriesLoader
.loadFactories(CnbBindingProcessor.class, getClass().getClassLoader());
AnnotationAwareOrderComparator.sort(cnbBindingProcessors);
for (CnbBindingProcessor processor : cnbBindingProcessors) {
List<Binding> cnbBindings = allBindings.stream()
.filter(processor::accept)
.collect(Collectors.toList());
if (cnbBindings.size() == 1) {
Binding binding = cnbBindings.get(0);
Map<String, Object> properties = new LinkedHashMap<>();
processor.process(binding, properties);
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources.contains(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new MapPropertySource(processor.getClass().getSimpleName(), properties));
} else {
propertySources.addFirst(
new MapPropertySource(processor.getClass().getSimpleName(), properties));
}
if (invocationCount == 1) {
DEFERRED_LOG.info(
"Setting " + processor.getProperties().getPropertyPrefixes() +
" properties from bound service ["
+ binding.getName() + "] using " + processor.getClass().getName());
}
}
}
} else {
if (invocationCount == 1) {
DEFERRED_LOG.debug(
"Not setting properties, CNB Bindings not detected");
}
}
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationPreparedEvent) {
DEFERRED_LOG
.switchTo(CnbBindingsPostProcessor.class);
}
}
/**
* EnvironmentPostProcessors can end up getting called twice due to spring-cloud-commons
* functionality
*/
protected void increaseInvocationCount() {
synchronized (this) {
invocationCount++;
}
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2019 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.cloud.cnb.boot;
import org.springframework.util.ClassUtils;
/**
* Determine if the Connector Library is on the classpath
* @author Mark Pollack
*/
public abstract class ConnectorLibraryDetector {
public static String MESSAGE = "Exiting the application since the Spring Cloud Connector library has been detected on the classpath. Please remove this dependency from your project and set the environment variable JBP_CONFIG_SPRING_AUTO_RECONFIGURATION '{enabled: false}' in the Cloud Foundry manifest.";
private static boolean usingConnectorLibrary;
static {
ClassLoader classLoader = ConnectorLibraryDetector.class.getClassLoader();
usingConnectorLibrary = ClassUtils.isPresent("org.springframework.cloud.Cloud", classLoader) ||
ClassUtils.isPresent("org.cloudfoundry.reconfiguration.org.springframework.cloud.Cloud", classLoader);
}
static boolean isUsingConnectorLibrary() {
return usingConnectorLibrary;
}
static void assertNoConnectorLibrary() {
if (ConnectorLibraryDetector.isUsingConnectorLibrary()) {
throw new IllegalStateException(ConnectorLibraryDetector.MESSAGE);
}
}
}

View File

@@ -1,269 +0,0 @@
/*
* Copyright 2012-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.cloud.cnb.boot;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Deferred {@link Log} that can be used to store messages that shouldn't be written until
* the logging system is fully initialized.
*
* @author Phillip Webb
* @since 1.3.0
*/
public class DeferredLog implements Log {
private volatile Log destination;
private final List<Line> lines = new ArrayList<>();
@Override
public boolean isTraceEnabled() {
synchronized (this.lines) {
return (this.destination != null) ? this.destination.isTraceEnabled() : true;
}
}
@Override
public boolean isDebugEnabled() {
synchronized (this.lines) {
return (this.destination != null) ? this.destination.isDebugEnabled() : true;
}
}
@Override
public boolean isInfoEnabled() {
synchronized (this.lines) {
return (this.destination != null) ? this.destination.isInfoEnabled() : true;
}
}
@Override
public boolean isWarnEnabled() {
synchronized (this.lines) {
return (this.destination != null) ? this.destination.isWarnEnabled() : true;
}
}
@Override
public boolean isErrorEnabled() {
synchronized (this.lines) {
return (this.destination != null) ? this.destination.isErrorEnabled() : true;
}
}
@Override
public boolean isFatalEnabled() {
synchronized (this.lines) {
return (this.destination != null) ? this.destination.isFatalEnabled() : true;
}
}
@Override
public void trace(Object message) {
log(LogLevel.TRACE, message, null);
}
@Override
public void trace(Object message, Throwable t) {
log(LogLevel.TRACE, message, t);
}
@Override
public void debug(Object message) {
log(LogLevel.DEBUG, message, null);
}
@Override
public void debug(Object message, Throwable t) {
log(LogLevel.DEBUG, message, t);
}
@Override
public void info(Object message) {
log(LogLevel.INFO, message, null);
}
@Override
public void info(Object message, Throwable t) {
log(LogLevel.INFO, message, t);
}
@Override
public void warn(Object message) {
log(LogLevel.WARN, message, null);
}
@Override
public void warn(Object message, Throwable t) {
log(LogLevel.WARN, message, t);
}
@Override
public void error(Object message) {
log(LogLevel.ERROR, message, null);
}
@Override
public void error(Object message, Throwable t) {
log(LogLevel.ERROR, message, t);
}
@Override
public void fatal(Object message) {
log(LogLevel.FATAL, message, null);
}
@Override
public void fatal(Object message, Throwable t) {
log(LogLevel.FATAL, message, t);
}
private void log(LogLevel level, Object message, Throwable t) {
synchronized (this.lines) {
if (this.destination != null) {
logTo(this.destination, level, message, t);
}
else {
this.lines.add(new Line(level, message, t));
}
}
}
/**
* Switch from deferred logging to immediate logging to the specified destination.
* @param destination the new log destination
* @since 2.1.0
*/
public void switchTo(Class<?> destination) {
switchTo(LogFactory.getLog(destination));
}
/**
* Switch from deferred logging to immediate logging to the specified destination.
* @param destination the new log destination
* @since 2.1.0
*/
public void switchTo(Log destination) {
synchronized (this.lines) {
replayTo(destination);
this.destination = destination;
}
}
/**
* Replay deferred logging to the specified destination.
* @param destination the destination for the deferred log messages
*/
public void replayTo(Class<?> destination) {
replayTo(LogFactory.getLog(destination));
}
/**
* Replay deferred logging to the specified destination.
* @param destination the destination for the deferred log messages
*/
public void replayTo(Log destination) {
synchronized (this.lines) {
for (Line line : this.lines) {
logTo(destination, line.getLevel(), line.getMessage(),
line.getThrowable());
}
this.lines.clear();
}
}
/**
* Replay from a source log to a destination log when the source is deferred.
* @param source the source logger
* @param destination the destination logger class
* @return the destination
*/
public static Log replay(Log source, Class<?> destination) {
return replay(source, LogFactory.getLog(destination));
}
/**
* Replay from a source log to a destination log when the source is deferred.
* @param source the source logger
* @param destination the destination logger
* @return the destination
*/
public static Log replay(Log source, Log destination) {
if (source instanceof DeferredLog) {
((DeferredLog) source).replayTo(destination);
}
return destination;
}
private static void logTo(Log log, LogLevel level, Object message,
Throwable throwable) {
switch (level) {
case TRACE:
log.trace(message, throwable);
return;
case DEBUG:
log.debug(message, throwable);
return;
case INFO:
log.info(message, throwable);
return;
case WARN:
log.warn(message, throwable);
return;
case ERROR:
log.error(message, throwable);
return;
case FATAL:
log.fatal(message, throwable);
return;
}
}
private static class Line {
private final LogLevel level;
private final Object message;
private final Throwable throwable;
Line(LogLevel level, Object message, Throwable throwable) {
this.level = level;
this.message = message;
this.throwable = throwable;
}
public LogLevel getLevel() {
return this.level;
}
public Object getMessage() {
return this.message;
}
public Throwable getThrowable() {
return this.throwable;
}
}
}

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2019 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.cloud.cnb.boot;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.cloud.cnb.Binding;
import static org.assertj.core.api.Assertions.assertThat;
public class CassandraCnbBindingProcessorTests {
@Test
public void acceptIfCassandraKind() {
CassandraCnbBindingProcessor bindingProcessor = new CassandraCnbBindingProcessor();
Map<String, String> bindingMetadata = new HashMap<String, String>();
bindingMetadata.put("kind", "cassandra");
Binding binding = new Binding(bindingMetadata, new HashMap<String, String>());
assertThat(bindingProcessor.accept(binding)).isTrue();
}
@Test
public void rejectIfNotCassandraKind() {
CassandraCnbBindingProcessor bindingProcessor = new CassandraCnbBindingProcessor();
Map<String, String> bindingMetadata = new HashMap<String, String>();
bindingMetadata.put("kind", "mysql");
Binding binding = new Binding(bindingMetadata, new HashMap<String, String>());
assertThat(bindingProcessor.accept(binding)).isFalse();
}
@Test
public void processDataSourcePropertiesTest() {
CassandraCnbBindingProcessor bindingProcessor = new CassandraCnbBindingProcessor();
Map<String, String> bindingMetadata = new HashMap<String, String>();
bindingMetadata.put("kind", "cassandra");
Map<String,String> bindingSecret = new HashMap<String,String>();
bindingSecret.put("port", "9042");
bindingSecret.put("node_ips", "10.0.4.35,10.0.4.36");
bindingSecret.put("password", "some-password");
bindingSecret.put("username", "some-username");
Binding binding = new Binding(bindingMetadata, bindingSecret);
Map<String,Object> properties = new HashMap<String,Object>();
bindingProcessor.process(binding, properties);
assertThat(properties.get("spring.data.cassandra.username")).isEqualTo("some-username");
assertThat(properties.get("spring.data.cassandra.password")).isEqualTo("some-password");
assertThat(properties.get("spring.data.cassandra.contact-points")).isEqualTo("10.0.4.35,10.0.4.36");
assertThat(properties.get("spring.data.cassandra.port")).isEqualTo("9042");
}
}

View File

@@ -1,90 +0,0 @@
/*
* Copyright 2019 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.cloud.cnb.boot;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import org.junit.Test;
import org.springframework.boot.Banner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.cnb.boot.test.EnvMock;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.CollectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
public class CnbBindingsPostProcessorTest {
@Test
public void testPostProcessEnvironment(){
Path resourceDirectory = Paths.get("src", "test", "resources", "test-bindings");
String bindingsDir = resourceDirectory.toFile().getAbsolutePath();
new EnvMock(bindingsDir);
CnbBindingsPostProcessor environmentPostProcessor = new CnbBindingsPostProcessor();
environmentPostProcessor.postProcessEnvironment(getEnvironment(),
null);
assertThat(getEnvironment().getProperty("spring.datasource.url"))
.isEqualTo("jdbc:mysql://10.0.4.35:3306/mysql_name?user=mysql_username&password=mysql_password");
assertThat(
getEnvironment().getProperty("spring.datasource.username"))
.isEqualTo("mysql_username");
assertThat(
getEnvironment().getProperty("spring.datasource.password"))
.isEqualTo("mysql_password");
}
// TODO: test multiple services
@Test
public void testNoBindingsPresent(){
CnbBindingsPostProcessor environmentPostProcessor = new CnbBindingsPostProcessor();
// doen't fail
environmentPostProcessor.postProcessEnvironment(getEnvironment(),
null);
}
public ConfigurableEnvironment getEnvironment() {
return getEnvironment(null);
}
public ConfigurableEnvironment getEnvironment(Map<String, Object> properties) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(TestApp.class)
.web(WebApplicationType.NONE);
if (!CollectionUtils.isEmpty(properties)) {
builder.properties(properties);
}
builder.bannerMode(Banner.Mode.OFF);
ConfigurableApplicationContext applicationContext = builder.run();
ConfigurableEnvironment environment = applicationContext.getEnvironment();
applicationContext.close();
return environment;
}
@SpringBootApplication
static class TestApp {
}
}

View File

@@ -1,194 +0,0 @@
/*
* Copyright 2019 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.cloud.cnb.boot;
import java.util.List;
import org.apache.commons.logging.Log;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link DeferredLog}.
*
* @author Phillip Webb
*/
public class DeferredLogTests {
private DeferredLog deferredLog = new DeferredLog();
private Object message = "Message";
private Throwable throwable = new IllegalStateException();
private Log log = mock(Log.class);
@Test
public void isTraceEnabled() {
assertThat(this.deferredLog.isTraceEnabled()).isTrue();
}
@Test
public void isDebugEnabled() {
assertThat(this.deferredLog.isDebugEnabled()).isTrue();
}
@Test
public void isInfoEnabled() {
assertThat(this.deferredLog.isInfoEnabled()).isTrue();
}
@Test
public void isWarnEnabled() {
assertThat(this.deferredLog.isWarnEnabled()).isTrue();
}
@Test
public void isErrorEnabled() {
assertThat(this.deferredLog.isErrorEnabled()).isTrue();
}
@Test
public void isFatalEnabled() {
assertThat(this.deferredLog.isFatalEnabled()).isTrue();
}
@Test
public void trace() {
this.deferredLog.trace(this.message);
this.deferredLog.replayTo(this.log);
verify(this.log).trace(this.message, null);
}
@Test
public void traceWithThrowable() {
this.deferredLog.trace(this.message, this.throwable);
this.deferredLog.replayTo(this.log);
verify(this.log).trace(this.message, this.throwable);
}
@Test
public void debug() {
this.deferredLog.debug(this.message);
this.deferredLog.replayTo(this.log);
verify(this.log).debug(this.message, null);
}
@Test
public void debugWithThrowable() {
this.deferredLog.debug(this.message, this.throwable);
this.deferredLog.replayTo(this.log);
verify(this.log).debug(this.message, this.throwable);
}
@Test
public void info() {
this.deferredLog.info(this.message);
this.deferredLog.replayTo(this.log);
verify(this.log).info(this.message, null);
}
@Test
public void infoWithThrowable() {
this.deferredLog.info(this.message, this.throwable);
this.deferredLog.replayTo(this.log);
verify(this.log).info(this.message, this.throwable);
}
@Test
public void warn() {
this.deferredLog.warn(this.message);
this.deferredLog.replayTo(this.log);
verify(this.log).warn(this.message, null);
}
@Test
public void warnWithThrowable() {
this.deferredLog.warn(this.message, this.throwable);
this.deferredLog.replayTo(this.log);
verify(this.log).warn(this.message, this.throwable);
}
@Test
public void error() {
this.deferredLog.error(this.message);
this.deferredLog.replayTo(this.log);
verify(this.log).error(this.message, null);
}
@Test
public void errorWithThrowable() {
this.deferredLog.error(this.message, this.throwable);
this.deferredLog.replayTo(this.log);
verify(this.log).error(this.message, this.throwable);
}
@Test
public void fatal() {
this.deferredLog.fatal(this.message);
this.deferredLog.replayTo(this.log);
verify(this.log).fatal(this.message, null);
}
@Test
public void fatalWithThrowable() {
this.deferredLog.fatal(this.message, this.throwable);
this.deferredLog.replayTo(this.log);
verify(this.log).fatal(this.message, this.throwable);
}
@Test
public void clearsOnReplayTo() {
this.deferredLog.info("1");
this.deferredLog.fatal("2");
Log log2 = mock(Log.class);
this.deferredLog.replayTo(this.log);
this.deferredLog.replayTo(log2);
verify(this.log).info("1", null);
verify(this.log).fatal("2", null);
verifyNoMoreInteractions(this.log);
verifyNoInteractions(log2);
}
@SuppressWarnings("unchecked")
@Test
public void switchTo() {
List<String> lines = (List<String>) ReflectionTestUtils.getField(this.deferredLog,
"lines");
assertThat(lines).isEmpty();
this.deferredLog.error(this.message, this.throwable);
assertThat(lines).hasSize(1);
this.deferredLog.switchTo(this.log);
assertThat(lines).isEmpty();
this.deferredLog.info("Message2");
assertThat(lines).isEmpty();
verify(this.log).error(this.message, this.throwable);
verify(this.log).info("Message2", null);
}
}

View File

@@ -50,12 +50,12 @@ public final class BindingsEnvironmentPostProcessor implements EnvironmentPostPr
*/
public static final String BINDINGS_PROPERTY_SOURCE_NAME = "cnbBindings";
final List<BindingsPropertiesProcessor> processors;
private final Log log = LogFactory.getLog(getClass());
private final Bindings bindings;
private final List<BindingsPropertiesProcessor> processors;
/**
* Creates a new instance of {@code BindingsEnvironmentPostProcessor} using the {@link Bindings} available in the
* environment and the {@link BindingsPropertiesProcessor}s registered with {@link SpringFactoriesLoader}.

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
*
* 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.cloud.bindings;
import org.jetbrains.annotations.NotNull;
import org.springframework.lang.NonNull;
import java.util.Map;
/**
* An implementation of {@link BindingsPropertiesProcessor} that detects {@link Binding}s of kind: {@value KIND}.
*/
public final class CassandraBindingsPropertiesProcessor implements BindingsPropertiesProcessor {
/**
* The {@link Binding} kind that this processor is interested in: {@value}.
**/
public static final String KIND = "cassandra";
@Override
public void process(@NonNull Bindings bindings, @NotNull Map<String, Object> properties) {
bindings.filterBindings(KIND).forEach(binding -> {
Map<String, String> secret = binding.getSecret();
properties.put("spring.data.cassandra.contact-points", secret.get("node_ips"));
properties.put("spring.data.cassandra.password", secret.get("password"));
properties.put("spring.data.cassandra.port", secret.get("port"));
properties.put("spring.data.cassandra.username", secret.get("username"));
});
}
}

View File

@@ -1,2 +1,5 @@
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.bindings.BindingsEnvironmentPostProcessor
# Included implementations
org.springframework.cloud.bindings.BindingsPropertiesProcessor=\
org.springframework.cloud.bindings.CassandraBindingsPropertiesProcessor

View File

@@ -113,4 +113,11 @@ final class BindingsEnvironmentPostProcessorTest {
assertThat(new BindingsEnvironmentPostProcessor(new Bindings()).getOrder())
.isLessThan(ConfigFileApplicationListener.DEFAULT_ORDER);
}
@Test
@DisplayName("included implementations are registered")
void includedImplementations() {
assertThat(new BindingsEnvironmentPostProcessor().processors).hasSize(1);
}
}

View File

@@ -23,8 +23,6 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -78,18 +76,13 @@ final class BindingsTests {
private final Bindings bindings = new Bindings(
new Binding("test-name-1", Paths.get("src/test/resources/test-name-1"),
metadata("test-kind-1", "test-provider-1"), Collections.emptyMap()),
new FluentMap().withEntry("kind", "test-kind-1").withEntry("provider", "test-provider-1"),
Collections.emptyMap()),
new Binding("test-name-2", Paths.get("src/test/resources/test-name-2"),
metadata("test-kind-2", "test-provider-2"), Collections.emptyMap())
new FluentMap().withEntry("kind", "test-kind-2").withEntry("provider", "test-provider-2"),
Collections.emptyMap())
);
private Map<String, String> metadata(String kind, String provider) {
Map<String, String> metadata = new HashMap<>(2);
metadata.put("kind", kind);
metadata.put("provider", provider);
return metadata;
}
@Test
@DisplayName("returns content")
void getBindings() {

View File

@@ -0,0 +1,55 @@
/*
* 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
*
* 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.cloud.bindings;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.bindings.CassandraBindingsPropertiesProcessor.KIND;
@DisplayName("Cassandra BindingsPropertiesProcessor")
final class CassandraBindingsPropertiesProcessorTest {
@Test
@DisplayName("contributes properties")
void test() {
HashMap<String, Object> properties = new HashMap<>();
new CassandraBindingsPropertiesProcessor().process(new Bindings(
new Binding("test-name", Paths.get("test-path"),
Collections.singletonMap("kind", KIND),
new FluentMap()
.withEntry("node_ips", "test-node-ips")
.withEntry("password", "test-password")
.withEntry("port", "test-port")
.withEntry("username", "test-username")
)
), properties);
assertThat(properties)
.containsEntry("spring.data.cassandra.contact-points", "test-node-ips")
.containsEntry("spring.data.cassandra.password", "test-password")
.containsEntry("spring.data.cassandra.port", "test-port")
.containsEntry("spring.data.cassandra.username", "test-username");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* 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.
@@ -13,15 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.cnb.boot;
/**
* Logging levels supported by a LoggingSystem.
*
* @author Phillip Webb
*/
public enum LogLevel {
package org.springframework.cloud.bindings;
TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
import java.util.HashMap;
final class FluentMap extends HashMap<String, String> {
FluentMap withEntry(String key, String value) {
put(key, value);
return this;
}
}