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
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* 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.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
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.ConfigurationPropertySource;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A container object which Binds objects from one or more
|
||||
* {@link ConfigurationPropertySource ConfigurationPropertySources}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class Binder {
|
||||
|
||||
private static final Set<Class<?>> NON_BEAN_CLASSES = Collections
|
||||
.unmodifiableSet(new HashSet<>(Arrays.asList(Object.class, Class.class)));
|
||||
|
||||
private static final List<BeanBinder> BEAN_BINDERS;
|
||||
|
||||
static {
|
||||
List<BeanBinder> beanBinders = new ArrayList<>();
|
||||
beanBinders.add(new JavaBeanBinder());
|
||||
BEAN_BINDERS = Collections.unmodifiableList(beanBinders);
|
||||
}
|
||||
|
||||
private final Iterable<ConfigurationPropertySource> sources;
|
||||
|
||||
private final PlaceholdersResolver placeholdersResolver;
|
||||
|
||||
private final BinderConversionService conversionService;
|
||||
|
||||
/**
|
||||
* Create a new {@link Binder} instance for the specified sources. A
|
||||
* {@link DefaultFormattingConversionService} will be used for all conversion.
|
||||
* @param sources the sources used for binding
|
||||
*/
|
||||
public Binder(ConfigurationPropertySource... sources) {
|
||||
this(Arrays.asList(sources), null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Binder} instance for the specified sources. A
|
||||
* {@link DefaultFormattingConversionService} will be used for all conversion.
|
||||
* @param sources the sources used for binding
|
||||
*/
|
||||
public Binder(Iterable<ConfigurationPropertySource> sources) {
|
||||
this(sources, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Binder} instance for the specified sources.
|
||||
* @param sources the sources used for binding
|
||||
* @param placeholdersResolver strategy to resolve any property place-holders
|
||||
*/
|
||||
public Binder(Iterable<ConfigurationPropertySource> sources,
|
||||
PlaceholdersResolver placeholdersResolver) {
|
||||
this(sources, placeholdersResolver, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Binder} instance for the specified sources.
|
||||
* @param sources the sources used for binding
|
||||
* @param placeholdersResolver strategy to resolve any property place-holders
|
||||
* @param conversionService the conversion service to convert values
|
||||
*/
|
||||
public Binder(Iterable<ConfigurationPropertySource> sources,
|
||||
PlaceholdersResolver placeholdersResolver,
|
||||
ConversionService conversionService) {
|
||||
Assert.notNull(sources, "Sources must not be null");
|
||||
this.sources = sources;
|
||||
this.placeholdersResolver = (placeholdersResolver != null ? placeholdersResolver
|
||||
: PlaceholdersResolver.NONE);
|
||||
this.conversionService = (conversionService instanceof BinderConversionService
|
||||
? (BinderConversionService) conversionService
|
||||
: new BinderConversionService(conversionService));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the specified target {@link Class} using this binders
|
||||
* {@link ConfigurationPropertySource property sources}.
|
||||
* @param name the configuration property name to bind
|
||||
* @param target the target class
|
||||
* @param <T> the bound type
|
||||
* @return the binding result (never {@code null})
|
||||
* @see #bind(ConfigurationPropertyName, Bindable, BindHandler)
|
||||
*/
|
||||
public <T> BindResult<T> bind(String name, Class<T> target) {
|
||||
return bind(name, Bindable.of(target));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the specified target {@link Bindable} using this binders
|
||||
* {@link ConfigurationPropertySource property sources}.
|
||||
* @param name the configuration property name to bind
|
||||
* @param target the target bindable
|
||||
* @param <T> the bound type
|
||||
* @return the binding result (never {@code null})
|
||||
* @see #bind(ConfigurationPropertyName, Bindable, BindHandler)
|
||||
*/
|
||||
public <T> BindResult<T> bind(String name, Bindable<T> target) {
|
||||
return bind(ConfigurationPropertyName.of(name), target, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the specified target {@link Bindable} using this binders
|
||||
* {@link ConfigurationPropertySource property sources}.
|
||||
* @param name the configuration property name to bind
|
||||
* @param target the target bindable
|
||||
* @param <T> the bound type
|
||||
* @return the binding result (never {@code null})
|
||||
* @see #bind(ConfigurationPropertyName, Bindable, BindHandler)
|
||||
*/
|
||||
public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target) {
|
||||
return bind(name, target, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the specified target {@link Bindable} using this binders
|
||||
* {@link ConfigurationPropertySource property sources}.
|
||||
* @param name the configuration property name to bind
|
||||
* @param target the target bindable
|
||||
* @param handler the bind handler (may be {@code null})
|
||||
* @param <T> the bound type
|
||||
* @return the binding result (never {@code null})
|
||||
*/
|
||||
public <T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler) {
|
||||
return bind(ConfigurationPropertyName.of(name), target, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the specified target {@link Bindable} using this binders
|
||||
* {@link ConfigurationPropertySource property sources}.
|
||||
* @param name the configuration property name to bind
|
||||
* @param target the target bindable
|
||||
* @param handler the bind handler (may be {@code null})
|
||||
* @param <T> the bound type
|
||||
* @return the binding result (never {@code null})
|
||||
*/
|
||||
public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target,
|
||||
BindHandler handler) {
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Assert.notNull(target, "Target must not be null");
|
||||
handler = (handler != null ? handler : BindHandler.DEFAULT);
|
||||
Context context = new Context();
|
||||
T bound = bind(name, target, handler, context);
|
||||
return BindResult.of(bound);
|
||||
}
|
||||
|
||||
protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
|
||||
BindHandler handler, Context context) {
|
||||
try {
|
||||
if (!handler.onStart(name, target, context)) {
|
||||
return null;
|
||||
}
|
||||
Object bound = bindObject(name, target, handler, context);
|
||||
return handleBindResult(name, target, handler, context, bound);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return handleBindError(name, target, handler, context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T handleBindResult(ConfigurationPropertyName name, Bindable<T> target,
|
||||
BindHandler handler, Context context, Object result) throws Exception {
|
||||
result = convert(result, target);
|
||||
if (result != null) {
|
||||
result = handler.onSuccess(name, target, context, result);
|
||||
result = convert(result, target);
|
||||
}
|
||||
handler.onFinish(name, target, context, result);
|
||||
return convert(result, target);
|
||||
}
|
||||
|
||||
private <T> T handleBindError(ConfigurationPropertyName name, Bindable<T> target,
|
||||
BindHandler handler, Context context, Exception error) {
|
||||
try {
|
||||
Object result = handler.onFailure(name, target, context, error);
|
||||
return convert(result, target);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ex instanceof BindException) {
|
||||
throw (BindException) ex;
|
||||
}
|
||||
throw new BindException(name, target, context.getConfigurationProperty(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T convert(Object value, Bindable<T> target) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return this.conversionService.convert(value, target);
|
||||
}
|
||||
|
||||
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target,
|
||||
BindHandler handler, Context context) throws Exception {
|
||||
AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
|
||||
if (aggregateBinder != null) {
|
||||
return bindAggregate(name, target, handler, context, aggregateBinder);
|
||||
}
|
||||
ConfigurationProperty property = findProperty(name, context);
|
||||
if (property != null) {
|
||||
return bindProperty(name, target, handler, context, property);
|
||||
}
|
||||
return bindBean(name, target, handler, context);
|
||||
}
|
||||
|
||||
private AggregateBinder<?> getAggregateBinder(Bindable<?> target, Context context) {
|
||||
Class<?> resolvedType = target.getType().resolve();
|
||||
if (Map.class.isAssignableFrom(resolvedType)) {
|
||||
return new MapBinder(context);
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(resolvedType)) {
|
||||
return new CollectionBinder(context);
|
||||
}
|
||||
if (target.getType().isArray()) {
|
||||
return new ArrayBinder(context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private <T> Object bindAggregate(ConfigurationPropertyName name, Bindable<T> target,
|
||||
BindHandler handler, Context context, AggregateBinder<?> aggregateBinder) {
|
||||
AggregateElementBinder elementBinder = (itemName, itemTarget, source) -> {
|
||||
return context.withSource(source,
|
||||
() -> Binder.this.bind(itemName, itemTarget, handler, context));
|
||||
};
|
||||
return context.withIncreasedDepth(
|
||||
() -> aggregateBinder.bind(name, target, elementBinder));
|
||||
}
|
||||
|
||||
private ConfigurationProperty findProperty(ConfigurationPropertyName name,
|
||||
Context context) {
|
||||
return context.streamSources()
|
||||
.map((source) -> source.getConfigurationProperty(name))
|
||||
.filter(Objects::nonNull).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private <T> Object bindProperty(ConfigurationPropertyName name, Bindable<T> target,
|
||||
BindHandler handler, Context context, ConfigurationProperty property) {
|
||||
context.setConfigurationProperty(property);
|
||||
Object result = property.getValue();
|
||||
result = this.placeholdersResolver.resolvePlaceholders(result);
|
||||
result = this.conversionService.convert(result, target);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target,
|
||||
BindHandler handler, Context context) throws Exception {
|
||||
boolean hasKnownBindableProperties = context.streamSources()
|
||||
.flatMap((s) -> s.filter(name::isAncestorOf).stream()).findAny()
|
||||
.isPresent();
|
||||
if (!hasKnownBindableProperties && isUnbindableBean(target)) {
|
||||
return null;
|
||||
}
|
||||
BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(
|
||||
name.append(propertyName), propertyTarget, handler, context);
|
||||
Class<?> type = target.getType().resolve();
|
||||
if (context.hasBoundBean(type)) {
|
||||
return null;
|
||||
}
|
||||
return context.withBean(type, () -> {
|
||||
Stream<?> boundBeans = BEAN_BINDERS.stream().map(
|
||||
(b) -> b.bind(target, hasKnownBindableProperties, propertyBinder));
|
||||
return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isUnbindableBean(Bindable<?> target) {
|
||||
Class<?> resolved = target.getType().resolve();
|
||||
if (resolved.isPrimitive() || NON_BEAN_CLASSES.contains(resolved)) {
|
||||
return true;
|
||||
}
|
||||
String packageName = ClassUtils.getPackageName(resolved);
|
||||
return packageName.startsWith("java.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Binder} instance from the specified environment.
|
||||
* @param environment the environment (must be a {@link ConfigurableEnvironment})
|
||||
* @return a {@link Binder} instance
|
||||
*/
|
||||
public static Binder get(Environment environment) {
|
||||
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
|
||||
return new Binder(
|
||||
ConfigurationPropertySources.get((ConfigurableEnvironment) environment),
|
||||
new PropertySourcesPlaceholdersResolver(environment));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BindContext} implementation.
|
||||
*/
|
||||
final class Context implements BindContext {
|
||||
|
||||
private int depth;
|
||||
|
||||
private int sourcePushCount;
|
||||
|
||||
private final List<ConfigurationPropertySource> source = Arrays
|
||||
.asList((ConfigurationPropertySource) null);
|
||||
|
||||
private final Deque<Class<?>> beans = new ArrayDeque<>();
|
||||
|
||||
private ConfigurationProperty configurationProperty;
|
||||
|
||||
void increaseDepth() {
|
||||
this.depth++;
|
||||
}
|
||||
|
||||
void decreaseDepth() {
|
||||
this.depth--;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDepth() {
|
||||
return this.depth;
|
||||
}
|
||||
|
||||
public <T> T withSource(ConfigurationPropertySource source,
|
||||
Supplier<T> supplier) {
|
||||
if (source == null) {
|
||||
return supplier.get();
|
||||
}
|
||||
this.source.set(0, source);
|
||||
this.sourcePushCount++;
|
||||
try {
|
||||
return supplier.get();
|
||||
}
|
||||
finally {
|
||||
this.sourcePushCount--;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T withBean(Class<?> bean, Supplier<T> supplier) {
|
||||
this.beans.push(bean);
|
||||
try {
|
||||
return withIncreasedDepth(supplier);
|
||||
}
|
||||
finally {
|
||||
this.beans.pop();
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T withIncreasedDepth(Supplier<T> supplier) {
|
||||
increaseDepth();
|
||||
try {
|
||||
return supplier.get();
|
||||
}
|
||||
finally {
|
||||
decreaseDepth();
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<ConfigurationPropertySource> streamSources() {
|
||||
if (this.sourcePushCount > 0) {
|
||||
return this.source.stream();
|
||||
}
|
||||
return StreamSupport.stream(Binder.this.sources.spliterator(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ConfigurationPropertySource> getSources() {
|
||||
if (this.sourcePushCount > 0) {
|
||||
return this.source;
|
||||
}
|
||||
return Binder.this.sources;
|
||||
}
|
||||
|
||||
public boolean hasBoundBean(Class<?> bean) {
|
||||
return this.beans.contains(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationProperty getConfigurationProperty() {
|
||||
return this.configurationProperty;
|
||||
}
|
||||
|
||||
void setConfigurationProperty(ConfigurationProperty configurationProperty) {
|
||||
this.configurationProperty = configurationProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaceholdersResolver getPlaceholdersResolver() {
|
||||
return Binder.this.placeholdersResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinderConversionService getConversionService() {
|
||||
return Binder.this.conversionService;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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("."));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.boot.context.properties.source.ConfigurationProperty;
|
||||
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.ResolvableType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
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 ArrayBinder}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class ArrayBinderTests {
|
||||
|
||||
private static final Bindable<List<Integer>> INTEGER_LIST = Bindable
|
||||
.listOf(Integer.class);
|
||||
|
||||
private static final Bindable<Integer[]> INTEGER_ARRAY = Bindable.of(Integer[].class);
|
||||
|
||||
private List<ConfigurationPropertySource> sources = new ArrayList<>();
|
||||
|
||||
private Binder binder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.binder = new Binder(this.sources);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayShouldReturnArray() 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);
|
||||
Integer[] result = this.binder.bind("foo", INTEGER_ARRAY).get();
|
||||
assertThat(result).containsExactly(1, 2, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToCollectionShouldTriggerOnSuccess() throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo[0]", "1", "line1"));
|
||||
BindHandler handler = mock(BindHandler.class,
|
||||
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
|
||||
this.binder.bind("foo", INTEGER_LIST, handler);
|
||||
InOrder inOrder = inOrder(handler);
|
||||
inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo[0]")),
|
||||
eq(Bindable.of(Integer.class)), any(), eq(1));
|
||||
inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo")),
|
||||
eq(INTEGER_LIST), any(), isA(List.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayShouldReturnPrimativeArray() 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);
|
||||
int[] result = this.binder.bind("foo", Bindable.of(int[].class)).get();
|
||||
assertThat(result).containsExactly(1, 2, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenNestedShouldReturnPopulatedArray() 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);
|
||||
ResolvableType type = ResolvableType.forArrayComponent(INTEGER_ARRAY.getType());
|
||||
Bindable<Integer[][]> target = Bindable.of(type);
|
||||
Integer[][] result = this.binder.bind("foo", target).get();
|
||||
assertThat(result).hasSize(2);
|
||||
assertThat(result[0]).containsExactly(1, 2);
|
||||
assertThat(result[1]).containsExactly(3, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenNestedListShouldReturnPopulatedArray() 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);
|
||||
ResolvableType type = ResolvableType.forArrayComponent(INTEGER_LIST.getType());
|
||||
Bindable<List<Integer>[]> target = Bindable.of(type);
|
||||
List<Integer>[] result = this.binder.bind("foo", target).get();
|
||||
assertThat(result).hasSize(2);
|
||||
assertThat(result[0]).containsExactly(1, 2);
|
||||
assertThat(result[1]).containsExactly(3, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenNotInOrderShouldReturnPopulatedArray() 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);
|
||||
Integer[] result = this.binder.bind("foo", INTEGER_ARRAY).get();
|
||||
assertThat(result).containsExactly(1, 2, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenNonSequentialShouldThrowException() 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_ARRAY);
|
||||
fail("No exception thrown");
|
||||
}
|
||||
catch (BindException ex) {
|
||||
Set<ConfigurationProperty> unbound = ((UnboundConfigurationPropertiesException) ex
|
||||
.getCause()).getUnboundProperties();
|
||||
assertThat(unbound.size()).isEqualTo(1);
|
||||
ConfigurationProperty property = unbound.iterator().next();
|
||||
assertThat(property.getName().toString()).isEqualTo("foo[3]");
|
||||
assertThat(property.getValue()).isEqualTo("3");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenNonIterableShouldReturnPopulatedArray() 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);
|
||||
Integer[] result = this.binder.bind("foo", INTEGER_ARRAY).get();
|
||||
assertThat(result).containsExactly(1, 2, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenMultipleSourceShouldOnlyUseFirst() 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);
|
||||
Integer[] result = this.binder.bind("foo", INTEGER_ARRAY).get();
|
||||
assertThat(result).containsExactly(1, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenHasExistingCollectionShouldReplaceAllContents()
|
||||
throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo[0]", "1"));
|
||||
Integer[] existing = new Integer[2];
|
||||
existing[0] = 1000;
|
||||
existing[1] = 1001;
|
||||
Integer[] result = this.binder
|
||||
.bind("foo", INTEGER_ARRAY.withExistingValue(existing)).get();
|
||||
assertThat(result).containsExactly(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenNoValueShouldReturnUnbound() throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("faf.bar", "1"));
|
||||
BindResult<Integer[]> result = this.binder.bind("foo", INTEGER_ARRAY);
|
||||
assertThat(result.isBound()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayShouldTriggerOnSuccess() throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo[0]", "1", "line1"));
|
||||
BindHandler handler = mock(BindHandler.class,
|
||||
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
|
||||
Bindable<Integer[]> target = INTEGER_ARRAY;
|
||||
this.binder.bind("foo", target, handler);
|
||||
InOrder inOrder = inOrder(handler);
|
||||
inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo[0]")),
|
||||
eq(Bindable.of(Integer.class)), any(), eq(1));
|
||||
inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo")),
|
||||
eq(target), any(), isA(Integer[].class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenCommaListShouldReturnPopulatedArray() throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo", "1,2,3"));
|
||||
int[] result = this.binder.bind("foo", Bindable.of(int[].class)).get();
|
||||
assertThat(result).containsExactly(1, 2, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenCommaListAndIndexedShouldOnlyUseFirst() 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");
|
||||
int[] result = this.binder.bind("foo", Bindable.of(int[].class)).get();
|
||||
assertThat(result).containsExactly(1, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenIndexedAndCommaListShouldOnlyUseFirst() 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");
|
||||
int[] result = this.binder.bind("foo", Bindable.of(int[].class)).get();
|
||||
assertThat(result).containsExactly(1, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayShouldBindCharArray() throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo", "word"));
|
||||
char[] result = this.binder.bind("foo", Bindable.of(char[].class)).get();
|
||||
assertThat(result).containsExactly("word".toCharArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToArrayWhenEmptyStringShouldReturnEmptyArray() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo", "");
|
||||
this.sources.add(source);
|
||||
String[] result = this.binder.bind("foo", Bindable.of(String[].class)).get();
|
||||
assertThat(result).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,810 @@
|
||||
/*
|
||||
* 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.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.handler.IgnoreErrorsBindHandler;
|
||||
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.format.annotation.DateTimeFormat;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
/**
|
||||
* Tests for {@link JavaBeanBinder}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class JavaBeanBinderTests {
|
||||
|
||||
@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 bindToClassShouldCreateBoundBean() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.int-value", "12");
|
||||
source.put("foo.long-value", "34");
|
||||
source.put("foo.string-value", "foo");
|
||||
source.put("foo.enum-value", "foo-bar");
|
||||
this.sources.add(source);
|
||||
ExampleValueBean bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleValueBean.class)).get();
|
||||
assertThat(bean.getIntValue()).isEqualTo(12);
|
||||
assertThat(bean.getLongValue()).isEqualTo(34);
|
||||
assertThat(bean.getStringValue()).isEqualTo("foo");
|
||||
assertThat(bean.getEnumValue()).isEqualTo(ExampleEnum.FOO_BAR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenHasNoPrefixShouldCreateBoundBean() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("int-value", "12");
|
||||
source.put("long-value", "34");
|
||||
source.put("string-value", "foo");
|
||||
source.put("enum-value", "foo-bar");
|
||||
this.sources.add(source);
|
||||
ExampleValueBean bean = this.binder.bind(ConfigurationPropertyName.of(""),
|
||||
Bindable.of(ExampleValueBean.class)).get();
|
||||
assertThat(bean.getIntValue()).isEqualTo(12);
|
||||
assertThat(bean.getLongValue()).isEqualTo(34);
|
||||
assertThat(bean.getStringValue()).isEqualTo("foo");
|
||||
assertThat(bean.getEnumValue()).isEqualTo(ExampleEnum.FOO_BAR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToInstanceShouldBindToInstance() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.int-value", "12");
|
||||
source.put("foo.long-value", "34");
|
||||
source.put("foo.string-value", "foo");
|
||||
source.put("foo.enum-value", "foo-bar");
|
||||
this.sources.add(source);
|
||||
ExampleValueBean bean = new ExampleValueBean();
|
||||
ExampleValueBean boundBean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleValueBean.class).withExistingValue(bean))
|
||||
.get();
|
||||
assertThat(boundBean).isSameAs(bean);
|
||||
assertThat(bean.getIntValue()).isEqualTo(12);
|
||||
assertThat(bean.getLongValue()).isEqualTo(34);
|
||||
assertThat(bean.getStringValue()).isEqualTo("foo");
|
||||
assertThat(bean.getEnumValue()).isEqualTo(ExampleEnum.FOO_BAR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToInstanceWithNoPropertiesShouldReturnUnbound() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
this.sources.add(source);
|
||||
ExampleDefaultsBean bean = new ExampleDefaultsBean();
|
||||
BindResult<ExampleDefaultsBean> boundBean = this.binder.bind("foo",
|
||||
Bindable.of(ExampleDefaultsBean.class).withExistingValue(bean));
|
||||
assertThat(boundBean.isBound()).isFalse();
|
||||
assertThat(bean.getFoo()).isEqualTo(123);
|
||||
assertThat(bean.getBar()).isEqualTo(456);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassShouldLeaveDefaults() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.bar", "999");
|
||||
this.sources.add(source);
|
||||
ExampleDefaultsBean bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleDefaultsBean.class)).get();
|
||||
assertThat(bean.getFoo()).isEqualTo(123);
|
||||
assertThat(bean.getBar()).isEqualTo(999);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToExistingInstanceShouldLeaveDefaults() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.bar", "999");
|
||||
this.sources.add(source);
|
||||
ExampleDefaultsBean bean = new ExampleDefaultsBean();
|
||||
bean.setFoo(888);
|
||||
ExampleDefaultsBean boundBean = this.binder
|
||||
.bind("foo",
|
||||
Bindable.of(ExampleDefaultsBean.class).withExistingValue(bean))
|
||||
.get();
|
||||
assertThat(boundBean).isSameAs(bean);
|
||||
assertThat(bean.getFoo()).isEqualTo(888);
|
||||
assertThat(bean.getBar()).isEqualTo(999);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassShouldBindToMap() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.map.foo-bar", "1");
|
||||
source.put("foo.map.bar-baz", "2");
|
||||
this.sources.add(source);
|
||||
ExampleMapBean bean = this.binder.bind("foo", Bindable.of(ExampleMapBean.class))
|
||||
.get();
|
||||
assertThat(bean.getMap()).containsExactly(entry(ExampleEnum.FOO_BAR, 1),
|
||||
entry(ExampleEnum.BAR_BAZ, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassShouldBindToList() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.list[0]", "foo-bar");
|
||||
source.put("foo.list[1]", "bar-baz");
|
||||
this.sources.add(source);
|
||||
ExampleListBean bean = this.binder.bind("foo", Bindable.of(ExampleListBean.class))
|
||||
.get();
|
||||
assertThat(bean.getList()).containsExactly(ExampleEnum.FOO_BAR,
|
||||
ExampleEnum.BAR_BAZ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToListIfUnboundElementsPresentShouldThrowException()
|
||||
throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.list[0]", "foo-bar");
|
||||
source.put("foo.list[2]", "bar-baz");
|
||||
this.sources.add(source);
|
||||
this.thrown.expect(BindException.class);
|
||||
this.thrown.expectCause(
|
||||
Matchers.instanceOf(UnboundConfigurationPropertiesException.class));
|
||||
this.binder.bind("foo", Bindable.of(ExampleListBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassShouldBindToSet() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.set[0]", "foo-bar");
|
||||
source.put("foo.set[1]", "bar-baz");
|
||||
this.sources.add(source);
|
||||
ExampleSetBean bean = this.binder.bind("foo", Bindable.of(ExampleSetBean.class))
|
||||
.get();
|
||||
assertThat(bean.getSet()).containsExactly(ExampleEnum.FOO_BAR,
|
||||
ExampleEnum.BAR_BAZ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassShouldBindToCollection() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.collection[0]", "foo-bar");
|
||||
source.put("foo.collection[1]", "bar-baz");
|
||||
this.sources.add(source);
|
||||
ExampleCollectionBean bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleCollectionBean.class)).get();
|
||||
assertThat(bean.getCollection()).containsExactly(ExampleEnum.FOO_BAR,
|
||||
ExampleEnum.BAR_BAZ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenHasNoSetterShouldBindToMap() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.map.foo-bar", "1");
|
||||
source.put("foo.map.bar-baz", "2");
|
||||
this.sources.add(source);
|
||||
ExampleMapBeanWithoutSetter bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleMapBeanWithoutSetter.class)).get();
|
||||
assertThat(bean.getMap()).containsExactly(entry(ExampleEnum.FOO_BAR, 1),
|
||||
entry(ExampleEnum.BAR_BAZ, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenHasNoSetterShouldBindToList() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.list[0]", "foo-bar");
|
||||
source.put("foo.list[1]", "bar-baz");
|
||||
this.sources.add(source);
|
||||
ExampleListBeanWithoutSetter bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleListBeanWithoutSetter.class)).get();
|
||||
assertThat(bean.getList()).containsExactly(ExampleEnum.FOO_BAR,
|
||||
ExampleEnum.BAR_BAZ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenHasNoSetterShouldBindToSet() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.set[0]", "foo-bar");
|
||||
source.put("foo.set[1]", "bar-baz");
|
||||
this.sources.add(source);
|
||||
ExampleSetBeanWithoutSetter bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleSetBeanWithoutSetter.class)).get();
|
||||
assertThat(bean.getSet()).containsExactly(ExampleEnum.FOO_BAR,
|
||||
ExampleEnum.BAR_BAZ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenHasNoSetterShouldBindToCollection() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.collection[0]", "foo-bar");
|
||||
source.put("foo.collection[1]", "bar-baz");
|
||||
this.sources.add(source);
|
||||
ExampleCollectionBeanWithoutSetter bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleCollectionBeanWithoutSetter.class)).get();
|
||||
assertThat(bean.getCollection()).containsExactly(ExampleEnum.FOO_BAR,
|
||||
ExampleEnum.BAR_BAZ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassShouldBindNested() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.value-bean.int-value", "123");
|
||||
source.put("foo.value-bean.string-value", "foo");
|
||||
this.sources.add(source);
|
||||
ExampleNestedBean bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleNestedBean.class)).get();
|
||||
assertThat(bean.getValueBean().getIntValue()).isEqualTo(123);
|
||||
assertThat(bean.getValueBean().getStringValue()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenIterableShouldBindNestedBasedOnInstance()
|
||||
throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.value-bean.int-value", "123");
|
||||
source.put("foo.value-bean.string-value", "foo");
|
||||
this.sources.add(source);
|
||||
ExampleNestedBeanWithoutSetterOrType bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleNestedBeanWithoutSetterOrType.class))
|
||||
.get();
|
||||
ExampleValueBean valueBean = (ExampleValueBean) bean.getValueBean();
|
||||
assertThat(valueBean.getIntValue()).isEqualTo(123);
|
||||
assertThat(valueBean.getStringValue()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenNotIterableShouldNotBindNestedBasedOnInstance()
|
||||
throws Exception {
|
||||
// If we can't tell that binding will happen, we don't want to randomly invoke
|
||||
// getters on the class and cause side effects
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.value-bean.int-value", "123");
|
||||
source.put("foo.value-bean.string-value", "foo");
|
||||
source.setNonIterable(true);
|
||||
this.sources.add(source);
|
||||
BindResult<ExampleNestedBeanWithoutSetterOrType> bean = this.binder.bind("foo",
|
||||
Bindable.of(ExampleNestedBeanWithoutSetterOrType.class));
|
||||
assertThat(bean.isBound()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenHasNoSetterShouldBindNested() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.value-bean.int-value", "123");
|
||||
source.put("foo.value-bean.string-value", "foo");
|
||||
this.sources.add(source);
|
||||
ExampleNestedBeanWithoutSetter bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleNestedBeanWithoutSetter.class)).get();
|
||||
assertThat(bean.getValueBean().getIntValue()).isEqualTo(123);
|
||||
assertThat(bean.getValueBean().getStringValue()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenHasNoSetterAndImmutableShouldThrowException()
|
||||
throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.nested.foo", "bar");
|
||||
this.sources.add(source);
|
||||
this.thrown.expect(BindException.class);
|
||||
this.binder.bind("foo",
|
||||
Bindable.of(ExampleImmutableNestedBeanWithoutSetter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToInstanceWhenNoNestedShouldLeaveNestedAsNull() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("faf.value-bean.int-value", "123");
|
||||
this.sources.add(source);
|
||||
ExampleNestedBean bean = new ExampleNestedBean();
|
||||
BindResult<ExampleNestedBean> boundBean = this.binder.bind("foo",
|
||||
Bindable.of(ExampleNestedBean.class).withExistingValue(bean));
|
||||
assertThat(boundBean.isBound()).isFalse();
|
||||
assertThat(bean.getValueBean()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenPropertiesMissingShouldReturnUnbound() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("faf.int-value", "12");
|
||||
this.sources.add(source);
|
||||
BindResult<ExampleValueBean> bean = this.binder.bind("foo",
|
||||
Bindable.of(ExampleValueBean.class));
|
||||
assertThat(bean.isBound()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenNoDefaultConstructorShouldReturnUnbound()
|
||||
throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.value", "bar");
|
||||
this.sources.add(source);
|
||||
BindResult<ExampleWithNonDefaultConstructor> bean = this.binder.bind("foo",
|
||||
Bindable.of(ExampleWithNonDefaultConstructor.class));
|
||||
assertThat(bean.isBound()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToInstanceWhenNoDefaultConstructorShouldBind() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.value", "bar");
|
||||
this.sources.add(source);
|
||||
ExampleWithNonDefaultConstructor bean = new ExampleWithNonDefaultConstructor(
|
||||
"faf");
|
||||
ExampleWithNonDefaultConstructor boundBean = this.binder.bind("foo", Bindable
|
||||
.of(ExampleWithNonDefaultConstructor.class).withExistingValue(bean))
|
||||
.get();
|
||||
assertThat(boundBean).isSameAs(bean);
|
||||
assertThat(bean.getValue()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassShouldBindHierarchy() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.int-value", "123");
|
||||
source.put("foo.long-value", "456");
|
||||
this.sources.add(source);
|
||||
ExampleSubclassBean bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleSubclassBean.class)).get();
|
||||
assertThat(bean.getIntValue()).isEqualTo(123);
|
||||
assertThat(bean.getLongValue()).isEqualTo(456);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenPropertyCannotBeConvertedShouldThrowException()
|
||||
throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo.int-value", "foo"));
|
||||
this.thrown.expect(BindException.class);
|
||||
this.binder.bind("foo", Bindable.of(ExampleValueBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenPropertyCannotBeConvertedAndIgnoreErrorsShouldNotSetValue()
|
||||
throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.int-value", "12");
|
||||
source.put("foo.long-value", "bang");
|
||||
source.put("foo.string-value", "foo");
|
||||
source.put("foo.enum-value", "foo-bar");
|
||||
this.sources.add(source);
|
||||
IgnoreErrorsBindHandler handler = new IgnoreErrorsBindHandler();
|
||||
ExampleValueBean bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleValueBean.class), handler).get();
|
||||
assertThat(bean.getIntValue()).isEqualTo(12);
|
||||
assertThat(bean.getLongValue()).isEqualTo(0);
|
||||
assertThat(bean.getStringValue()).isEqualTo("foo");
|
||||
assertThat(bean.getEnumValue()).isEqualTo(ExampleEnum.FOO_BAR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWhenMismatchedGetSetShouldBind() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.value", "123");
|
||||
this.sources.add(source);
|
||||
ExampleMismatchBean bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleMismatchBean.class)).get();
|
||||
assertThat(bean.getValue()).isEqualTo("123");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassShouldNotInvokeExtraMethods() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource(
|
||||
"foo.value", "123");
|
||||
source.setNonIterable(true);
|
||||
this.sources.add(source);
|
||||
ExampleWithThrowingGetters bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleWithThrowingGetters.class)).get();
|
||||
assertThat(bean.getValue()).isEqualTo(123);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToClassWithSelfReferenceShouldBind() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.value", "123");
|
||||
this.sources.add(source);
|
||||
ExampleWithSelfReference bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleWithSelfReference.class)).get();
|
||||
assertThat(bean.getValue()).isEqualTo(123);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindtoInstanceWithExistingValueShouldReturnUnbound() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
this.sources.add(source);
|
||||
ExampleNestedBean existingValue = new ExampleNestedBean();
|
||||
ExampleValueBean valueBean = new ExampleValueBean();
|
||||
existingValue.setValueBean(valueBean);
|
||||
BindResult<ExampleNestedBean> result = this.binder.bind("foo",
|
||||
Bindable.of(ExampleNestedBean.class).withExistingValue(existingValue));
|
||||
assertThat(result.isBound()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWithAnnotations() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.date", "2014-04-01");
|
||||
this.sources.add(source);
|
||||
ConverterAnnotatedExampleBean bean = this.binder
|
||||
.bind("foo", Bindable.of(ConverterAnnotatedExampleBean.class)).get();
|
||||
assertThat(bean.getDate().toString()).isEqualTo("2014-04-01");
|
||||
}
|
||||
|
||||
public static class ExampleValueBean {
|
||||
|
||||
private int intValue;
|
||||
|
||||
private long longValue;
|
||||
|
||||
private String stringValue;
|
||||
|
||||
private ExampleEnum enumValue;
|
||||
|
||||
public int getIntValue() {
|
||||
return this.intValue;
|
||||
}
|
||||
|
||||
public void setIntValue(int intValue) {
|
||||
this.intValue = intValue;
|
||||
}
|
||||
|
||||
public long getLongValue() {
|
||||
return this.longValue;
|
||||
}
|
||||
|
||||
public void setLongValue(long longValue) {
|
||||
this.longValue = longValue;
|
||||
}
|
||||
|
||||
public String getStringValue() {
|
||||
return this.stringValue;
|
||||
}
|
||||
|
||||
public void setStringValue(String stringValue) {
|
||||
this.stringValue = stringValue;
|
||||
}
|
||||
|
||||
public ExampleEnum getEnumValue() {
|
||||
return this.enumValue;
|
||||
}
|
||||
|
||||
public void setEnumValue(ExampleEnum enumValue) {
|
||||
this.enumValue = enumValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleDefaultsBean {
|
||||
|
||||
private int foo = 123;
|
||||
|
||||
private int bar = 456;
|
||||
|
||||
public int getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(int foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
public int getBar() {
|
||||
return this.bar;
|
||||
}
|
||||
|
||||
public void setBar(int bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleMapBean {
|
||||
|
||||
private Map<ExampleEnum, Integer> map;
|
||||
|
||||
public Map<ExampleEnum, Integer> getMap() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
public void setMap(Map<ExampleEnum, Integer> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleListBean {
|
||||
|
||||
private List<ExampleEnum> list;
|
||||
|
||||
public List<ExampleEnum> getList() {
|
||||
return this.list;
|
||||
}
|
||||
|
||||
public void setList(List<ExampleEnum> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleSetBean {
|
||||
|
||||
private Set<ExampleEnum> set;
|
||||
|
||||
public Set<ExampleEnum> getSet() {
|
||||
return this.set;
|
||||
}
|
||||
|
||||
public void setSet(Set<ExampleEnum> set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleCollectionBean {
|
||||
|
||||
private Collection<ExampleEnum> collection;
|
||||
|
||||
public Collection<ExampleEnum> getCollection() {
|
||||
return this.collection;
|
||||
}
|
||||
|
||||
public void setCollection(Collection<ExampleEnum> collection) {
|
||||
this.collection = collection;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleMapBeanWithoutSetter {
|
||||
|
||||
private Map<ExampleEnum, Integer> map = new LinkedHashMap<>();
|
||||
|
||||
public Map<ExampleEnum, Integer> getMap() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleListBeanWithoutSetter {
|
||||
|
||||
private List<ExampleEnum> list = new ArrayList<>();
|
||||
|
||||
public List<ExampleEnum> getList() {
|
||||
return this.list;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleSetBeanWithoutSetter {
|
||||
|
||||
private Set<ExampleEnum> set = new LinkedHashSet<>();
|
||||
|
||||
public Set<ExampleEnum> getSet() {
|
||||
return this.set;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleCollectionBeanWithoutSetter {
|
||||
|
||||
private Collection<ExampleEnum> collection = new ArrayList<>();
|
||||
|
||||
public Collection<ExampleEnum> getCollection() {
|
||||
return this.collection;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleNestedBean {
|
||||
|
||||
private ExampleValueBean valueBean;
|
||||
|
||||
public ExampleValueBean getValueBean() {
|
||||
return this.valueBean;
|
||||
}
|
||||
|
||||
public void setValueBean(ExampleValueBean valueBean) {
|
||||
this.valueBean = valueBean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleNestedBeanWithoutSetter {
|
||||
|
||||
private ExampleValueBean valueBean = new ExampleValueBean();
|
||||
|
||||
public ExampleValueBean getValueBean() {
|
||||
return this.valueBean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleNestedBeanWithoutSetterOrType {
|
||||
|
||||
private ExampleValueBean valueBean = new ExampleValueBean();
|
||||
|
||||
public Object getValueBean() {
|
||||
return this.valueBean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleImmutableNestedBeanWithoutSetter {
|
||||
|
||||
private NestedImmutable nested = new NestedImmutable();
|
||||
|
||||
public NestedImmutable getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
public static class NestedImmutable {
|
||||
|
||||
public String getFoo() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleWithNonDefaultConstructor {
|
||||
|
||||
private String value;
|
||||
|
||||
public ExampleWithNonDefaultConstructor(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public abstract static class ExampleSuperClassBean {
|
||||
|
||||
private int intValue;
|
||||
|
||||
public int getIntValue() {
|
||||
return this.intValue;
|
||||
}
|
||||
|
||||
public void setIntValue(int intValue) {
|
||||
this.intValue = intValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleSubclassBean extends ExampleSuperClassBean {
|
||||
|
||||
private long longValue;
|
||||
|
||||
public long getLongValue() {
|
||||
return this.longValue;
|
||||
}
|
||||
|
||||
public void setLongValue(long longValue) {
|
||||
this.longValue = longValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleMismatchBean {
|
||||
|
||||
private int value;
|
||||
|
||||
public String getValue() {
|
||||
return String.valueOf(this.value);
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleWithThrowingGetters {
|
||||
|
||||
private int value;
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public List<String> getNames() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
public ExampleValueBean getNested() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleWithSelfReference {
|
||||
|
||||
private int value;
|
||||
|
||||
private ExampleWithSelfReference self;
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ExampleWithSelfReference getSelf() {
|
||||
return this.self;
|
||||
}
|
||||
|
||||
public void setSelf(ExampleWithSelfReference self) {
|
||||
this.self = self;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum ExampleEnum {
|
||||
|
||||
FOO_BAR,
|
||||
|
||||
BAR_BAZ
|
||||
|
||||
}
|
||||
|
||||
public static class ConverterAnnotatedExampleBean {
|
||||
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
|
||||
private LocalDate date;
|
||||
|
||||
public LocalDate getDate() {
|
||||
return this.date;
|
||||
}
|
||||
|
||||
public void setDate(LocalDate date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* 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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.BinderTests.ExampleEnum;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
|
||||
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
|
||||
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.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 MapBinder}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class MapBinderTests {
|
||||
|
||||
private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable
|
||||
.mapOf(String.class, String.class);
|
||||
|
||||
private static final Bindable<Map<String, Integer>> STRING_INTEGER_MAP = Bindable
|
||||
.mapOf(String.class, Integer.class);
|
||||
|
||||
private static final Bindable<Map<Integer, Integer>> INTEGER_INTEGER_MAP = Bindable
|
||||
.mapOf(Integer.class, Integer.class);
|
||||
|
||||
private static final Bindable<Map<String, Object>> STRING_OBJECT_MAP = Bindable
|
||||
.mapOf(String.class, Object.class);
|
||||
|
||||
private List<ConfigurationPropertySource> sources = new ArrayList<>();
|
||||
|
||||
private Binder binder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.binder = new Binder(this.sources);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapShouldReturnPopulatedMap() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.bar", "1");
|
||||
source.put("foo.[baz]", "2");
|
||||
source.put("foo[BiNg]", "3");
|
||||
this.sources.add(source);
|
||||
Map<String, String> result = this.binder.bind("foo", STRING_STRING_MAP).get();
|
||||
assertThat(result).hasSize(3);
|
||||
assertThat(result).containsEntry("bar", "1");
|
||||
assertThat(result).containsEntry("baz", "2");
|
||||
assertThat(result).containsEntry("BiNg", "3");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void bindToMapWithEmptyPrefix() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.bar", "1");
|
||||
this.sources.add(source);
|
||||
Map<String, Object> result = this.binder.bind("", STRING_OBJECT_MAP).get();
|
||||
assertThat((Map<String, Object>) result.get("foo")).containsEntry("bar", "1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapShouldConvertMapValue() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.bar", "1");
|
||||
source.put("foo.[baz]", "2");
|
||||
source.put("foo[BiNg]", "3");
|
||||
source.put("faf.bar", "x");
|
||||
this.sources.add(source);
|
||||
Map<String, Integer> result = this.binder.bind("foo", STRING_INTEGER_MAP).get();
|
||||
assertThat(result).hasSize(3);
|
||||
assertThat(result).containsEntry("bar", 1);
|
||||
assertThat(result).containsEntry("baz", 2);
|
||||
assertThat(result).containsEntry("BiNg", 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapShouldBindToMapValue() throws Exception {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(Map.class,
|
||||
ResolvableType.forClass(String.class), STRING_INTEGER_MAP.getType());
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.bar.baz", "1");
|
||||
source.put("foo.bar.bin", "2");
|
||||
source.put("foo.far.baz", "3");
|
||||
source.put("foo.far.bin", "4");
|
||||
source.put("faf.far.bin", "x");
|
||||
this.sources.add(source);
|
||||
Map<String, Map<String, Integer>> result = this.binder
|
||||
.bind("foo", Bindable.<Map<String, Map<String, Integer>>>of(type)).get();
|
||||
System.out.println(result);
|
||||
assertThat(result).hasSize(2);
|
||||
assertThat(result.get("bar")).containsEntry("baz", 1).containsEntry("bin", 2);
|
||||
assertThat(result.get("far")).containsEntry("baz", 3).containsEntry("bin", 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapShouldBindNestedMapValue() throws Exception {
|
||||
ResolvableType nestedType = ResolvableType.forClassWithGenerics(Map.class,
|
||||
ResolvableType.forClass(String.class), STRING_INTEGER_MAP.getType());
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(Map.class,
|
||||
ResolvableType.forClass(String.class), nestedType);
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.nested.bar.baz", "1");
|
||||
source.put("foo.nested.bar.bin", "2");
|
||||
source.put("foo.nested.far.baz", "3");
|
||||
source.put("foo.nested.far.bin", "4");
|
||||
source.put("faf.nested.far.bin", "x");
|
||||
this.sources.add(source);
|
||||
Bindable<Map<String, Map<String, Map<String, Integer>>>> target = Bindable
|
||||
.of(type);
|
||||
Map<String, Map<String, Map<String, Integer>>> result = this.binder
|
||||
.bind("foo", target).get();
|
||||
Map<String, Map<String, Integer>> nested = result.get("nested");
|
||||
assertThat(nested).hasSize(2);
|
||||
assertThat(nested.get("bar")).containsEntry("baz", 1).containsEntry("bin", 2);
|
||||
assertThat(nested.get("far")).containsEntry("baz", 3).containsEntry("bin", 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void bindToMapWhenMapValueIsObjectShouldBindNestedMapValue() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.nested.bar.baz", "1");
|
||||
source.put("foo.nested.bar.bin", "2");
|
||||
source.put("foo.nested.far.baz", "3");
|
||||
source.put("foo.nested.far.bin", "4");
|
||||
source.put("faf.nested.far.bin", "x");
|
||||
this.sources.add(source);
|
||||
Map<String, Object> result = this.binder
|
||||
.bind("foo", Bindable.mapOf(String.class, Object.class)).get();
|
||||
Map<String, Object> nested = (Map<String, Object>) result.get("nested");
|
||||
assertThat(nested).hasSize(2);
|
||||
Map<String, Object> bar = (Map<String, Object>) nested.get("bar");
|
||||
assertThat(bar).containsEntry("baz", "1").containsEntry("bin", "2");
|
||||
Map<String, Object> far = (Map<String, Object>) nested.get("far");
|
||||
assertThat(far).containsEntry("baz", "3").containsEntry("bin", "4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapWhenMapValueIsObjectAndNoRootShouldBindNestedMapValue()
|
||||
throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("commit.id", "abcdefg");
|
||||
source.put("branch", "master");
|
||||
source.put("foo", "bar");
|
||||
this.sources.add(source);
|
||||
Map<String, Object> result = this.binder
|
||||
.bind("", Bindable.mapOf(String.class, Object.class)).get();
|
||||
assertThat(result.get("commit"))
|
||||
.isEqualTo(Collections.singletonMap("id", "abcdefg"));
|
||||
assertThat(result.get("branch")).isEqualTo("master");
|
||||
assertThat(result.get("foo")).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapWhenEmptyRootNameShouldBindMap() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("bar.baz", "1");
|
||||
source.put("bar.bin", "2");
|
||||
this.sources.add(source);
|
||||
Map<String, Integer> result = this.binder.bind("", STRING_INTEGER_MAP).get();
|
||||
assertThat(result).hasSize(2);
|
||||
assertThat(result).containsEntry("bar.baz", 1).containsEntry("bar.bin", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapWhenMultipleCandidateShouldBindFirst() throws Exception {
|
||||
MockConfigurationPropertySource source1 = new MockConfigurationPropertySource();
|
||||
source1.put("foo.bar", "1");
|
||||
source1.put("foo.baz", "2");
|
||||
this.sources.add(source1);
|
||||
MockConfigurationPropertySource source2 = new MockConfigurationPropertySource();
|
||||
source2.put("foo.baz", "3");
|
||||
source2.put("foo.bin", "4");
|
||||
this.sources.add(source2);
|
||||
Map<String, Integer> result = this.binder.bind("foo", STRING_INTEGER_MAP).get();
|
||||
assertThat(result).hasSize(3);
|
||||
assertThat(result).containsEntry("bar", 1);
|
||||
assertThat(result).containsEntry("baz", 2);
|
||||
assertThat(result).containsEntry("bin", 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapWhenMultipleInSameSourceCandidateShouldBindFirst()
|
||||
throws Exception {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("foo.bar", "1");
|
||||
map.put("foo.b-az", "2");
|
||||
map.put("foo.ba-z", "3");
|
||||
map.put("foo.bin", "4");
|
||||
MapConfigurationPropertySource propertySource = new MapConfigurationPropertySource(
|
||||
map);
|
||||
this.sources.add(propertySource);
|
||||
Map<String, Integer> result = this.binder.bind("foo", STRING_INTEGER_MAP).get();
|
||||
assertThat(result).hasSize(4);
|
||||
assertThat(result).containsEntry("bar", 1);
|
||||
assertThat(result).containsEntry("b-az", 2);
|
||||
assertThat(result).containsEntry("ba-z", 3);
|
||||
assertThat(result).containsEntry("bin", 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapWhenHasExistingMapShouldReplaceOnlyNewContents()
|
||||
throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo.bar", "1"));
|
||||
Map<String, Integer> existing = new HashMap<>();
|
||||
existing.put("bar", 1000);
|
||||
existing.put("baz", 1001);
|
||||
Bindable<Map<String, Integer>> target = STRING_INTEGER_MAP
|
||||
.withExistingValue(existing);
|
||||
Map<String, Integer> result = this.binder.bind("foo", target).get();
|
||||
assertThat(result).isExactlyInstanceOf(HashMap.class);
|
||||
assertThat(result).isSameAs(existing);
|
||||
assertThat(result).hasSize(2);
|
||||
assertThat(result).containsEntry("bar", 1);
|
||||
assertThat(result).containsEntry("baz", 1001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapShouldRespectMapType() throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo.bar", "1"));
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(HashMap.class,
|
||||
String.class, Integer.class);
|
||||
Object defaultMap = this.binder.bind("foo", STRING_INTEGER_MAP).get();
|
||||
Object customMap = this.binder.bind("foo", Bindable.of(type)).get();
|
||||
assertThat(customMap).isExactlyInstanceOf(HashMap.class)
|
||||
.isNotInstanceOf(defaultMap.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapWhenNoValueShouldReturnUnbound() throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("faf.bar", "1"));
|
||||
BindResult<Map<String, Integer>> result = this.binder.bind("foo",
|
||||
STRING_INTEGER_MAP);
|
||||
assertThat(result.isBound()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapShouldConvertKey() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo[0]", "1");
|
||||
source.put("foo[1]", "2");
|
||||
source.put("foo[9]", "3");
|
||||
this.sources.add(source);
|
||||
Map<Integer, Integer> result = this.binder.bind("foo", INTEGER_INTEGER_MAP).get();
|
||||
assertThat(result).hasSize(3);
|
||||
assertThat(result).containsEntry(0, 1);
|
||||
assertThat(result).containsEntry(1, 2);
|
||||
assertThat(result).containsEntry(9, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapShouldBeGreedyForStrings() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.aaa.bbb.ccc", "b");
|
||||
source.put("foo.bbb.ccc.ddd", "a");
|
||||
source.put("foo.ccc.ddd.eee", "r");
|
||||
this.sources.add(source);
|
||||
Map<String, String> result = this.binder.bind("foo", STRING_STRING_MAP).get();
|
||||
assertThat(result).hasSize(3);
|
||||
assertThat(result).containsEntry("aaa.bbb.ccc", "b");
|
||||
assertThat(result).containsEntry("bbb.ccc.ddd", "a");
|
||||
assertThat(result).containsEntry("ccc.ddd.eee", "r");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapShouldBeGreedyForScalars() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.aaa.bbb.ccc", "foo-bar");
|
||||
source.put("foo.bbb.ccc.ddd", "BAR_BAZ");
|
||||
source.put("foo.ccc.ddd.eee", "bazboo");
|
||||
this.sources.add(source);
|
||||
Map<String, ExampleEnum> result = this.binder
|
||||
.bind("foo", Bindable.mapOf(String.class, ExampleEnum.class)).get();
|
||||
assertThat(result).hasSize(3);
|
||||
assertThat(result).containsEntry("aaa.bbb.ccc", ExampleEnum.FOO_BAR);
|
||||
assertThat(result).containsEntry("bbb.ccc.ddd", ExampleEnum.BAR_BAZ);
|
||||
assertThat(result).containsEntry("ccc.ddd.eee", ExampleEnum.BAZ_BOO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapWithPlaceholdersShouldBeGreedyForScalars() throws Exception {
|
||||
StandardEnvironment environment = new StandardEnvironment();
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment, "foo=boo");
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource(
|
||||
"foo.aaa.bbb.ccc", "baz-${foo}");
|
||||
this.sources.add(source);
|
||||
this.binder = new Binder(this.sources,
|
||||
new PropertySourcesPlaceholdersResolver(environment));
|
||||
Map<String, ExampleEnum> result = this.binder
|
||||
.bind("foo", Bindable.mapOf(String.class, ExampleEnum.class)).get();
|
||||
assertThat(result).containsEntry("aaa.bbb.ccc", ExampleEnum.BAZ_BOO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapWithNoPropertiesShouldReturnUnbound() throws Exception {
|
||||
this.binder = new Binder(this.sources);
|
||||
BindResult<Map<String, ExampleEnum>> result = this.binder.bind("foo",
|
||||
Bindable.mapOf(String.class, ExampleEnum.class));
|
||||
assertThat(result.isBound()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapShouldTriggerOnSuccess() throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo.bar", "1", "line1"));
|
||||
BindHandler handler = mock(BindHandler.class,
|
||||
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
|
||||
Bindable<Map<String, Integer>> target = STRING_INTEGER_MAP;
|
||||
this.binder.bind("foo", target, handler);
|
||||
InOrder inOrder = inOrder(handler);
|
||||
inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo.bar")),
|
||||
eq(Bindable.of(Integer.class)), any(), eq(1));
|
||||
inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo")),
|
||||
eq(target), any(), isA(Map.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link BinderConversionService}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class BinderConversionServiceTests {
|
||||
|
||||
private ConversionService delegate;
|
||||
|
||||
private BinderConversionService service;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.delegate = mock(ConversionService.class);
|
||||
this.service = new BinderConversionService(this.delegate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createConversionServiceShouldAcceptNullConversionService()
|
||||
throws Exception {
|
||||
BinderConversionService service = new BinderConversionService(null);
|
||||
assertThat(service.canConvert(String.class, TestEnum.class)).isTrue();
|
||||
assertThat(service.canConvert(TypeDescriptor.valueOf(String.class),
|
||||
TypeDescriptor.valueOf(TestEnum.class))).isTrue();
|
||||
assertThat(service.convert("ONE", TestEnum.class)).isEqualTo(TestEnum.ONE);
|
||||
assertThat(service.convert("ONE", TypeDescriptor.valueOf(String.class),
|
||||
TypeDescriptor.valueOf(TestEnum.class))).isEqualTo(TestEnum.ONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canConvertShouldDelegateToConversionService() throws Exception {
|
||||
Class<String> from = String.class;
|
||||
Class<InputStream> to = InputStream.class;
|
||||
given(this.delegate.canConvert(from, to)).willReturn(true);
|
||||
assertThat(this.service.canConvert(from, to)).isEqualTo(true);
|
||||
verify(this.delegate).canConvert(from, to);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canConvertTypeDescriptorShouldDelegateToConversionService()
|
||||
throws Exception {
|
||||
TypeDescriptor from = TypeDescriptor.valueOf(String.class);
|
||||
TypeDescriptor to = TypeDescriptor.valueOf(InputStream.class);
|
||||
given(this.delegate.canConvert(from, to)).willReturn(true);
|
||||
assertThat(this.service.canConvert(from, to)).isEqualTo(true);
|
||||
verify(this.delegate).canConvert(from, to);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertShouldDelegateToConversionService() throws Exception {
|
||||
String from = "foo";
|
||||
InputStream to = mock(InputStream.class);
|
||||
given(this.delegate.convert(from, InputStream.class)).willReturn(to);
|
||||
assertThat(this.service.convert(from, InputStream.class)).isEqualTo(to);
|
||||
verify(this.delegate).convert(from, InputStream.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertTargetTypeShouldDelegateToConversionService() throws Exception {
|
||||
String from = "foo";
|
||||
InputStream to = mock(InputStream.class);
|
||||
TypeDescriptor fromType = TypeDescriptor.valueOf(String.class);
|
||||
TypeDescriptor toType = TypeDescriptor.valueOf(InputStream.class);
|
||||
given(this.delegate.convert(from, fromType, toType)).willReturn(to);
|
||||
assertThat(this.service.convert(from, fromType, toType)).isEqualTo(to);
|
||||
verify(this.delegate).convert(from, fromType, toType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertShouldSwallowDelegateConversionFailedException() throws Exception {
|
||||
given(this.delegate.convert("one", TestEnum.class))
|
||||
.willThrow(new ConversionFailedException(null, null, null, null));
|
||||
assertThat(this.service.convert("one", TestEnum.class)).isEqualTo(TestEnum.ONE);
|
||||
verify(this.delegate).convert("one", TestEnum.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversionServiceShouldSupportEnums() throws Exception {
|
||||
this.service = new BinderConversionService(null);
|
||||
assertThat(this.service.canConvert(String.class, TestEnum.class)).isTrue();
|
||||
assertThat(this.service.convert("one", TestEnum.class)).isEqualTo(TestEnum.ONE);
|
||||
assertThat(this.service.convert("t-w-o", TestEnum.class)).isEqualTo(TestEnum.TWO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversionServiceShouldSupportStringToCharArray() throws Exception {
|
||||
this.service = new BinderConversionService(null);
|
||||
assertThat(this.service.canConvert(String.class, char[].class)).isTrue();
|
||||
assertThat(this.service.convert("test", char[].class)).containsExactly('t', 'e',
|
||||
's', 't');
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversionServiceShouldSupportStringToInetAddress() throws Exception {
|
||||
this.service = new BinderConversionService(null);
|
||||
assertThat(this.service.canConvert(String.class, InetAddress.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversionServiceShouldSupportInetAddressToString() throws Exception {
|
||||
this.service = new BinderConversionService(null);
|
||||
assertThat(this.service.canConvert(InetAddress.class, String.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversionServiceShouldSupportStringToResource() throws Exception {
|
||||
this.service = new BinderConversionService(null);
|
||||
Resource resource = this.service.convert(
|
||||
"org/springframework/boot/context/properties/bind/convert/resource.txt",
|
||||
Resource.class);
|
||||
assertThat(resource).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversionServiceShouldSupportStringToClass() throws Exception {
|
||||
this.service = new BinderConversionService(null);
|
||||
Class<?> converted = this.service.convert(InputStream.class.getName(),
|
||||
Class.class);
|
||||
assertThat(converted).isEqualTo(InputStream.class);
|
||||
}
|
||||
|
||||
enum TestEnum {
|
||||
|
||||
ONE, TWO
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.SimpleTypeConverter;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertyEditorConverter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class PropertyEditorConverterTests {
|
||||
|
||||
private final PropertyEditorConverter converter = new PropertyEditorConverter();
|
||||
|
||||
@Test
|
||||
public void matchesShouldLimitToPropertyEditor() {
|
||||
String converted = new SimpleTypeConverter().convertIfNecessary(123,
|
||||
String.class);
|
||||
assertThat(converted).isEqualTo("123");
|
||||
// Even though the SimpleTypeConverter can convert, we should limit to just
|
||||
// PropertyEditors not implicit support
|
||||
assertThat(this.converter.matches(TypeDescriptor.valueOf(Integer.class),
|
||||
TypeDescriptor.valueOf(String.class))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertShouldSupportConventionBasedEditors() throws Exception {
|
||||
String source = "org/springframework/boot/context/properties/bind/convert/resource.txt";
|
||||
TypeDescriptor sourceType = TypeDescriptor.forObject(source);
|
||||
TypeDescriptor targetType = TypeDescriptor.valueOf(Resource.class);
|
||||
assertThat(this.converter.matches(sourceType, targetType)).isTrue();
|
||||
Object converted = this.converter.convert(source, sourceType, targetType);
|
||||
assertThat(converted).isNotNull().isInstanceOf(Resource.class);
|
||||
assertThat(converted.toString()).endsWith("resource.txt]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertShouldSupportDefaultEditors() throws Exception {
|
||||
String source = "en_UK";
|
||||
TypeDescriptor sourceType = TypeDescriptor.forObject(source);
|
||||
TypeDescriptor targetType = TypeDescriptor.valueOf(Locale.class);
|
||||
assertThat(this.converter.matches(sourceType, targetType)).isTrue();
|
||||
Object converted = this.converter.convert(source, sourceType, targetType);
|
||||
assertThat(converted).isNotNull().isInstanceOf(Locale.class);
|
||||
assertThat(converted.toString()).endsWith("en_UK");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchShouldNotMatchCollection() throws Exception {
|
||||
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
|
||||
assertThat(this.converter.matches(sourceType,
|
||||
TypeDescriptor.valueOf(Collection.class))).isFalse();
|
||||
assertThat(this.converter.matches(sourceType, TypeDescriptor.valueOf(List.class)))
|
||||
.isFalse();
|
||||
assertThat(this.converter.matches(sourceType, TypeDescriptor.valueOf(Set.class)))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchShouldNotMatchMap() throws Exception {
|
||||
TypeDescriptor sourceType = TypeDescriptor.valueOf(String.class);
|
||||
assertThat(this.converter.matches(sourceType, TypeDescriptor.valueOf(Map.class)))
|
||||
.isFalse();
|
||||
assertThat(this.converter.matches(sourceType,
|
||||
TypeDescriptor.valueOf(SortedMap.class))).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResolvableTypeDescriptor}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ResolvableTypeDescriptorTests {
|
||||
|
||||
@Test
|
||||
public void forBindableShouldIncludeType() throws Exception {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(List.class,
|
||||
String.class);
|
||||
Bindable<?> bindable = Bindable.of(type);
|
||||
TypeDescriptor descriptor = ResolvableTypeDescriptor.forBindable(bindable);
|
||||
assertThat(descriptor.getResolvableType()).isEqualTo(type);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forBindableShouldIncludeAnnotations() throws Exception {
|
||||
Annotation annotation = AnnotationUtils.synthesizeAnnotation(Test.class);
|
||||
Bindable<?> bindable = Bindable.of(String.class).withAnnotations(annotation);
|
||||
TypeDescriptor descriptor = ResolvableTypeDescriptor.forBindable(bindable);
|
||||
assertThat(descriptor.getAnnotations()).containsExactly(annotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forTypeShouldIncludeType() throws Exception {
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(List.class,
|
||||
String.class);
|
||||
TypeDescriptor descriptor = ResolvableTypeDescriptor.forType(type);
|
||||
assertThat(descriptor.getResolvableType()).isEqualTo(type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 org.springframework.core.convert.converter.Converter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link StringToEnumConverterFactory}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class StringToEnumConverterFactoryTests {
|
||||
|
||||
private StringToEnumConverterFactory factory = new StringToEnumConverterFactory();
|
||||
|
||||
@Test
|
||||
public void getConverterShouldReturnConverter() {
|
||||
Converter<String, TestEnum> converter = this.factory.getConverter(TestEnum.class);
|
||||
assertThat(converter).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void getConverterWhenEnumSubclassShouldReturnConverter() throws Exception {
|
||||
Converter<String, TestEnum> converter = this.factory
|
||||
.getConverter((Class) TestSubclassEnum.ONE.getClass());
|
||||
assertThat(converter).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenExactMatchShouldConvertValue() throws Exception {
|
||||
Converter<String, TestEnum> converter = this.factory.getConverter(TestEnum.class);
|
||||
assertThat(converter.convert("")).isNull();
|
||||
assertThat(converter.convert("ONE")).isEqualTo(TestEnum.ONE);
|
||||
assertThat(converter.convert("TWO")).isEqualTo(TestEnum.TWO);
|
||||
assertThat(converter.convert("THREE_AND_FOUR"))
|
||||
.isEqualTo(TestEnum.THREE_AND_FOUR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenFuzzyMatchShouldConvertValue() throws Exception {
|
||||
Converter<String, TestEnum> converter = this.factory.getConverter(TestEnum.class);
|
||||
assertThat(converter.convert("")).isNull();
|
||||
assertThat(converter.convert("one")).isEqualTo(TestEnum.ONE);
|
||||
assertThat(converter.convert("tWo")).isEqualTo(TestEnum.TWO);
|
||||
assertThat(converter.convert("three_and_four"))
|
||||
.isEqualTo(TestEnum.THREE_AND_FOUR);
|
||||
assertThat(converter.convert("threeandfour")).isEqualTo(TestEnum.THREE_AND_FOUR);
|
||||
assertThat(converter.convert("three-and-four"))
|
||||
.isEqualTo(TestEnum.THREE_AND_FOUR);
|
||||
assertThat(converter.convert("threeAndFour")).isEqualTo(TestEnum.THREE_AND_FOUR);
|
||||
}
|
||||
|
||||
enum TestEnum {
|
||||
|
||||
ONE, TWO, THREE_AND_FOUR
|
||||
|
||||
}
|
||||
|
||||
enum TestSubclassEnum {
|
||||
|
||||
ONE {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 StringToInetAddressConverter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class StringToInetAddressConverterTests extends AbstractInetAddressTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private StringToInetAddressConverter converter = new StringToInetAddressConverter();
|
||||
|
||||
@Test
|
||||
public void convertWhenHostDoesNotExistShouldThrowException() {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Unknown host");
|
||||
this.converter.convert("ireallydontexist.example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertWhenHostExistsShouldConvert() throws Exception {
|
||||
assumeResolves("example.com");
|
||||
InetAddress converted = this.converter.convert("example.com");
|
||||
assertThat(converted.toString()).startsWith("example.com");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.BindException;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
|
||||
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link IgnoreErrorsBindHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class IgnoreErrorsBindHandlerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private List<ConfigurationPropertySource> sources = new ArrayList<>();
|
||||
|
||||
private Binder binder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("example.foo", "bar");
|
||||
this.sources.add(source);
|
||||
this.binder = new Binder(this.sources);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWhenNotIgnoringErrorsShouldFail() throws Exception {
|
||||
this.thrown.expect(BindException.class);
|
||||
this.binder.bind("example", Bindable.of(Example.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWhenIgnoringErrorsShouldBind() throws Exception {
|
||||
Example bound = this.binder.bind("example", Bindable.of(Example.class),
|
||||
new IgnoreErrorsBindHandler()).get();
|
||||
assertThat(bound.getFoo()).isEqualTo(0);
|
||||
}
|
||||
|
||||
public static class Example {
|
||||
|
||||
private int foo;
|
||||
|
||||
public int getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(int foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
|
||||
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link IgnoreNestedPropertiesBindHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class IgnoreNestedPropertiesBindHandlerTests {
|
||||
|
||||
private List<ConfigurationPropertySource> sources = new ArrayList<>();
|
||||
|
||||
private Binder binder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("example.foo", "foovalue");
|
||||
source.put("example.nested.bar", "barvalue");
|
||||
this.sources.add(source);
|
||||
this.binder = new Binder(this.sources);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWhenNotIngoringNestedShouldBindAll() throws Exception {
|
||||
Example bound = this.binder.bind("example", Bindable.of(Example.class)).get();
|
||||
assertThat(bound.getFoo()).isEqualTo("foovalue");
|
||||
assertThat(bound.getNested().getBar()).isEqualTo("barvalue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWhenIngoringNestedShouldFilterNested() throws Exception {
|
||||
Example bound = this.binder.bind("example", Bindable.of(Example.class),
|
||||
new IgnoreNestedPropertiesBindHandler()).get();
|
||||
assertThat(bound.getFoo()).isEqualTo("foovalue");
|
||||
assertThat(bound.getNested()).isNull();
|
||||
}
|
||||
|
||||
public static class Example {
|
||||
|
||||
private String foo;
|
||||
|
||||
private ExampleNested nested;
|
||||
|
||||
public String getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
public ExampleNested getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
public void setNested(ExampleNested nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleNested {
|
||||
|
||||
private String bar;
|
||||
|
||||
public String getBar() {
|
||||
return this.bar;
|
||||
}
|
||||
|
||||
public void setBar(String bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.BindException;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* Tests for {@link NoUnboundElementsBindHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class NoUnboundElementsBindHandlerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private List<ConfigurationPropertySource> sources = new ArrayList<>();
|
||||
|
||||
private Binder binder;
|
||||
|
||||
@Test
|
||||
public void bindWhenNotUsingNoUnboundElementsHandlerShouldBind() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("example.foo", "bar");
|
||||
source.put("example.baz", "bar");
|
||||
this.sources.add(source);
|
||||
this.binder = new Binder(this.sources);
|
||||
Example bound = this.binder
|
||||
.bind(ConfigurationPropertyName.of("example"), Bindable.of(Example.class))
|
||||
.get();
|
||||
assertThat(bound.getFoo()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWhenUsingNoUnboundElementsHandlerShouldBind() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("example.foo", "bar");
|
||||
this.sources.add(source);
|
||||
this.binder = new Binder(this.sources);
|
||||
Example bound = this.binder.bind("example", Bindable.of(Example.class),
|
||||
new NoUnboundElementsBindHandler()).get();
|
||||
assertThat(bound.getFoo()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWhenUsingNoUnboundElementsHandlerThrowException() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("example.foo", "bar");
|
||||
source.put("example.baz", "bar");
|
||||
this.sources.add(source);
|
||||
this.binder = new Binder(this.sources);
|
||||
try {
|
||||
this.binder.bind("example", Bindable.of(Example.class),
|
||||
new NoUnboundElementsBindHandler());
|
||||
fail("did not throw");
|
||||
}
|
||||
catch (BindException ex) {
|
||||
assertThat(ex.getCause().getMessage())
|
||||
.contains("The elements [example.baz] were left unbound");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWhenUsingNoUnboundElementsHandlerShouldBindIfPrefixDifferent()
|
||||
throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("example.foo", "bar");
|
||||
source.put("other.baz", "bar");
|
||||
this.sources.add(source);
|
||||
this.binder = new Binder(this.sources);
|
||||
Example bound = this.binder.bind("example", Bindable.of(Example.class),
|
||||
new NoUnboundElementsBindHandler()).get();
|
||||
assertThat(bound.getFoo()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
public static class Example {
|
||||
|
||||
private String foo;
|
||||
|
||||
public String getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Binder} using package private Java beans.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class PackagePrivateBeanBindingTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private List<ConfigurationPropertySource> sources = new ArrayList<>();
|
||||
|
||||
private Binder binder;
|
||||
|
||||
private ConfigurationPropertyName name;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.binder = new Binder(this.sources);
|
||||
this.name = ConfigurationPropertyName.of("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToPackagePrivateClassShouldBindToInstance() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.bar", "999");
|
||||
this.sources.add(source);
|
||||
ExamplePackagePrivateBean bean = this.binder
|
||||
.bind(this.name, Bindable.of(ExamplePackagePrivateBean.class)).get();
|
||||
assertThat(bean.getBar()).isEqualTo(999);
|
||||
}
|
||||
|
||||
static class ExamplePackagePrivateBean {
|
||||
|
||||
private int bar;
|
||||
|
||||
public int getBar() {
|
||||
return this.bar;
|
||||
}
|
||||
|
||||
public void setBar(int bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link BindValidationException}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class BindValidationExceptionTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void createWhenValidationErrorsIsNullShouldThrowException() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("ValidationErrors must not be null");
|
||||
new BindValidationException(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValidationErrorsShouldReturnValidationErrors() throws Exception {
|
||||
ValidationErrors errors = mock(ValidationErrors.class);
|
||||
BindValidationException exception = new BindValidationException(errors);
|
||||
assertThat(exception.getValidationErrors()).isEqualTo(errors);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.junit.Test;
|
||||
|
||||
import org.springframework.boot.origin.MockOrigin;
|
||||
import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.validation.FieldError;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link OriginTrackedFieldError}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class OriginTrackedFieldErrorTests {
|
||||
|
||||
private static final FieldError FIELD_ERROR = new FieldError("foo", "bar", "faf");
|
||||
|
||||
private static final Origin ORIGIN = MockOrigin.of("afile");
|
||||
|
||||
@Test
|
||||
public void ofWhenFieldErrorIsNullShouldReturnNull() throws Exception {
|
||||
assertThat(OriginTrackedFieldError.of(null, ORIGIN)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ofWhenOriginIsNullShouldReturnFieldErrorWithoutOrigin() throws Exception {
|
||||
assertThat(OriginTrackedFieldError.of(FIELD_ERROR, null)).isSameAs(FIELD_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ofShouldReturnOriginCapableFieldError() throws Exception {
|
||||
FieldError fieldError = OriginTrackedFieldError.of(FIELD_ERROR, ORIGIN);
|
||||
assertThat(fieldError.getObjectName()).isEqualTo("foo");
|
||||
assertThat(fieldError.getField()).isEqualTo("bar");
|
||||
assertThat(Origin.from(fieldError)).isEqualTo(ORIGIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringShouldAddOrigin() throws Exception {
|
||||
assertThat(OriginTrackedFieldError.of(FIELD_ERROR, ORIGIN).toString()).isEqualTo(
|
||||
"Field error in object 'foo' on field 'bar': rejected value [null]"
|
||||
+ "; codes []; arguments []; default message [faf]; origin afile");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* 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.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.BindException;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationProperty;
|
||||
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.boot.origin.Origin;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
|
||||
/**
|
||||
* Tests for {@link ValidationBindHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class ValidationBindHandlerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private List<ConfigurationPropertySource> sources = new ArrayList<>();
|
||||
|
||||
private ValidationBindHandler handler;
|
||||
|
||||
private Binder binder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.binder = new Binder(this.sources);
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
this.handler = new ValidationBindHandler(validator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindShouldBindWithoutHandler() {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo.age", 4));
|
||||
ExampleValidatedBean bean = this.binder
|
||||
.bind("foo", Bindable.of(ExampleValidatedBean.class)).get();
|
||||
assertThat(bean.getAge()).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindShouldFailWithHandler() {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo.age", 4));
|
||||
this.thrown.expect(BindException.class);
|
||||
this.thrown.expectCause(instanceOf(BindValidationException.class));
|
||||
this.binder.bind("foo", Bindable.of(ExampleValidatedBean.class), this.handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindShouldValidateNestedProperties() throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo.nested.age", 4));
|
||||
this.thrown.expect(BindException.class);
|
||||
this.thrown.expectCause(instanceOf(BindValidationException.class));
|
||||
this.binder.bind("foo", Bindable.of(ExampleValidatedWithNestedBean.class),
|
||||
this.handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindShouldFailWithAccessToOrigin() {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo.age", 4, "file"));
|
||||
BindValidationException cause = bindAndExpectValidationError(
|
||||
() -> this.binder.bind(ConfigurationPropertyName.of("foo"),
|
||||
Bindable.of(ExampleValidatedBean.class), this.handler));
|
||||
ObjectError objectError = cause.getValidationErrors().getAllErrors().get(0);
|
||||
assertThat(Origin.from(objectError).toString()).isEqualTo("file");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindShouldFailWithAccessToBoundProperties() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
source.put("foo.nested.name", "baz");
|
||||
source.put("foo.nested.age", "4");
|
||||
source.put("faf.bar", "baz");
|
||||
this.sources.add(source);
|
||||
BindValidationException cause = bindAndExpectValidationError(
|
||||
() -> this.binder.bind(ConfigurationPropertyName.of("foo"),
|
||||
Bindable.of(ExampleValidatedWithNestedBean.class), this.handler));
|
||||
Set<ConfigurationProperty> boundProperties = cause.getValidationErrors()
|
||||
.getBoundProperties();
|
||||
assertThat(boundProperties).extracting((p) -> p.getName().toString())
|
||||
.contains("foo.nested.age", "foo.nested.name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindShouldFailWithAccessToName() throws Exception {
|
||||
this.sources.add(new MockConfigurationPropertySource("foo.nested.age", "4"));
|
||||
BindValidationException cause = bindAndExpectValidationError(
|
||||
() -> this.binder.bind(ConfigurationPropertyName.of("foo"),
|
||||
Bindable.of(ExampleValidatedWithNestedBean.class), this.handler));
|
||||
assertThat(cause.getValidationErrors().getName().toString())
|
||||
.isEqualTo("foo.nested");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindShouldFailIfExistingValueIsInvalid() throws Exception {
|
||||
ExampleValidatedBean existingValue = new ExampleValidatedBean();
|
||||
BindValidationException cause = bindAndExpectValidationError(
|
||||
() -> this.binder.bind(ConfigurationPropertyName.of("foo"), Bindable
|
||||
.of(ExampleValidatedBean.class).withExistingValue(existingValue),
|
||||
this.handler));
|
||||
FieldError fieldError = (FieldError) cause.getValidationErrors().getAllErrors()
|
||||
.get(0);
|
||||
assertThat(fieldError.getField()).isEqualTo("age");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindShouldNotValidateWithoutAnnotation() throws Exception {
|
||||
ExampleNonValidatedBean existingValue = new ExampleNonValidatedBean();
|
||||
this.binder.bind(ConfigurationPropertyName.of("foo"), Bindable
|
||||
.of(ExampleNonValidatedBean.class).withExistingValue(existingValue),
|
||||
this.handler);
|
||||
}
|
||||
|
||||
private BindValidationException bindAndExpectValidationError(Runnable action) {
|
||||
try {
|
||||
action.run();
|
||||
}
|
||||
catch (BindException ex) {
|
||||
ex.printStackTrace();
|
||||
|
||||
BindValidationException cause = (BindValidationException) ex.getCause();
|
||||
return cause;
|
||||
}
|
||||
throw new IllegalStateException("Did not throw");
|
||||
}
|
||||
|
||||
public static class ExampleNonValidatedBean {
|
||||
|
||||
@Min(5)
|
||||
private int age;
|
||||
|
||||
public int getAge() {
|
||||
return this.age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Validated
|
||||
public static class ExampleValidatedBean {
|
||||
|
||||
@Min(5)
|
||||
private int age;
|
||||
|
||||
public int getAge() {
|
||||
return this.age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Validated
|
||||
public static class ExampleValidatedWithNestedBean {
|
||||
|
||||
@Valid
|
||||
private ExampleNested nested = new ExampleNested();
|
||||
|
||||
public ExampleNested getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
public void setNested(ExampleNested nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ExampleNested {
|
||||
|
||||
private String name;
|
||||
|
||||
@Min(5)
|
||||
private int age;
|
||||
|
||||
@NotNull
|
||||
private String address;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return this.age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.context.properties.source.ConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.boot.origin.MockOrigin;
|
||||
import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ValidationErrors}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class ValidationErrorsTests {
|
||||
|
||||
private static final ConfigurationPropertyName NAME = ConfigurationPropertyName
|
||||
.of("foo");
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void createWhenNameIsNullShouldThrowException() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Name must not be null");
|
||||
new ValidationErrors(null, Collections.emptySet(), Collections.emptyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWhenBoundPropertiesIsNullShouldThrowException() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("BoundProperties must not be null");
|
||||
new ValidationErrors(NAME, null, Collections.emptyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createWhenErrorsIsNullShouldThrowException() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Errors must not be null");
|
||||
new ValidationErrors(NAME, Collections.emptySet(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNameShouldReturnName() throws Exception {
|
||||
ConfigurationPropertyName name = NAME;
|
||||
ValidationErrors errors = new ValidationErrors(name, Collections.emptySet(),
|
||||
Collections.emptyList());
|
||||
assertThat((Object) errors.getName()).isEqualTo(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBoundPropertiesShouldReturnBoundProperties() throws Exception {
|
||||
Set<ConfigurationProperty> boundProperties = new LinkedHashSet<>();
|
||||
boundProperties.add(new ConfigurationProperty(NAME, "foo", null));
|
||||
ValidationErrors errors = new ValidationErrors(NAME, boundProperties,
|
||||
Collections.emptyList());
|
||||
assertThat(errors.getBoundProperties()).isEqualTo(boundProperties);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getErrorsShouldReturnErrors() throws Exception {
|
||||
List<ObjectError> allErrors = new ArrayList<>();
|
||||
allErrors.add(new ObjectError("foo", "bar"));
|
||||
ValidationErrors errors = new ValidationErrors(NAME, Collections.emptySet(),
|
||||
allErrors);
|
||||
assertThat(errors.getAllErrors()).isEqualTo(allErrors);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iteratorShouldIterateErrors() throws Exception {
|
||||
List<ObjectError> allErrors = new ArrayList<>();
|
||||
allErrors.add(new ObjectError("foo", "bar"));
|
||||
ValidationErrors errors = new ValidationErrors(NAME, Collections.emptySet(),
|
||||
allErrors);
|
||||
assertThat(errors.iterator()).containsExactlyElementsOf(allErrors);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getErrorsShouldAdaptFieldErrorsToBeOriginProviders() throws Exception {
|
||||
Set<ConfigurationProperty> boundProperties = new LinkedHashSet<>();
|
||||
ConfigurationPropertyName name1 = ConfigurationPropertyName.of("foo.bar");
|
||||
Origin origin1 = MockOrigin.of("line1");
|
||||
boundProperties.add(new ConfigurationProperty(name1, "boot", origin1));
|
||||
ConfigurationPropertyName name2 = ConfigurationPropertyName.of("foo.baz.bar");
|
||||
Origin origin2 = MockOrigin.of("line2");
|
||||
boundProperties.add(new ConfigurationProperty(name2, "boot", origin2));
|
||||
List<ObjectError> allErrors = new ArrayList<>();
|
||||
allErrors.add(new FieldError("objectname", "bar", "message"));
|
||||
ValidationErrors errors = new ValidationErrors(
|
||||
ConfigurationPropertyName.of("foo.baz"), boundProperties, allErrors);
|
||||
assertThat(Origin.from(errors.getAllErrors().get(0))).isEqualTo(origin2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
test
|
||||
Reference in New Issue
Block a user