Commit 813128a4 authored by Phillip Webb's avatar Phillip Webb

Add support for configuration properties binding

Create a new `Binder` class specifically designed to bind properties
from one or more `ConfigurationPropertySources` to an object.

The binder provides a replacement for `RelaxedBinder` and attempts to
fix the limitations of the previous solution.

Closes gh-8868
parent a5651b75
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.util.Assert;
/**
* Abstract base class for {@link BindHandler} implementations.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public abstract class AbstractBindHandler implements BindHandler {
private final BindHandler parent;
/**
* Create a new binding handler instance.
*/
public AbstractBindHandler() {
this(BindHandler.DEFAULT);
}
/**
* Create a new binding handler instance with a specific parent.
* @param parent the parent handler
*/
public AbstractBindHandler(BindHandler parent) {
Assert.notNull(parent, "Parent must not be null");
this.parent = parent;
}
@Override
public boolean onStart(ConfigurationPropertyName name, Bindable<?> target,
BindContext context) {
return this.parent.onStart(name, target, context);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
return this.parent.onSuccess(name, target, context, result);
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception {
return this.parent.onFailure(name, target, context, error);
}
@Override
public void onFinish(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
this.parent.onFinish(name, target, context, result);
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.function.Supplier;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.ResolvableType;
/**
* Internal strategy used by {@link Binder} to bind aggregates (Maps, Lists, Arrays).
*
* @param <T> the type being bound
* @author Phillip Webb
* @author Madhura Bhave
*/
abstract class AggregateBinder<T> {
private final BindContext context;
AggregateBinder(BindContext context) {
this.context = context;
}
/**
* Perform binding for the aggregate.
* @param name the configuration property name to bind
* @param target the target to bind
* @param itemBinder an item binder
* @return the bound aggregate or null
*/
@SuppressWarnings("unchecked")
public final Object bind(ConfigurationPropertyName name, Bindable<?> target,
AggregateElementBinder itemBinder) {
Supplier<?> value = target.getValue();
Class<?> type = (value == null ? target.getType().resolve()
: ResolvableType.forClass(AggregateBinder.class, getClass())
.resolveGeneric());
Object result = bind(name, target, itemBinder, type);
if (result == null || value == null || value.get() == null) {
return result;
}
return merge((T) value.get(), (T) result);
}
/**
* Perform the actual aggregate binding.
* @param name the configuration property name to bind
* @param target the target to bind
* @param elementBinder an element binder
* @param type the aggregate actual type to use
* @return the bound result
*/
protected abstract Object bind(ConfigurationPropertyName name, Bindable<?> target,
AggregateElementBinder elementBinder, Class<?> type);
/**
* Merge any additional elements into the existing aggregate.
* @param existing the existing value
* @param additional the additional elements to merge
* @return the merged result
*/
protected abstract T merge(T existing, T additional);
/**
* Return the context being used by this binder.
* @return the context
*/
protected final BindContext getContext() {
return this.context;
}
/**
* Roll up the given name to the first element below the root. For example a name of
* {@code foo.bar.baz} rolled up to the root {@code foo} would be {@code foo.bar}.
* @param name the name to roll up
* @param root the root name
* @return the rolled up name or {@code null}
*/
protected final ConfigurationPropertyName rollUp(ConfigurationPropertyName name,
ConfigurationPropertyName root) {
while (name != null && (name.getParent() != null)
&& (!root.equals(name.getParent()))) {
name = name.getParent();
}
return name;
}
/**
* Internal class used to supply the aggregate and cache the value.
* @param <T> The aggregate type
*/
protected static class AggregateSupplier<T> {
private final Supplier<T> supplier;
private T supplied;
public AggregateSupplier(Supplier<T> supplier) {
this.supplier = supplier;
}
public T get() {
if (this.supplied == null) {
this.supplied = this.supplier.get();
}
return this.supplied;
}
public boolean wasSupplied() {
return this.supplied != null;
}
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
/**
* Binder that can be used by {@link AggregateBinder} implementations to recursively bind
* elements.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
@FunctionalInterface
interface AggregateElementBinder {
/**
* Bind the given name to a target bindable.
* @param name the name to bind
* @param target the target bindable
* @return a bound object or {@code null}
*/
default Object bind(ConfigurationPropertyName name, Bindable<?> target) {
return bind(name, target, null);
}
/**
* Bind the given name to a target bindable using optionally limited to a single
* source.
* @param name the name to bind
* @param target the target bindable
* @param source the source of the elements or {@code null} to use all sources
* @return a bound object or {@code null}
*/
Object bind(ConfigurationPropertyName name, Bindable<?> target,
ConfigurationPropertySource source);
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.ResolvableType;
/**
* {@link AggregateBinder} for arrays.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ArrayBinder extends IndexedElementsBinder<Object> {
ArrayBinder(BindContext context) {
super(context);
}
@Override
protected Object bind(ConfigurationPropertyName name, Bindable<?> target,
AggregateElementBinder elementBinder, Class<?> type) {
IndexedCollectionSupplier collection = new IndexedCollectionSupplier(
ArrayList::new);
ResolvableType elementType = target.getType().getComponentType();
bindIndexed(name, target, elementBinder, collection, target.getType(),
elementType);
if (collection.wasSupplied()) {
List<Object> list = (List<Object>) collection.get();
Object array = Array.newInstance(elementType.resolve(), list.size());
for (int i = 0; i < list.size(); i++) {
Array.set(array, i, list.get(i));
}
return array;
}
return null;
}
@Override
protected Object merge(Object existing, Object additional) {
return additional;
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
/**
* Internal strategy used by {@link Binder} to bind beans.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
interface BeanBinder {
/**
* Return a bound bean instance or {@code null} if the {@link BeanBinder} does not
* support the specified {@link Bindable}.
* @param target the binable to bind
* @param hasKnownBindableProperties if this binder has known bindable elements. If
* names from underlying {@link ConfigurationPropertySource} cannot be iterated this
* method can be {@code false}, even though binding may ultimately succeed.
* @param propertyBinder property binder
* @param <T> The source type
* @return a bound instance or {@code null}
*/
<T> T bind(Bindable<T> target, boolean hasKnownBindableProperties,
BeanPropertyBinder propertyBinder);
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
/**
* Binder that can be used by {@link BeanBinder} implementations to recursively bind bean
* properties.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
interface BeanPropertyBinder {
/**
* Bind the given property.
* @param propertyName the property name (in lowercase dashed form, e.g.
* {@code first-name})
* @param target the target bindable
* @return the bound value or {@code null}
*/
Object bindProperty(String propertyName, Bindable<?> target);
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
/**
* Internal utility to help when dealing with Java Bean property names.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
abstract class BeanPropertyName {
private BeanPropertyName() {
}
/**
* Return the specified Java Bean property name in dashed form.
* @param name the source name
* @return the dashed from
*/
public static String toDashedForm(String name) {
return toDashedForm(name, 0);
}
/**
* Return the specified Java Bean property name in dashed form.
* @param name the source name
* @param start the starting char
* @return the dashed from
*/
public static String toDashedForm(String name, int start) {
StringBuilder result = new StringBuilder();
char[] chars = name.replace("_", "-").toCharArray();
for (int i = start; i < chars.length; i++) {
char ch = chars[i];
if (Character.isUpperCase(ch) && result.length() > 0
&& result.charAt(result.length() - 1) != '-') {
result.append("-");
}
result.append(Character.toLowerCase(ch));
}
return result.toString();
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.core.convert.ConversionService;
/**
* Context information for use by {@link BindHandler BindHandlers}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public interface BindContext {
/**
* Return the current depth of the binding. Root binding starts with a depth of
* {@code 0}. Each subsequent property binding increases the depth by {@code 1}.
* @return the depth of the current binding
*/
int getDepth();
/**
* Return the {@link ConfigurationPropertySource sources} being used by the
* {@link Binder}.
* @return the sources
*/
Iterable<ConfigurationPropertySource> getSources();
/**
* Return the {@link ConfigurationProperty} actually being bound or {@code null} if
* the property has not yet been determined.
* @return the configuration property (may be {@code null}).
*/
ConfigurationProperty getConfigurationProperty();
/**
* Return the {@link PlaceholdersResolver} being used by the binder.
* @return the {@link PlaceholdersResolver} (never {@code null})
*/
PlaceholdersResolver getPlaceholdersResolver();
/**
* Return the {@link ConversionService} used by the binder.
* @return the conversion service
*/
BinderConversionService getConversionService();
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginProvider;
/**
* Exception thrown when binding fails.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public class BindException extends RuntimeException implements OriginProvider {
private final Bindable<?> target;
private final ConfigurationProperty property;
private final ConfigurationPropertyName name;
BindException(ConfigurationPropertyName name, Bindable<?> target,
ConfigurationProperty property, Throwable cause) {
super(buildMessage(name, target), cause);
this.name = name;
this.target = target;
this.property = property;
}
/**
* Return the name of the configuration property being bound.
* @return the configuration property name
*/
public ConfigurationPropertyName getName() {
return this.name;
}
/**
* Return the target being bound.
* @return the bind target
*/
public Bindable<?> getTarget() {
return this.target;
}
/**
* Return the configuration property name of the item that was being bound.
* @return the configuration property name
*/
public ConfigurationProperty getProperty() {
return this.property;
}
@Override
public Origin getOrigin() {
return Origin.from(this.name);
}
private static String buildMessage(ConfigurationPropertyName name,
Bindable<?> target) {
StringBuilder message = new StringBuilder();
message.append("Failed to bind properties");
message.append(name == null ? "" : " under '" + name + "'");
message.append(" to " + target.getType());
return message.toString();
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
/**
* Callback interface that can be used to handle additional logic during element
* {@link Binder binding}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public interface BindHandler {
/**
* Default no-op bind hander.
*/
BindHandler DEFAULT = new BindHandler() {
};
/**
* Called when binding of an element starts but before any result has been determined.
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @return {@code true} if binding should proceed
*/
default boolean onStart(ConfigurationPropertyName name, Bindable<?> target,
BindContext context) {
return true;
}
/**
* Called when binding of an element ends with a successful result. Implementations
* may change the ultimately returned result or perform addition validation.
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param result the bound result (never {@code null})
* @return the actual result that should be used (may be {@code null})
*/
default Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
return result;
}
/**
* Called when binding fails for any reason (including failures from
* {@link #onSuccess} calls). Implementations may chose to swallow exceptions and
* return an alternative result.
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param error the cause of the error (if the exception stands it may be re-thrown)
* @return the actual result that should be used (may be {@code null}).
* @throws Exception if the binding isn't valid
*/
default Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception {
throw error;
}
/**
* Called when binding finishes, regardless of whether the property was bound or not.
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param result the bound result (may be {@code null})
* @throws Exception if the binding isn't valid
*/
default void onFinish(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* A container object to return result of a {@link Binder} bind operation. May contain
* either a successfully bound object or an empty result.
*
* @param <T> The result type
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public final class BindResult<T> {
private static final BindResult<?> UNBOUND = new BindResult<>(null);
private final T value;
private BindResult(T value) {
this.value = value;
}
/**
* Return the object that was bound or throw a {@link NoSuchElementException} if no
* value was bound.
* @return the the bound value (never {@code null})
* @throws NoSuchElementException if no value was bound
* @see #isBound()
*/
public T get() throws NoSuchElementException {
if (this.value == null) {
throw new NoSuchElementException("No value bound");
}
return this.value;
}
/**
* Returns {@code true} if a result was bound.
* @return if a result was bound
*/
public boolean isBound() {
return (this.value != null);
}
/**
* Invoke the specified consumer with the bound value, or do nothing if no value has
* been bound.
* @param consumer block to execute if a value has been bound
*/
public void ifBound(Consumer<? super T> consumer) {
Assert.notNull(consumer, "Consumer must not be null");
if (this.value != null) {
consumer.accept(this.value);
}
}
/**
* Apply the provided mapping function to the bound value, or return an updated
* unbound result if no value has been bound.
* @param <U> The type of the result of the mapping function
* @param mapper a mapping function to apply to the bound value. The mapper will not
* be invoked if no value has been bound.
* @return an {@code BindResult} describing the result of applying a mapping function
* to the value of this {@code BindResult}.
*/
public <U> BindResult<U> map(Function<? super T, ? extends U> mapper) {
Assert.notNull(mapper, "Mapper must not be null");
return of(this.value == null ? null : mapper.apply(this.value));
}
/**
* Return the object that was bound, or {@code other} if no value has been bound.
* @param other the value to be returned if there is no bound value (may be
* {@code null})
* @return the value, if bound, otherwise {@code other}
*/
public T orElse(T other) {
return (this.value != null ? this.value : other);
}
/**
* Return the object that was bound, or the result of invoking {@code other} if no
* value has been bound.
* @param other a {@link Supplier} of the value to be returned if there is no bound
* value
* @return the value, if bound, otherwise the supplied {@code other}
*/
public T orElseGet(Supplier<? extends T> other) {
return (this.value != null ? this.value : other.get());
}
/**
* Return the object that was bound, or a new instance of the specified class if no
* value has been bound.
* @param type the type to create if no value was bound
* @return the value, if bound, otherwise a new instance of {@code type}
*/
public T orElseCreate(Class<? extends T> type) {
Assert.notNull(type, "Type must not be null");
return (this.value != null ? this.value : BeanUtils.instantiateClass(type));
}
/**
* Return the object that was bound, or throw an exception to be created by the
* provided supplier if no value has been bound.
* @param <X> Type of the exception to be thrown
* @param exceptionSupplier The supplier which will return the exception to be thrown
* @return the present value
* @throws X if there is no value present
*/
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
throws X {
if (this.value == null) {
throw exceptionSupplier.get();
}
return this.value;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.value);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return ObjectUtils.nullSafeEquals(this.value, ((BindResult<?>) obj).value);
}
@SuppressWarnings("unchecked")
static <T> BindResult<T> of(T value) {
if (value == null) {
return (BindResult<T>) UNBOUND;
}
return new BindResult<T>(value);
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Source that can be bound by a {@link Binder}.
*
* @param <T> The source type
* @author Philip Webb
* @author Madhura Bhave
* @since 2.0.0
* @see Bindable#of(Class)
* @see Bindable#of(ResolvableType)
*/
public final class Bindable<T> {
private static final Annotation[] NO_ANNOTATIONS = {};
private final ResolvableType type;
private final ResolvableType boxedType;
private final Supplier<T> value;
private final Annotation[] annotations;
private Bindable(ResolvableType type, ResolvableType boxedType, Supplier<T> value,
Annotation[] annotations) {
this.type = type;
this.boxedType = boxedType;
this.value = value;
this.annotations = annotations;
}
/**
* Return the type of the item to bind.
* @return the type being bound
*/
public ResolvableType getType() {
return this.type;
}
public ResolvableType getBoxedType() {
return this.boxedType;
}
/**
* Return a supplier that provides the object value or {@code null}.
* @return the value or {@code null}
*/
public Supplier<T> getValue() {
return this.value;
}
/**
* Return any associated annotations that could affect binding.
* @return the associated annotations
*/
public Annotation[] getAnnotations() {
return this.annotations;
}
@Override
public String toString() {
ToStringCreator creator = new ToStringCreator(this);
creator.append("type", this.type);
creator.append("value", (this.value == null ? "none" : "provided"));
creator.append("annotations", this.annotations);
return creator.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.type);
result = prime * result + ObjectUtils.nullSafeHashCode(this.annotations);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Bindable<?> other = (Bindable<?>) obj;
boolean result = true;
result = result && nullSafeEquals(this.type.resolve(), other.type.resolve());
result = result && nullSafeEquals(this.annotations, other.annotations);
return result;
}
private boolean nullSafeEquals(Object o1, Object o2) {
return ObjectUtils.nullSafeEquals(o1, o2);
}
/**
* Create an updated {@link Bindable} instance with the specified annotations.
* @param annotations the annotations
* @return an updated {@link Bindable}
*/
public Bindable<T> withAnnotations(Annotation... annotations) {
return new Bindable<T>(this.type, this.boxedType, this.value,
(annotations == null ? NO_ANNOTATIONS : annotations));
}
public Bindable<T> withExistingValue(T existingValue) {
Assert.isTrue(
existingValue == null || this.type.isArray()
|| this.boxedType.resolve().isInstance(existingValue),
"ExistingValue must be an instance of " + this.type);
Supplier<T> value = (existingValue == null ? null : () -> existingValue);
return new Bindable<>(this.type, this.boxedType, value, NO_ANNOTATIONS);
}
public Bindable<T> withSuppliedValue(Supplier<T> suppliedValue) {
return new Bindable<>(this.type, this.boxedType, suppliedValue, NO_ANNOTATIONS);
}
/**
* Create a new {@link Bindable} of the type of the specified instance with an
* existing value equal to the instance.
* @param <T> The source type
* @param instance the instance (must not be {@code null})
* @return a {@link Bindable} instance
* @see #of(ResolvableType)
* @see #withExistingValue(Object)
*/
@SuppressWarnings("unchecked")
public static <T> Bindable<T> ofInstance(T instance) {
Assert.notNull(instance, "Instance must not be null");
Class<T> type = (Class<T>) instance.getClass();
return of(type).withExistingValue(instance);
}
/**
* Create a new {@link Bindable} of the specified type.
* @param <T> The source type
* @param type the type (must not be {@code null})
* @return a {@link Bindable} instance
* @see #of(ResolvableType)
*/
public static <T> Bindable<T> of(Class<T> type) {
Assert.notNull(type, "Type must not be null");
return of(ResolvableType.forClass(type));
}
/**
* Create a new {@link Bindable} {@link List} of the specified element type.
* @param <E> the element type
* @param elementType the list element type
* @return a {@link Bindable} instance
*/
public static <E> Bindable<List<E>> listOf(Class<E> elementType) {
return of(ResolvableType.forClassWithGenerics(List.class, elementType));
}
/**
* Create a new {@link Bindable} {@link Set} of the specified element type.
* @param <E> the element type
* @param elementType the set element type
* @return a {@link Bindable} instance
*/
public static <E> Bindable<Set<E>> setOf(Class<E> elementType) {
return of(ResolvableType.forClassWithGenerics(Set.class, elementType));
}
/**
* Create a new {@link Bindable} {@link Map} of the specified kay and value type.
* @param <K> the key type
* @param <V> the value type
* @param keyType the map key type
* @param valueType the map value type
* @return a {@link Bindable} instance
*/
public static <K, V> Bindable<Map<K, V>> mapOf(Class<K> keyType, Class<V> valueType) {
return of(ResolvableType.forClassWithGenerics(Map.class, keyType, valueType));
}
/**
* Create a new {@link Bindable} of the specified type.
* @param <T> The source type
* @param type the type (must not be {@code null})
* @return a {@link Bindable} instance
* @see #of(Class)
*/
public static <T> Bindable<T> of(ResolvableType type) {
Assert.notNull(type, "Type must not be null");
ResolvableType boxedType = box(type);
return new Bindable<>(type, boxedType, null, NO_ANNOTATIONS);
}
private static ResolvableType box(ResolvableType type) {
Class<?> resolved = type.resolve();
if (resolved != null && resolved.isPrimitive()) {
Object array = Array.newInstance(resolved, 1);
Class<?> wrapperType = Array.get(array, 0).getClass();
return ResolvableType.forClass(wrapperType);
}
if (resolved.isArray()) {
return ResolvableType.forArrayComponent(box(type.getComponentType()));
}
return type;
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.Collection;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.CollectionFactory;
import org.springframework.core.ResolvableType;
/**
* {@link AggregateBinder} for collections.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class CollectionBinder extends IndexedElementsBinder<Collection<Object>> {
CollectionBinder(BindContext context) {
super(context);
}
@Override
protected Object bind(ConfigurationPropertyName name, Bindable<?> target,
AggregateElementBinder elementBinder, Class<?> type) {
IndexedCollectionSupplier collection = new IndexedCollectionSupplier(
() -> CollectionFactory.createCollection(type, 0));
ResolvableType elementType = target.getType().asCollection().getGeneric();
bindIndexed(name, target, elementBinder, collection, target.getType(),
elementType);
if (collection.wasSupplied()) {
return collection.get();
}
return null;
}
@Override
protected Collection<Object> merge(Collection<Object> existing,
Collection<Object> additional) {
existing.clear();
existing.addAll(additional);
return existing;
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.Collection;
import java.util.List;
import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.core.ResolvableType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Base class for {@link AggregateBinder AggregateBinders} that read a sequential run of
* indexed items.
*
* @param <T> the type being bound
* @author Phillip Webb
* @author Madhura Bhave
*/
abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
IndexedElementsBinder(BindContext context) {
super(context);
}
protected final void bindIndexed(ConfigurationPropertyName name, Bindable<?> target,
AggregateElementBinder elementBinder, IndexedCollectionSupplier collection,
ResolvableType aggregateType, ResolvableType elementType) {
for (ConfigurationPropertySource source : getContext().getSources()) {
bindIndexed(source, name, elementBinder, collection, aggregateType,
elementType);
if (collection.wasSupplied() && collection.get() != null) {
return;
}
}
}
private void bindIndexed(ConfigurationPropertySource source,
ConfigurationPropertyName root, AggregateElementBinder elementBinder,
IndexedCollectionSupplier collection, ResolvableType aggregateType,
ResolvableType elementType) {
ConfigurationProperty property = source.getConfigurationProperty(root);
if (property != null) {
Object aggregate = convert(property.getValue(), aggregateType);
ResolvableType collectionType = ResolvableType
.forClassWithGenerics(collection.get().getClass(), elementType);
Collection<Object> elements = convert(aggregate, collectionType);
collection.get().addAll(elements);
}
else {
bindIndexed(source, root, elementBinder, collection, elementType);
}
}
private void bindIndexed(ConfigurationPropertySource source,
ConfigurationPropertyName root, AggregateElementBinder elementBinder,
IndexedCollectionSupplier collection, ResolvableType elementType) {
MultiValueMap<String, ConfigurationProperty> knownIndexedChildren = getKnownIndexedChildren(
source, root);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
ConfigurationPropertyName name = root.appendIndex(i);
Object value = elementBinder.bind(name, Bindable.of(elementType), source);
if (value == null) {
break;
}
knownIndexedChildren.remove(name.getElement().getValue(Form.UNIFORM));
collection.get().add(value);
}
assertNoUnboundChildren(knownIndexedChildren);
}
private MultiValueMap<String, ConfigurationProperty> getKnownIndexedChildren(
ConfigurationPropertySource source, ConfigurationPropertyName root) {
MultiValueMap<String, ConfigurationProperty> children = new LinkedMultiValueMap<>();
for (ConfigurationPropertyName name : source.filter(root::isAncestorOf)) {
name = rollUp(name, root);
if (name.getElement().isIndexed()) {
String key = name.getElement().getValue(Form.UNIFORM);
ConfigurationProperty value = source.getConfigurationProperty(name);
children.add(key, value);
}
}
return children;
}
private void assertNoUnboundChildren(
MultiValueMap<String, ConfigurationProperty> children) {
if (!children.isEmpty()) {
throw new UnboundConfigurationPropertiesException(
children.values().stream().flatMap(List::stream)
.collect(Collectors.toCollection(TreeSet::new)));
}
}
@SuppressWarnings("unchecked")
private <C> C convert(Object value, ResolvableType type) {
value = getContext().getPlaceholdersResolver().resolvePlaceholders(value);
BinderConversionService conversionService = getContext().getConversionService();
return (C) conversionService.convert(value, type);
}
/**
* {@link AggregateBinder.AggregateSupplier AggregateSupplier} for an index
* collection.
*/
protected static class IndexedCollectionSupplier
extends AggregateSupplier<Collection<Object>> {
public IndexedCollectionSupplier(Supplier<Collection<Object>> supplier) {
super(supplier);
}
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
/**
* {@link BeanBinder} for mutable Java Beans.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class JavaBeanBinder implements BeanBinder {
@Override
public <T> T bind(Bindable<T> target, boolean hasKnownBindableProperties,
BeanPropertyBinder propertyBinder) {
Bean<T> bean = Bean.get(target, hasKnownBindableProperties);
if (bean == null) {
return null;
}
BeanSupplier<T> beanSupplier = bean.getSupplier(target);
boolean bound = bind(target, propertyBinder, bean, beanSupplier);
return (bound ? beanSupplier.get() : null);
}
private <T> boolean bind(Bindable<T> target, BeanPropertyBinder propertyBinder,
Bean<T> bean, BeanSupplier<T> beanSupplier) {
boolean bound = false;
for (Map.Entry<String, BeanProperty> entry : bean.getProperties().entrySet()) {
bound |= bind(beanSupplier, propertyBinder, entry.getValue());
}
return bound;
}
private <T> boolean bind(BeanSupplier<T> beanSupplier,
BeanPropertyBinder propertyBinder, BeanProperty property) {
String propertyName = property.getName();
ResolvableType type = property.getType();
Supplier<Object> value = property.getValue(beanSupplier);
Annotation[] annotations = property.getAnnotations();
Object bound = propertyBinder.bindProperty(propertyName,
Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
if (bound == null) {
return false;
}
if (property.isSettable()) {
property.setValue(beanSupplier, bound);
}
else if (value == null || !bound.equals(value.get())) {
throw new IllegalStateException(
"No setter found for property: " + property.getName());
}
return true;
}
/**
* The bean being bound.
*/
private static class Bean<T> {
private static Bean<?> cached;
private final Class<?> type;
private final Map<String, BeanProperty> properties = new LinkedHashMap<>();
Bean(Class<?> type) {
this.type = type;
putProperties(type);
}
private void putProperties(Class<?> type) {
while (type != null && !Object.class.equals(type)) {
for (Method method : type.getDeclaredMethods()) {
if (isCandidate(method)) {
addMethod(method);
}
}
for (Field field : type.getDeclaredFields()) {
addField(field);
}
type = type.getSuperclass();
}
}
private boolean isCandidate(Method method) {
return Modifier.isPublic(method.getModifiers())
&& !Object.class.equals(method.getDeclaringClass())
&& !Class.class.equals(method.getDeclaringClass());
}
private void addMethod(Method method) {
String name = method.getName();
int parameterCount = method.getParameterCount();
if (name.startsWith("get") && parameterCount == 0) {
name = Introspector.decapitalize(name.substring(3));
this.properties.computeIfAbsent(name, BeanProperty::new)
.addGetter(method);
}
else if (name.startsWith("is") && parameterCount == 0) {
name = Introspector.decapitalize(name.substring(2));
this.properties.computeIfAbsent(name, BeanProperty::new)
.addGetter(method);
}
else if (name.startsWith("set") && parameterCount == 1) {
name = Introspector.decapitalize(name.substring(3));
this.properties.computeIfAbsent(name, BeanProperty::new)
.addSetter(method);
}
}
private void addField(Field field) {
BeanProperty property = this.properties.get(field.getName());
if (property != null) {
property.addField(field);
}
}
public Class<?> getType() {
return this.type;
}
public Map<String, BeanProperty> getProperties() {
return this.properties;
}
@SuppressWarnings("unchecked")
public BeanSupplier<T> getSupplier(Bindable<T> target) {
return new BeanSupplier<T>(() -> {
T instance = null;
if (target.getValue() != null) {
instance = target.getValue().get();
}
if (instance == null) {
instance = (T) BeanUtils.instantiateClass(this.type);
}
return instance;
});
}
@SuppressWarnings("unchecked")
public static <T> Bean<T> get(Bindable<T> bindable,
boolean useExistingValueForType) {
Class<?> type = bindable.getType().resolve();
Supplier<T> value = bindable.getValue();
if (value == null && !isInstantiatable(type)) {
return null;
}
if (useExistingValueForType && value != null) {
T instance = value.get();
type = (instance != null ? instance.getClass() : type);
}
Bean<?> bean = Bean.cached;
if (bean == null || !type.equals(bean.getType())) {
bean = new Bean<>(type);
cached = bean;
}
return (Bean<T>) bean;
}
private static boolean isInstantiatable(Class<?> type) {
if (type.isInterface()) {
return false;
}
try {
type.getDeclaredConstructor();
return true;
}
catch (Exception ex) {
return false;
}
}
}
private static class BeanSupplier<T> implements Supplier<T> {
private final Supplier<T> factory;
private T instance;
BeanSupplier(Supplier<T> factory) {
this.factory = factory;
}
@Override
public T get() {
if (this.instance == null) {
this.instance = this.factory.get();
}
return this.instance;
}
}
/**
* A bean property being bound.
*/
private static class BeanProperty {
private final String name;
private Method getter;
private Method setter;
private Field field;
BeanProperty(String name) {
this.name = BeanPropertyName.toDashedForm(name);
}
public void addGetter(Method getter) {
if (this.getter == null) {
this.getter = getter;
}
}
public void addSetter(Method setter) {
if (this.setter == null) {
this.setter = setter;
}
}
public void addField(Field field) {
if (this.field == null) {
this.field = field;
}
}
public String getName() {
return this.name;
}
public ResolvableType getType() {
if (this.setter != null) {
return ResolvableType.forMethodParameter(this.setter, 0);
}
return ResolvableType.forMethodReturnType(this.getter);
}
public Annotation[] getAnnotations() {
try {
return (this.field == null ? null : this.field.getDeclaredAnnotations());
}
catch (Exception ex) {
return null;
}
}
public Supplier<Object> getValue(Supplier<?> instance) {
if (this.getter == null) {
return null;
}
return () -> {
try {
this.getter.setAccessible(true);
return this.getter.invoke(instance.get());
}
catch (Exception ex) {
throw new IllegalStateException(
"Unable to get value for property " + this.name, ex);
}
};
}
public boolean isSettable() {
return this.setter != null;
}
public void setValue(Supplier<?> instance, Object value) {
try {
this.setter.setAccessible(true);
this.setter.invoke(instance.get(), value);
}
catch (Exception ex) {
throw new IllegalStateException(
"Unable to set value for property " + this.name, ex);
}
}
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.core.CollectionFactory;
import org.springframework.core.ResolvableType;
/**
* {@link AggregateBinder} for Maps.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class MapBinder extends AggregateBinder<Map<Object, Object>> {
MapBinder(BindContext context) {
super(context);
}
@Override
protected Object bind(ConfigurationPropertyName name, Bindable<?> target,
AggregateElementBinder elementBinder, Class<?> type) {
Map<Object, Object> map = CollectionFactory.createMap(type, 0);
for (ConfigurationPropertySource source : getContext().getSources()) {
if (!ConfigurationPropertyName.EMPTY.equals(name)) {
source = source.filter(name::isAncestorOf);
}
new EntryBinder(name, target, elementBinder).bindEntries(source, map);
}
return (map.isEmpty() ? null : map);
}
@Override
protected Map<Object, Object> merge(Map<Object, Object> existing,
Map<Object, Object> additional) {
existing.putAll(additional);
return existing;
}
private class EntryBinder {
private final ConfigurationPropertyName root;
private final AggregateElementBinder elementBinder;
private final ResolvableType mapType;
private final ResolvableType keyType;
private final ResolvableType valueType;
EntryBinder(ConfigurationPropertyName root, Bindable<?> target,
AggregateElementBinder elementBinder) {
this.root = root;
this.elementBinder = elementBinder;
this.mapType = target.getType().asMap();
this.keyType = this.mapType.getGeneric(0);
this.valueType = this.mapType.getGeneric(1);
}
public void bindEntries(ConfigurationPropertySource source,
Map<Object, Object> map) {
for (ConfigurationPropertyName name : source) {
Bindable<?> valueBindable = getValueBindable(source, name);
ConfigurationPropertyName entryName = getEntryName(source, name);
Object key = getContext().getConversionService()
.convert(getKeyName(entryName), this.keyType);
Object value = this.elementBinder.bind(entryName, valueBindable);
map.putIfAbsent(key, value);
}
}
private Bindable<?> getValueBindable(ConfigurationPropertySource source,
ConfigurationPropertyName name) {
if (isMultiElementName(name) && isValueTreatedAsNestedMap()) {
return Bindable.of(this.mapType);
}
return Bindable.of(this.valueType);
}
private ConfigurationPropertyName getEntryName(ConfigurationPropertySource source,
ConfigurationPropertyName name) {
if (isMultiElementName(name)
&& (isValueTreatedAsNestedMap() || !isScalarValue(source, name))) {
return rollUp(name, this.root);
}
return name;
}
private boolean isMultiElementName(ConfigurationPropertyName name) {
return name.getParent() != null && !this.root.equals(name.getParent());
}
private boolean isValueTreatedAsNestedMap() {
return Object.class.equals(this.valueType.resolve(Object.class));
}
private boolean isScalarValue(ConfigurationPropertySource source,
ConfigurationPropertyName name) {
if (Map.class.isAssignableFrom(this.valueType.resolve())
|| Collection.class.isAssignableFrom(this.valueType.resolve())
|| this.valueType.isArray()) {
return false;
}
ConfigurationProperty property = source.getConfigurationProperty(name);
if (property == null) {
return false;
}
Object value = property.getValue();
value = getContext().getPlaceholdersResolver().resolvePlaceholders(value);
BinderConversionService conversionService = getContext()
.getConversionService();
return conversionService.canConvert(value, this.valueType);
}
private String getKeyName(ConfigurationPropertyName name) {
return name.stream(this.root).map((e) -> e.getValue(Form.ORIGINAL))
.collect(Collectors.joining("."));
}
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import org.springframework.core.env.PropertyResolver;
/**
* Optional strategy that used by a {@link Binder} to resolve property placeholders.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
* @see PropertySourcesPlaceholdersResolver
*/
@FunctionalInterface
public interface PlaceholdersResolver {
/**
* No-op {@link PropertyResolver}.
*/
PlaceholdersResolver NONE = (value) -> value;
/**
* Called to resolve any place holders in the given value.
* @param value the source value
* @return a value with place holders resolved
*/
Object resolvePlaceholders(Object value);
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.util.Assert;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.SystemPropertyUtils;
/**
* {@link PlaceholdersResolver} to resolve placeholders from {@link PropertySources}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public class PropertySourcesPlaceholdersResolver implements PlaceholdersResolver {
private PropertySources sources;
private PropertyPlaceholderHelper helper;
public PropertySourcesPlaceholdersResolver(Environment environment) {
this(getSources(environment), null);
}
public PropertySourcesPlaceholdersResolver(PropertySources sources) {
this(sources, null);
}
public PropertySourcesPlaceholdersResolver(PropertySources sources,
PropertyPlaceholderHelper helper) {
this.sources = sources;
this.helper = (helper != null ? helper
: new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
SystemPropertyUtils.PLACEHOLDER_SUFFIX,
SystemPropertyUtils.VALUE_SEPARATOR, false));
}
@Override
public Object resolvePlaceholders(Object value) {
if (value != null && value instanceof String) {
return this.helper.replacePlaceholders((String) value,
this::resolvePlaceholder);
}
return value;
}
private String resolvePlaceholder(String placeholder) {
if (this.sources != null) {
for (PropertySource<?> source : this.sources) {
Object value = source.getProperty(placeholder);
if (value != null) {
return String.valueOf(value);
}
}
}
return null;
}
private static PropertySources getSources(Environment environment) {
Assert.notNull(environment, "Environment must not be null");
Assert.isInstanceOf(ConfigurableEnvironment.class, environment,
"Environment must be a ConfigurableEnvironment");
return ((ConfigurableEnvironment) environment).getPropertySources();
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
/**
* {@link BindException} thrown when {@link ConfigurationPropertySource} elements were
* left unbound.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public class UnboundConfigurationPropertiesException extends RuntimeException {
private final Set<ConfigurationProperty> unboundProperties;
public UnboundConfigurationPropertiesException(
Set<ConfigurationProperty> unboundProperties) {
super(buildMessage(unboundProperties));
this.unboundProperties = Collections.unmodifiableSet(unboundProperties);
}
public Set<ConfigurationProperty> getUnboundProperties() {
return this.unboundProperties;
}
private static String buildMessage(Set<ConfigurationProperty> unboundProperties) {
StringBuilder builder = new StringBuilder();
builder.append("The elements [");
String message = unboundProperties.stream().map((p) -> p.getName().toString())
.collect(Collectors.joining(","));
builder.append(message).append("] were left unbound.");
return builder.toString();
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
import java.util.function.Function;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.format.support.DefaultFormattingConversionService;
/**
* Internal {@link ConversionService} used by the {@link Binder}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 2.0.0
*/
public class BinderConversionService implements ConversionService {
private final ConversionService conversionService;
private final ConversionService additionalConversionService;
/**
* Create a new {@link BinderConversionService} instance.
* @param conversionService and option root conversion service
*/
public BinderConversionService(ConversionService conversionService) {
this.conversionService = (conversionService != null ? conversionService
: new DefaultFormattingConversionService());
this.additionalConversionService = createAdditionalConversionService();
}
/**
* Return {@code true} if the given source object can be converted to the
* {@code targetType}.
* @param source the source object
* @param targetType the target type to convert to (required)
* @return {@code true} if a conversion can be performed, {@code false} if not
* @throws IllegalArgumentException if {@code targetType} is {@code null}
*/
public boolean canConvert(Object source, ResolvableType targetType) {
TypeDescriptor sourceType = TypeDescriptor.forObject(source);
return canConvert(sourceType, ResolvableTypeDescriptor.forType(targetType));
}
@Override
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
return (this.conversionService != null
&& this.conversionService.canConvert(sourceType, targetType))
|| this.additionalConversionService.canConvert(sourceType, targetType);
}
@Override
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
return (this.conversionService != null
&& this.conversionService.canConvert(sourceType, targetType))
|| this.additionalConversionService.canConvert(sourceType, targetType);
}
@SuppressWarnings("unchecked")
public <T> T convert(Object value, ResolvableType type) {
TypeDescriptor sourceType = TypeDescriptor.forObject(value);
TypeDescriptor targetType = ResolvableTypeDescriptor.forType(type);
return (T) convert(value, sourceType, targetType);
}
@SuppressWarnings("unchecked")
public <T> T convert(Object value, Bindable<T> bindable) {
TypeDescriptor sourceType = TypeDescriptor.forObject(value);
TypeDescriptor targetType = ResolvableTypeDescriptor.forBindable(bindable);
return (T) convert(value, sourceType, targetType);
}
@Override
public <T> T convert(Object source, Class<T> targetType) {
return callConversionService((c) -> c.convert(source, targetType));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
return callConversionService((c) -> c.convert(source, sourceType, targetType));
}
private <T> T callConversionService(Function<ConversionService, T> call) {
if (this.conversionService == null) {
return callAdditionalConversionService(call, null);
}
try {
return call.apply(this.conversionService);
}
catch (ConversionException ex) {
return callAdditionalConversionService(call, ex);
}
}
private <T> T callAdditionalConversionService(Function<ConversionService, T> call,
RuntimeException cause) {
try {
return call.apply(this.additionalConversionService);
}
catch (ConverterNotFoundException ex) {
throw (cause != null ? cause : ex);
}
}
private static ConversionService createAdditionalConversionService() {
DefaultFormattingConversionService service = new DefaultFormattingConversionService();
DefaultConversionService.addCollectionConverters(service);
service.addConverterFactory(new StringToEnumConverterFactory());
service.addConverter(new StringToCharArrayConverter());
service.addConverter(new StringToInetAddressConverter());
service.addConverter(new InetAddressToStringConverter());
service.addConverter(new PropertyEditorConverter());
DateFormatterRegistrar registrar = new DateFormatterRegistrar();
DateFormatter formatter = new DateFormatter();
formatter.setIso(DateTimeFormat.ISO.DATE_TIME);
registrar.setFormatter(formatter);
registrar.registerFormatters(service);
return service;
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
import java.beans.PropertyEditor;
import java.net.InetAddress;
import org.springframework.core.convert.converter.Converter;
/**
* {@link PropertyEditor} for {@link InetAddress} objects.
*
* @author Dave Syer
* @author Phillip Webb
*/
class InetAddressToStringConverter implements Converter<InetAddress, String> {
@Override
public String convert(InetAddress source) {
return source.getHostAddress();
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
import java.beans.PropertyEditor;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyEditorRegistrySupport;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalConverter;
import org.springframework.core.convert.converter.GenericConverter;
/**
* {@link GenericConverter} that delegates to Java bean {@link PropertyEditor
* PropertyEditors}.
*
* @author Phillip Webb
*/
class PropertyEditorConverter implements GenericConverter, ConditionalConverter {
private static final Set<Class<?>> SKIPPED;
static {
Set<Class<?>> skipped = new LinkedHashSet<>();
skipped.add(Collection.class);
skipped.add(Map.class);
SKIPPED = Collections.unmodifiableSet(skipped);
}
/**
* Registry that can be used to check if conversion is supported. Since
* {@link PropertyEditor PropertyEditors} are not thread safe this can't be used for
* actual conversion.
*/
private final PropertyEditorRegistrySupport registry = new SimpleTypeConverter();
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return null;
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
Class<?> type = targetType.getType();
if (isSkipped(type)) {
return false;
}
PropertyEditor editor = this.registry.getDefaultEditor(type);
editor = (editor != null ? editor : BeanUtils.findEditorByConvention(type));
return editor != null;
}
private boolean isSkipped(Class<?> type) {
return SKIPPED.stream().anyMatch((c) -> c.isAssignableFrom(type));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
return new SimpleTypeConverter().convertIfNecessary(source, targetType.getType());
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
import java.lang.annotation.Annotation;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.TypeDescriptor;
/**
* A {@link TypeDescriptor} backed by a {@link ResolvableType}.
*
* @author Phillip Webb
*/
@SuppressWarnings("serial")
final class ResolvableTypeDescriptor extends TypeDescriptor {
private ResolvableTypeDescriptor(ResolvableType resolvableType,
Annotation[] annotations) {
super(resolvableType, null, annotations);
}
/**
* Create a {@link TypeDescriptor} for the specified {@link Bindable}.
* @param bindable the bindable
* @return the type descriptor
*/
public static TypeDescriptor forBindable(Bindable<?> bindable) {
return forType(bindable.getType(), bindable.getAnnotations());
}
/**
* Return a {@link TypeDescriptor} for the specified {@link ResolvableType}.
* @param type the resolvable type
* @param annotations the annotations to include
* @return the type descriptor
*/
public static TypeDescriptor forType(ResolvableType type, Annotation... annotations) {
return new ResolvableTypeDescriptor(type, annotations);
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
import org.springframework.core.convert.converter.Converter;
/**
* Converts a String to a Char Array.
*
* @author Phillip Webb
*/
class StringToCharArrayConverter implements Converter<String, char[]> {
@Override
public char[] convert(String source) {
return source.toCharArray();
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
import java.util.EnumSet;
import java.util.Set;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.util.Assert;
/**
* Converts from a String to a {@link java.lang.Enum} by calling searching matching enum
* names (ignoring case).
*
* @author Phillip Webb
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
Assert.notNull(enumType,
"The target type " + targetType.getName() + " does not refer to an enum");
return new StringToEnum(enumType);
}
private class StringToEnum<T extends Enum> implements Converter<String, T> {
private final Class<T> enumType;
StringToEnum(Class<T> enumType) {
this.enumType = enumType;
}
@Override
public T convert(String source) {
if (source.isEmpty()) {
return null;
}
source = source.trim();
try {
return (T) Enum.valueOf(this.enumType, source);
}
catch (Exception ex) {
return findEnum(source);
}
}
private T findEnum(String source) {
String name = getLettersAndDigits(source);
for (T candidate : (Set<T>) EnumSet.allOf(this.enumType)) {
if (getLettersAndDigits(candidate.name()).equals(name)) {
return candidate;
}
}
throw new IllegalArgumentException("No enum constant "
+ this.enumType.getCanonicalName() + "." + source);
}
private String getLettersAndDigits(String name) {
StringBuilder canonicalName = new StringBuilder(name.length());
name.chars().map((c) -> (char) c).filter(Character::isLetterOrDigit)
.map(Character::toLowerCase).forEach(canonicalName::append);
return canonicalName.toString();
}
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
import java.beans.PropertyEditor;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.springframework.core.convert.converter.Converter;
/**
* {@link PropertyEditor} for {@link InetAddress} objects.
*
* @author Dave Syer
* @author Phillip Webb
*/
class StringToInetAddressConverter implements Converter<String, InetAddress> {
@Override
public InetAddress convert(String source) {
try {
return InetAddress.getByName(source);
}
catch (UnknownHostException ex) {
throw new IllegalStateException("Unknown host " + source, ex);
}
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Conversion support for configuration properties binding.
*/
package org.springframework.boot.context.properties.bind.convert;
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.handler;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
/**
* {@link BindHandler} that can be used to ignore binding errors.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public class IgnoreErrorsBindHandler extends AbstractBindHandler {
public IgnoreErrorsBindHandler() {
super();
}
public IgnoreErrorsBindHandler(BindHandler parent) {
super(parent);
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception {
return (target.getValue() == null ? null : target.getValue().get());
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.handler;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
/**
* {@link BindHandler} to limit binding to only first level properties.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public class IgnoreNestedPropertiesBindHandler extends AbstractBindHandler {
public IgnoreNestedPropertiesBindHandler() {
super();
}
public IgnoreNestedPropertiesBindHandler(BindHandler parent) {
super(parent);
}
@Override
public boolean onStart(ConfigurationPropertyName name, Bindable<?> target,
BindContext context) {
if (context.getDepth() > 1) {
return false;
}
return super.onStart(name, target, context);
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.handler;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.UnboundConfigurationPropertiesException;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
/**
* {@link BindHandler} to enforce that all configuration properties under the root name
* have been bound.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public class NoUnboundElementsBindHandler extends AbstractBindHandler {
private final Set<ConfigurationPropertyName> boundNames = new HashSet<>();
public NoUnboundElementsBindHandler() {
super();
}
public NoUnboundElementsBindHandler(BindHandler parent) {
super(parent);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
this.boundNames.add(name);
return super.onSuccess(name, target, context, result);
}
@Override
public void onFinish(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
if (context.getDepth() == 0) {
checkNoUnboundElements(name, context);
}
}
private void checkNoUnboundElements(ConfigurationPropertyName name,
BindContext context) {
Set<ConfigurationProperty> unbound = new TreeSet<>();
for (ConfigurationPropertySource source : context.getSources()) {
ConfigurationPropertySource filtered = source
.filter((candidate) -> isUnbound(name, candidate));
for (ConfigurationPropertyName unboundName : filtered) {
try {
unbound.add(filtered.getConfigurationProperty(unboundName));
}
catch (Exception ex) {
}
}
}
if (!unbound.isEmpty()) {
throw new UnboundConfigurationPropertiesException(unbound);
}
}
private boolean isUnbound(ConfigurationPropertyName name,
ConfigurationPropertyName candidate) {
return name.isAncestorOf(candidate) && !this.boundNames.contains(candidate);
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* General {@link org.springframework.boot.context.properties.bind.BindHandler
* BindHandler} implementations.
*/
package org.springframework.boot.context.properties.bind.handler;
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support for {@code @ConfigurationProperties} binding.
*/
package org.springframework.boot.context.properties.bind;
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.validation;
import org.springframework.util.Assert;
/**
* Error thrown when validation fails during a bind operation.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
* @see ValidationErrors
* @see ValidationBindHandler
*/
public class BindValidationException extends RuntimeException {
private final ValidationErrors validationErrors;
BindValidationException(ValidationErrors validationErrors) {
Assert.notNull(validationErrors, "ValidationErrors must not be null");
this.validationErrors = validationErrors;
}
/**
* Return the validation errors that caused the exception.
* @return the validationErrors the validation errors
*/
public ValidationErrors getValidationErrors() {
return this.validationErrors;
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.validation;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginProvider;
import org.springframework.validation.FieldError;
/**
* {@link FieldError} implementation that tracks the source {@link Origin}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
final class OriginTrackedFieldError extends FieldError implements OriginProvider {
private final Origin origin;
private OriginTrackedFieldError(FieldError fieldError, Origin origin) {
super(fieldError.getObjectName(), fieldError.getField(),
fieldError.getRejectedValue(), fieldError.isBindingFailure(),
fieldError.getCodes(), fieldError.getArguments(),
fieldError.getDefaultMessage());
this.origin = origin;
}
@Override
public Origin getOrigin() {
return this.origin;
}
@Override
public String toString() {
if (this.origin == null) {
return toString();
}
return super.toString() + "; origin " + this.origin;
}
public static FieldError of(FieldError fieldError, Origin origin) {
if (fieldError == null || origin == null) {
return fieldError;
}
return new OriginTrackedFieldError(fieldError, origin);
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.validation;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
/**
* {@link BindHandler} to apply {@link Validator Validators} to bound results.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public class ValidationBindHandler extends AbstractBindHandler {
private final Validator[] validators;
private boolean validate;
private Set<ConfigurationProperty> boundProperties = new LinkedHashSet<>();
public ValidationBindHandler(Validator... validators) {
super();
this.validators = validators;
}
public ValidationBindHandler(BindHandler parent, Validator... validators) {
super(parent);
this.validators = validators;
}
@Override
public boolean onStart(ConfigurationPropertyName name, Bindable<?> target,
BindContext context) {
if (context.getDepth() == 0) {
this.validate = shouldValidate(target);
}
return super.onStart(name, target, context);
}
private boolean shouldValidate(Bindable<?> target) {
Validated annotation = AnnotationUtils
.findAnnotation(target.getBoxedType().resolve(), Validated.class);
return (annotation != null);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
if (context.getConfigurationProperty() != null) {
this.boundProperties.add(context.getConfigurationProperty());
}
return super.onSuccess(name, target, context, result);
}
@Override
public void onFinish(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
if (this.validate) {
validate(name, target, result);
}
super.onFinish(name, target, context, result);
}
private void validate(ConfigurationPropertyName name, Bindable<?> target,
Object result) {
Object validationTarget = getValidationTarget(target, result);
Class<?> validationType = target.getBoxedType().resolve();
validate(name, validationTarget, validationType);
}
private Object getValidationTarget(Bindable<?> target, Object result) {
if (result != null) {
return result;
}
if (target.getValue() != null) {
return target.getValue().get();
}
return null;
}
private void validate(ConfigurationPropertyName name, Object target, Class<?> type) {
if (target != null) {
BindingResult errors = new BeanPropertyBindingResult(target, name.toString());
Arrays.stream(this.validators).filter((v) -> v.supports(type))
.forEach((v) -> v.validate(target, errors));
if (errors.hasErrors()) {
throwBindValidationException(name, errors);
}
}
}
private void throwBindValidationException(ConfigurationPropertyName name,
BindingResult errors) {
Set<ConfigurationProperty> boundProperties = this.boundProperties.stream()
.filter((property) -> name.isAncestorOf(property.getName()))
.collect(Collectors.toCollection(LinkedHashSet::new));
ValidationErrors validationErrors = new ValidationErrors(name, boundProperties,
errors.getAllErrors());
throw new BindValidationException(validationErrors);
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.validation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.assertj.core.util.Objects;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
import org.springframework.boot.origin.Origin;
import org.springframework.util.Assert;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
/**
* A collection of {@link ObjectError ObjectErrors} caused by bind validation failures.
* Where possible, included {@link FieldError FieldErrors} will be OriginProvider.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public class ValidationErrors implements Iterable<ObjectError> {
private final ConfigurationPropertyName name;
private final Set<ConfigurationProperty> boundProperties;
private final List<ObjectError> errors;
ValidationErrors(ConfigurationPropertyName name,
Set<ConfigurationProperty> boundProperties, List<ObjectError> errors) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(boundProperties, "BoundProperties must not be null");
Assert.notNull(errors, "Errors must not be null");
this.name = name;
this.boundProperties = Collections.unmodifiableSet(boundProperties);
this.errors = convertErrors(name, boundProperties, errors);
}
private List<ObjectError> convertErrors(ConfigurationPropertyName name,
Set<ConfigurationProperty> boundProperties, List<ObjectError> errors) {
List<ObjectError> converted = new ArrayList<>(errors.size());
for (ObjectError error : errors) {
converted.add(convertError(name, boundProperties, error));
}
return Collections.unmodifiableList(converted);
}
private ObjectError convertError(ConfigurationPropertyName name,
Set<ConfigurationProperty> boundProperties, ObjectError error) {
if (error instanceof FieldError) {
return convertFieldError(name, boundProperties, (FieldError) error);
}
return error;
}
private FieldError convertFieldError(ConfigurationPropertyName name,
Set<ConfigurationProperty> boundProperties, FieldError error) {
if (error instanceof ObjectProvider<?>) {
return error;
}
return OriginTrackedFieldError.of(error,
findFieldErrorOrigin(name, boundProperties, error));
}
private Origin findFieldErrorOrigin(ConfigurationPropertyName name,
Set<ConfigurationProperty> boundProperties, FieldError error) {
for (ConfigurationProperty boundProperty : boundProperties) {
if (isForError(name, boundProperty.getName(), error)) {
return Origin.from(boundProperty);
}
}
return null;
}
private boolean isForError(ConfigurationPropertyName name,
ConfigurationPropertyName boundPropertyName, FieldError error) {
return Objects.areEqual(boundPropertyName.getParent(), name) && boundPropertyName
.getElement().getValue(Form.UNIFORM).equalsIgnoreCase(error.getField());
}
/**
* Return the name of the item that was being validated.
* @return the name of the item
*/
public ConfigurationPropertyName getName() {
return this.name;
}
/**
* Return the properties that were bound before validation failed.
* @return the boundProperties
*/
public Set<ConfigurationProperty> getBoundProperties() {
return this.boundProperties;
}
public boolean hasErrors() {
return !this.errors.isEmpty();
}
/**
* Return the list of all validation errors.
* @return the errors
*/
public List<ObjectError> getAllErrors() {
return this.errors;
}
@Override
public Iterator<ObjectError> iterator() {
return this.errors.iterator();
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Binding validation support.
*/
package org.springframework.boot.context.properties.bind.validation;
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link BeanPropertyName}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class BeanPropertyNameTests {
@Test
public void toDashedCaseShouldConvertValue() {
assertThat(BeanPropertyName.toDashedForm("Foo")).isEqualTo("foo");
assertThat(BeanPropertyName.toDashedForm("foo")).isEqualTo("foo");
assertThat(BeanPropertyName.toDashedForm("fooBar")).isEqualTo("foo-bar");
assertThat(BeanPropertyName.toDashedForm("foo_bar")).isEqualTo("foo-bar");
assertThat(BeanPropertyName.toDashedForm("_foo_bar")).isEqualTo("-foo-bar");
assertThat(BeanPropertyName.toDashedForm("foo_Bar")).isEqualTo("foo-bar");
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Tests for {@link BindResult}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class BindResultTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Mock
private Consumer<String> consumer;
@Mock
private Function<String, String> mapper;
@Mock
private Supplier<String> supplier;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void getWhenHasValueShouldReturnValue() throws Exception {
BindResult<String> result = BindResult.of("foo");
assertThat(result.get()).isEqualTo("foo");
}
@Test
public void getWhenHasNoValueShouldThrowException() throws Exception {
BindResult<String> result = BindResult.of(null);
this.thrown.expect(NoSuchElementException.class);
this.thrown.expectMessage("No value bound");
result.get();
}
@Test
public void isBoundWhenHasValueShouldReturnTrue() throws Exception {
BindResult<String> result = BindResult.of("foo");
assertThat(result.isBound()).isTrue();
}
@Test
public void isBoundWhenHasNoValueShouldFalse() throws Exception {
BindResult<String> result = BindResult.of(null);
assertThat(result.isBound()).isFalse();
}
@Test
public void ifBoundWhenConsumerIsNullShouldThrowException() throws Exception {
BindResult<String> result = BindResult.of("foo");
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Consumer must not be null");
result.ifBound(null);
}
@Test
public void ifBoundWhenHasValueShouldCallConsumer() throws Exception {
BindResult<String> result = BindResult.of("foo");
result.ifBound(this.consumer);
verify(this.consumer).accept("foo");
}
@Test
public void ifBoundWhenHasNoValueShouldNotCallConsumer() throws Exception {
BindResult<String> result = BindResult.of(null);
result.ifBound(this.consumer);
verifyZeroInteractions(this.consumer);
}
@Test
public void mapWhenMapperIsNullShouldThrowException() throws Exception {
BindResult<String> result = BindResult.of("foo");
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Mapper must not be null");
result.map(null);
}
@Test
public void mapWhenHasValueShouldCallMapper() throws Exception {
BindResult<String> result = BindResult.of("foo");
given(this.mapper.apply("foo")).willReturn("bar");
assertThat(result.map(this.mapper).get()).isEqualTo("bar");
}
@Test
public void mapWhenHasNoValueShouldNotCallMapper() throws Exception {
BindResult<String> result = BindResult.of(null);
result.map(this.mapper);
verifyZeroInteractions(this.mapper);
}
@Test
public void orElseWhenHasValueShouldReturnValue() throws Exception {
BindResult<String> result = BindResult.of("foo");
assertThat(result.orElse("bar")).isEqualTo("foo");
}
@Test
public void orElseWhenHasValueNoShouldReturnOther() throws Exception {
BindResult<String> result = BindResult.of(null);
assertThat(result.orElse("bar")).isEqualTo("bar");
}
@Test
public void orElseGetWhenHasValueShouldReturnValue() throws Exception {
BindResult<String> result = BindResult.of("foo");
assertThat(result.orElseGet(this.supplier)).isEqualTo("foo");
verifyZeroInteractions(this.supplier);
}
@Test
public void orElseGetWhenHasValueNoShouldReturnOther() throws Exception {
BindResult<String> result = BindResult.of(null);
given(this.supplier.get()).willReturn("bar");
assertThat(result.orElseGet(this.supplier)).isEqualTo("bar");
}
@Test
public void orElseCreateWhenTypeIsNullShouldThrowException() throws Exception {
BindResult<String> result = BindResult.of("foo");
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Type must not be null");
result.orElseCreate(null);
}
@Test
public void orElseCreateWhenHasValueShouldReturnValue() throws Exception {
BindResult<ExampleBean> result = BindResult.of(new ExampleBean("foo"));
assertThat(result.orElseCreate(ExampleBean.class).getValue()).isEqualTo("foo");
}
@Test
public void orElseCreateWhenHasValueNoShouldReturnCreatedValue() throws Exception {
BindResult<ExampleBean> result = BindResult.of(null);
assertThat(result.orElseCreate(ExampleBean.class).getValue()).isEqualTo("new");
}
@Test
public void orElseThrowWhenHasValueShouldReturnValue() throws Exception {
BindResult<String> result = BindResult.of("foo");
assertThat(result.orElseThrow(IOException::new)).isEqualTo("foo");
}
@Test
public void orElseThrowWhenHasNoValueShouldThrowException() throws Exception {
BindResult<String> result = BindResult.of(null);
this.thrown.expect(IOException.class);
result.orElseThrow(IOException::new);
}
@Test
public void hashCodeAndEquals() throws Exception {
BindResult<?> result1 = BindResult.of("foo");
BindResult<?> result2 = BindResult.of("foo");
BindResult<?> result3 = BindResult.of("bar");
BindResult<?> result4 = BindResult.of(null);
assertThat(result1.hashCode()).isEqualTo(result2.hashCode());
assertThat(result1).isEqualTo(result1).isEqualTo(result2).isNotEqualTo(result3)
.isNotEqualTo(result4);
}
@Test
public void ofWhenHasValueShouldReturnBoundResultOfValue() throws Exception {
BindResult<Object> result = BindResult.of("foo");
assertThat(result.isBound()).isTrue();
assertThat(result.get()).isEqualTo("foo");
}
@Test
public void ofWhenValueIsNullShouldReturnUnbound() throws Exception {
BindResult<Object> result = BindResult.of(null);
assertThat(result.isBound()).isFalse();
assertThat(result).isSameAs(BindResult.of(null));
}
static class ExampleBean {
private final String value;
ExampleBean() {
this.value = "new";
}
ExampleBean(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link Bindable}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class BindableTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void ofClassWhenTypeIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Type must not be null");
Bindable.of((Class<?>) null);
}
@Test
public void ofTypeWhenTypeIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Type must not be null");
Bindable.of((ResolvableType) null);
}
@Test
public void ofClassShouldSetType() throws Exception {
assertThat(Bindable.of(String.class).getType())
.isEqualTo(ResolvableType.forClass(String.class));
}
@Test
public void ofTypeShouldSetType() throws Exception {
ResolvableType type = ResolvableType.forClass(String.class);
assertThat(Bindable.of(type).getType()).isEqualTo(type);
}
@Test
public void ofInstanceShouldSetTypeAndExistingValue() throws Exception {
String instance = "foo";
ResolvableType type = ResolvableType.forClass(String.class);
assertThat(Bindable.ofInstance(instance).getType()).isEqualTo(type);
assertThat(Bindable.ofInstance(instance).getValue().get()).isEqualTo("foo");
}
@Test
public void ofClassWithExistingValueShouldSetTypeAndExistingValue() throws Exception {
assertThat(Bindable.of(String.class).withExistingValue("foo").getValue().get())
.isEqualTo("foo");
}
@Test
public void ofTypeWithExistingValueShouldSetTypeAndExistingValue() throws Exception {
assertThat(Bindable.of(ResolvableType.forClass(String.class))
.withExistingValue("foo").getValue().get()).isEqualTo("foo");
}
@Test
public void ofTypeWhenExistingValueIsNotInstanceOfTypeShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage(
"ExistingValue must be an instance of " + String.class.getName());
Bindable.of(ResolvableType.forClass(String.class)).withExistingValue(123);
}
@Test
public void ofTypeWhenPrimitiveWithExistingValueWrapperShouldNotThrowException()
throws Exception {
Bindable<Integer> bindable = Bindable
.<Integer>of(ResolvableType.forClass(int.class)).withExistingValue(123);
assertThat(bindable.getType().resolve()).isEqualTo(int.class);
assertThat(bindable.getValue().get()).isEqualTo(123);
}
@Test
public void getBoxedTypeWhenNotBoxedShouldReturnType() throws Exception {
Bindable<String> bindable = Bindable.of(String.class);
assertThat(bindable.getBoxedType())
.isEqualTo(ResolvableType.forClass(String.class));
}
@Test
public void getBoxedTypeWhenPrimativeShouldReturnBoxedType() throws Exception {
Bindable<Integer> bindable = Bindable.of(int.class);
assertThat(bindable.getType()).isEqualTo(ResolvableType.forClass(int.class));
assertThat(bindable.getBoxedType())
.isEqualTo(ResolvableType.forClass(Integer.class));
}
@Test
public void getBoxedTypeWhenPrimativeArrayShouldReturnBoxedType() throws Exception {
Bindable<int[]> bindable = Bindable.of(int[].class);
assertThat(bindable.getType().getComponentType())
.isEqualTo(ResolvableType.forClass(int.class));
assertThat(bindable.getBoxedType().isArray()).isTrue();
assertThat(bindable.getBoxedType().getComponentType())
.isEqualTo(ResolvableType.forClass(Integer.class));
}
@Test
public void getAnnotationsShouldReturnEmptyArray() throws Exception {
assertThat(Bindable.of(String.class).getAnnotations()).isEmpty();
}
@Test
public void withAnnotationsShouldSetAnnotations() throws Exception {
Annotation annotation = mock(Annotation.class);
assertThat(Bindable.of(String.class).withAnnotations(annotation).getAnnotations())
.containsExactly(annotation);
}
@Test
public void toStringShouldShowDetails() throws Exception {
Annotation annotation = AnnotationUtils
.synthesizeAnnotation(TestAnnotation.class);
Bindable<String> bindable = Bindable.of(String.class).withExistingValue("foo")
.withAnnotations(annotation);
System.out.println(bindable.toString());
assertThat(bindable.toString()).contains("type = java.lang.String, "
+ "value = 'provided', annotations = array<Annotation>["
+ "@org.springframework.boot.context.properties.bind."
+ "BindableTests$TestAnnotation()]");
}
@Test
public void equalsAndHashcode() throws Exception {
Annotation annotation = AnnotationUtils
.synthesizeAnnotation(TestAnnotation.class);
Bindable<String> bindable1 = Bindable.of(String.class).withExistingValue("foo")
.withAnnotations(annotation);
Bindable<String> bindable2 = Bindable.of(String.class).withExistingValue("foo")
.withAnnotations(annotation);
Bindable<String> bindable3 = Bindable.of(String.class).withExistingValue("fof")
.withAnnotations(annotation);
assertThat(bindable1.hashCode()).isEqualTo(bindable2.hashCode());
assertThat(bindable1).isEqualTo(bindable1).isEqualTo(bindable2);
assertThat(bindable1).isEqualTo(bindable3);
}
@Retention(RetentionPolicy.RUNTIME)
static @interface TestAnnotation {
}
static class TestNewInstance {
private String foo = "hello world";
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
static class TestNewInstanceWithNoDefaultConstructor {
TestNewInstanceWithNoDefaultConstructor(String foo) {
this.foo = foo;
}
private String foo = "hello world";
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Answers;
import org.mockito.InOrder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.test.context.support.TestPropertySourceUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;
/**
* Tests for {@link Binder}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class BinderTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private List<ConfigurationPropertySource> sources = new ArrayList<>();
private Binder binder;
@Before
public void setup() {
this.binder = new Binder(this.sources);
}
@Test
public void createWhenSourcesIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Sources must not be null");
new Binder((Iterable<ConfigurationPropertySource>) null);
}
@Test
public void bindWhenNameIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Name must not be null");
this.binder.bind((ConfigurationPropertyName) null, Bindable.of(String.class),
BindHandler.DEFAULT);
}
@Test
public void bindWhenTargetIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Target must not be null");
this.binder.bind(ConfigurationPropertyName.of("foo"), null, BindHandler.DEFAULT);
}
@Test
public void bindToValueWhenPropertyIsMissingShouldReturnUnbound() throws Exception {
this.sources.add(new MockConfigurationPropertySource());
BindResult<String> result = this.binder.bind("foo", Bindable.of(String.class));
assertThat(result.isBound()).isFalse();
}
@Test
public void bindToValueShouldReturnPropertyValue() throws Exception {
this.sources.add(new MockConfigurationPropertySource("foo", 123));
BindResult<Integer> result = this.binder.bind("foo", Bindable.of(Integer.class));
assertThat(result.get()).isEqualTo(123);
}
@Test
public void bindToValueShouldReturnPropertyValueFromSecondSource() throws Exception {
this.sources.add(new MockConfigurationPropertySource("foo", 123));
this.sources.add(new MockConfigurationPropertySource("bar", 234));
BindResult<Integer> result = this.binder.bind("bar", Bindable.of(Integer.class));
assertThat(result.get()).isEqualTo(234);
}
@Test
public void bindToValueShouldReturnConvertedPropertyValue() throws Exception {
this.sources.add(new MockConfigurationPropertySource("foo", "123"));
BindResult<Integer> result = this.binder.bind("foo", Bindable.of(Integer.class));
assertThat(result.get()).isEqualTo(123);
}
@Test
public void bindToValueWhenMultipleCandidatesShouldReturnFirst() throws Exception {
this.sources.add(new MockConfigurationPropertySource("foo", 123));
this.sources.add(new MockConfigurationPropertySource("foo", 234));
BindResult<Integer> result = this.binder.bind("foo", Bindable.of(Integer.class));
assertThat(result.get()).isEqualTo(123);
}
@Test
public void bindToValueWithPlaceholdersShouldResolve() throws Exception {
StandardEnvironment environment = new StandardEnvironment();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment, "bar=23");
this.sources.add(new MockConfigurationPropertySource("foo", "1${bar}"));
this.binder = new Binder(this.sources,
new PropertySourcesPlaceholdersResolver(environment));
BindResult<Integer> result = this.binder.bind("foo", Bindable.of(Integer.class));
assertThat(result.get()).isEqualTo(123);
}
@Test
public void bindToValueShouldTriggerOnSuccess() throws Exception {
this.sources.add(new MockConfigurationPropertySource("foo", "1", "line1"));
BindHandler handler = mock(BindHandler.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
Bindable<Integer> target = Bindable.of(Integer.class);
this.binder.bind("foo", target, handler);
InOrder ordered = inOrder(handler);
ordered.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo")),
eq(target), any(), eq(1));
}
@Test
public void bindToJavaBeanShouldReturnPopulatedBean() throws Exception {
this.sources.add(new MockConfigurationPropertySource("foo.value", "bar"));
JavaBean result = this.binder.bind("foo", Bindable.of(JavaBean.class)).get();
assertThat(result.getValue()).isEqualTo("bar");
}
@Test
public void bindToJavaBeanWhenNonIterableShouldReturnPopulatedBean()
throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource(
"foo.value", "bar");
source.setNonIterable(true);
this.sources.add(source);
JavaBean result = this.binder.bind("foo", Bindable.of(JavaBean.class)).get();
assertThat(result.getValue()).isEqualTo("bar");
}
@Test
public void bindToJavaBeanShouldTriggerOnSuccess() throws Exception {
this.sources
.add(new MockConfigurationPropertySource("foo.value", "bar", "line1"));
BindHandler handler = mock(BindHandler.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
Bindable<JavaBean> target = Bindable.of(JavaBean.class);
this.binder.bind("foo", target, handler);
InOrder inOrder = inOrder(handler);
inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo.value")),
eq(Bindable.of(String.class)), any(), eq("bar"));
inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo")),
eq(target), any(), isA(JavaBean.class));
}
@Test
public void bindWhenHasMalformedDateShouldThrowException() throws Exception {
this.thrown.expectCause(instanceOf(ConversionFailedException.class));
this.sources.add(new MockConfigurationPropertySource("foo", "2014-04-01"));
this.binder.bind("foo", Bindable.of(LocalDate.class));
}
@Test
public void bindWhenHasAnnotationsShouldChangeConvertedValue() throws Exception {
this.sources.add(new MockConfigurationPropertySource("foo", "2014-04-01"));
DateTimeFormat annotation = AnnotationUtils.synthesizeAnnotation(
Collections.singletonMap("iso", DateTimeFormat.ISO.DATE),
DateTimeFormat.class, null);
LocalDate result = this.binder
.bind("foo", Bindable.of(LocalDate.class).withAnnotations(annotation))
.get();
assertThat(result.toString()).isEqualTo("2014-04-01");
}
public static class JavaBean {
private String value;
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
}
public enum ExampleEnum {
FOO_BAR, BAR_BAZ, BAZ_BOO
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.test.context.support.TestPropertySourceUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
/**
* Tests for {@link CollectionBinder}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class CollectionBinderTests {
private static final Bindable<List<Integer>> INTEGER_LIST = Bindable
.listOf(Integer.class);
private static final Bindable<List<String>> STRING_LIST = Bindable
.listOf(String.class);
private List<ConfigurationPropertySource> sources = new ArrayList<>();
private Binder binder;
@Before
public void setup() {
this.binder = new Binder(this.sources);
}
@Test
public void bindToCollectionShouldReturnPopulatedCollection() throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo[0]", "1");
source.put("foo[1]", "2");
source.put("foo[2]", "3");
this.sources.add(source);
List<Integer> result = this.binder.bind("foo", INTEGER_LIST).get();
assertThat(result).containsExactly(1, 2, 3);
}
@Test
public void bindToCollectionWhenNestedShouldReturnPopulatedCollection()
throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo[0][0]", "1");
source.put("foo[0][1]", "2");
source.put("foo[1][0]", "3");
source.put("foo[1][1]", "4");
this.sources.add(source);
Bindable<List<List<Integer>>> target = Bindable.of(
ResolvableType.forClassWithGenerics(List.class, INTEGER_LIST.getType()));
List<List<Integer>> result = this.binder.bind("foo", target).get();
assertThat(result).hasSize(2);
assertThat(result.get(0)).containsExactly(1, 2);
assertThat(result.get(1)).containsExactly(3, 4);
}
@Test
public void bindToCollectionWhenNotInOrderShouldReturnPopulatedCollection()
throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo[1]", "2");
source.put("foo[0]", "1");
source.put("foo[2]", "3");
this.sources.add(source);
List<Integer> result = this.binder.bind("foo", INTEGER_LIST).get();
assertThat(result).containsExactly(1, 2, 3);
}
@Test
public void bindToCollectionWhenNonSequentialShouldThrowException() throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo[0]", "2");
source.put("foo[1]", "1");
source.put("foo[3]", "3");
this.sources.add(source);
try {
this.binder.bind("foo", INTEGER_LIST);
fail("No exception thrown");
}
catch (BindException ex) {
ex.printStackTrace();
Set<ConfigurationProperty> unbound = ((UnboundConfigurationPropertiesException) ex
.getCause()).getUnboundProperties();
assertThat(unbound).hasSize(1);
ConfigurationProperty property = unbound.iterator().next();
assertThat(property.getName().toString()).isEqualTo("foo[3]");
assertThat(property.getValue()).isEqualTo("3");
}
}
@Test
public void bindToCollectionWhenNonIterableShouldReturnPopulatedCollection()
throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo[1]", "2");
source.put("foo[0]", "1");
source.put("foo[2]", "3");
source.setNonIterable(true);
this.sources.add(source);
List<Integer> result = this.binder.bind("foo", INTEGER_LIST).get();
assertThat(result).containsExactly(1, 2, 3);
}
@Test
public void bindToCollectionWhenMultipleSourceShouldOnlyUseFirst() throws Exception {
MockConfigurationPropertySource source1 = new MockConfigurationPropertySource();
source1.put("bar", "baz");
this.sources.add(source1);
MockConfigurationPropertySource source2 = new MockConfigurationPropertySource();
source2.put("foo[0]", "1");
source2.put("foo[1]", "2");
this.sources.add(source2);
MockConfigurationPropertySource source3 = new MockConfigurationPropertySource();
source3.put("foo[0]", "7");
source3.put("foo[1]", "8");
source3.put("foo[2]", "9");
this.sources.add(source3);
List<Integer> result = this.binder.bind("foo", INTEGER_LIST).get();
assertThat(result).containsExactly(1, 2);
}
@Test
public void bindToCollectionWhenHasExistingCollectionShouldReplaceAllContents()
throws Exception {
this.sources.add(new MockConfigurationPropertySource("foo[0]", "1"));
List<Integer> existing = new LinkedList<>();
existing.add(1000);
existing.add(1001);
List<Integer> result = this.binder
.bind("foo", INTEGER_LIST.withExistingValue(existing)).get();
assertThat(result).isExactlyInstanceOf(LinkedList.class);
assertThat(result).isSameAs(existing);
assertThat(result).containsExactly(1);
}
@Test
public void bindToCollectionWhenHasExistingCollectionButNoValueShouldReturnUnbound()
throws Exception {
this.sources.add(new MockConfigurationPropertySource("faf[0]", "1"));
List<Integer> existing = new LinkedList<>();
existing.add(1000);
BindResult<List<Integer>> result = this.binder.bind("foo",
INTEGER_LIST.withExistingValue(existing));
assertThat(result.isBound()).isFalse();
}
@Test
public void bindToCollectionShouldRespectCollectionType() throws Exception {
this.sources.add(new MockConfigurationPropertySource("foo[0]", "1"));
ResolvableType type = ResolvableType.forClassWithGenerics(LinkedList.class,
Integer.class);
Object defaultList = this.binder.bind("foo", INTEGER_LIST).get();
Object customList = this.binder.bind("foo", Bindable.of(type)).get();
assertThat(customList).isExactlyInstanceOf(LinkedList.class)
.isNotInstanceOf(defaultList.getClass());
}
@Test
public void bindToCollectionWhenNoValueShouldReturnUnbound() throws Exception {
this.sources.add(new MockConfigurationPropertySource("faf.bar", "1"));
BindResult<List<Integer>> result = this.binder.bind("foo", INTEGER_LIST);
assertThat(result.isBound()).isFalse();
}
@Test
public void bindToCollectionWhenCommaListShouldReturnPopulatedCollection()
throws Exception {
this.sources.add(new MockConfigurationPropertySource("foo", "1,2,3"));
List<Integer> result = this.binder.bind("foo", INTEGER_LIST).get();
assertThat(result).containsExactly(1, 2, 3);
}
@Test
public void bindToCollectionWhenCommaListWithPlaceholdersShouldReturnPopulatedCollection()
throws Exception {
StandardEnvironment environment = new StandardEnvironment();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment,
"bar=1,2,3");
this.binder = new Binder(this.sources,
new PropertySourcesPlaceholdersResolver(environment));
this.sources.add(new MockConfigurationPropertySource("foo", "${bar}"));
List<Integer> result = this.binder.bind("foo", INTEGER_LIST).get();
assertThat(result).containsExactly(1, 2, 3);
}
@Test
public void bindToCollectionWhenCommaListAndIndexedShouldOnlyUseFirst()
throws Exception {
MockConfigurationPropertySource source1 = new MockConfigurationPropertySource();
source1.put("foo", "1,2");
this.sources.add(source1);
MockConfigurationPropertySource source2 = new MockConfigurationPropertySource();
source2.put("foo[0]", "2");
source2.put("foo[1]", "3");
List<Integer> result = this.binder.bind("foo", INTEGER_LIST).get();
assertThat(result).containsExactly(1, 2);
}
@Test
public void bindToCollectionWhenIndexedAndCommaListShouldOnlyUseFirst()
throws Exception {
MockConfigurationPropertySource source1 = new MockConfigurationPropertySource();
source1.put("foo[0]", "1");
source1.put("foo[1]", "2");
this.sources.add(source1);
MockConfigurationPropertySource source2 = new MockConfigurationPropertySource();
source2.put("foo", "2,3");
List<Integer> result = this.binder.bind("foo", INTEGER_LIST).get();
assertThat(result).containsExactly(1, 2);
}
@Test
public void bindToCollectionWhenItemContainsCommasShouldReturnPopulatedCollection()
throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo[0]", "1,2");
source.put("foo[1]", "3");
this.sources.add(source);
List<String> result = this.binder.bind("foo", STRING_LIST).get();
assertThat(result).containsExactly("1,2", "3");
}
@Test
public void bindToCollectionWhenEmptyStringShouldReturnEmptyCollection() throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo", "");
this.sources.add(source);
List<String> result = this.binder.bind("foo", STRING_LIST).get();
assertThat(result).isNotNull().isEmpty();
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySources;
import org.springframework.util.PropertyPlaceholderHelper;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link PropertySourcesPlaceholdersResolver}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class PropertySourcesPlaceholdersResolverTests {
private PropertySourcesPlaceholdersResolver resolver;
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void placeholderResolverIfEnvironmentNullShouldThrowException()
throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Environment must not be null");
new PropertySourcesPlaceholdersResolver((Environment) null);
}
@Test
public void resolveIfPlaceholderPresentResolvesProperty() {
MutablePropertySources sources = getPropertySources();
this.resolver = new PropertySourcesPlaceholdersResolver(sources);
Object resolved = this.resolver.resolvePlaceholders("${FOO}");
assertThat(resolved).isEqualTo("hello world");
}
@Test
public void resolveIfPlaceholderAbsentUsesDefault() {
this.resolver = new PropertySourcesPlaceholdersResolver((PropertySources) null);
Object resolved = this.resolver.resolvePlaceholders("${FOO:bar}");
assertThat(resolved).isEqualTo("bar");
}
@Test
public void resolveIfPlaceholderAbsentAndNoDefaultShouldThrowException() {
this.resolver = new PropertySourcesPlaceholdersResolver((PropertySources) null);
this.thrown.expect(IllegalArgumentException.class);
this.thrown
.expectMessage("Could not resolve placeholder 'FOO' in value \"${FOO}\"");
this.resolver.resolvePlaceholders("${FOO}");
}
@Test
public void resolveIfHelperPresentShouldUseIt() {
MutablePropertySources sources = getPropertySources();
TestPropertyPlaceholderHelper helper = new TestPropertyPlaceholderHelper("$<",
">");
this.resolver = new PropertySourcesPlaceholdersResolver(sources, helper);
Object resolved = this.resolver.resolvePlaceholders("$<FOO>");
assertThat(resolved).isEqualTo("hello world");
}
private MutablePropertySources getPropertySources() {
MutablePropertySources sources = new MutablePropertySources();
Map<String, Object> source = new HashMap<>();
source.put("FOO", "hello world");
sources.addFirst(new MapPropertySource("test", source));
return sources;
}
static class TestPropertyPlaceholderHelper extends PropertyPlaceholderHelper {
TestPropertyPlaceholderHelper(String placeholderPrefix,
String placeholderSuffix) {
super(placeholderPrefix, placeholderSuffix);
}
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.junit.AssumptionViolatedException;
/**
* Base class for {@link InetAddress} tests.
*
* @author Phillip Webb
*/
public abstract class AbstractInetAddressTests {
public void assumeResolves(String host) {
try {
InetAddress.getByName(host);
}
catch (UnknownHostException ex) {
throw new AssumptionViolatedException("Host " + host + " not resolvable", ex);
}
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
import java.net.InetAddress;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link InetAddressToStringConverter}.
*
* @author Phillip Webb
*/
public class InetAddressToStringConverterTests extends AbstractInetAddressTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private InetAddressToStringConverter converter = new InetAddressToStringConverter();
@Test
public void convertShouldConvertToHostAddress() throws Exception {
assumeResolves("example.com");
InetAddress address = InetAddress.getByName("example.com");
String converted = this.converter.convert(address);
assertThat(converted).isEqualTo(address.getHostAddress());
}
}
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind.convert;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link StringToCharArrayConverter}.
*
* @author Phillip Webb
*/
public class StringToCharArrayConverterTests {
private StringToCharArrayConverter converter = new StringToCharArrayConverter();
@Test
public void convertShouldConvertSource() throws Exception {
char[] converted = this.converter.convert("test");
assertThat(converted).containsExactly('t', 'e', 's', 't');
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment