/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.module.extension.internal.runtime.resolver;
import static com.google.common.collect.ImmutableList.copyOf;
import static java.lang.String.format;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.collections.CollectionUtils.intersection;
import static org.mule.metadata.api.utils.MetadataTypeUtils.getDefaultValue;
import static org.mule.metadata.api.utils.MetadataTypeUtils.getLocalPart;
import static org.mule.metadata.java.api.utils.JavaTypeUtils.getType;
import static org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage;
import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.initialiseIfNeeded;
import static org.mule.runtime.extension.api.util.ExtensionMetadataTypeUtils.isFlattenedParameterGroup;
import static org.mule.runtime.extension.api.util.NameUtils.getComponentModelTypeName;
import static org.mule.runtime.extension.api.util.NameUtils.getModelName;
import static org.mule.runtime.module.extension.internal.runtime.resolver.ResolverUtils.getExpressionBasedValueResolver;
import static org.mule.runtime.module.extension.internal.runtime.resolver.ResolverUtils.getFieldDefaultValueValueResolver;
import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.getContainerName;
import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.getFieldByNameOrAlias;
import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.getMemberName;
import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.getMetadataType;
import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.isParameterResolver;
import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.isTypedValue;
import static org.mule.runtime.module.extension.internal.util.MuleExtensionUtils.isNullSafe;
import org.mule.metadata.api.model.MetadataType;
import org.mule.metadata.api.model.ObjectType;
import org.mule.runtime.api.exception.MuleRuntimeException;
import org.mule.runtime.api.lifecycle.InitialisationException;
import org.mule.runtime.api.meta.model.ModelProperty;
import org.mule.runtime.api.meta.model.parameter.ExclusiveParametersModel;
import org.mule.runtime.api.meta.model.parameter.ParameterGroupModel;
import org.mule.runtime.api.meta.model.parameter.ParameterModel;
import org.mule.runtime.api.meta.model.parameter.ParameterizedModel;
import org.mule.runtime.api.metadata.DataType;
import org.mule.runtime.api.metadata.TypedValue;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.config.ConfigurationException;
import org.mule.runtime.core.util.collection.ImmutableListCollector;
import org.mule.runtime.extension.api.declaration.type.ExtensionsTypeLoaderFactory;
import org.mule.runtime.extension.api.declaration.type.annotation.ConfigOverrideTypeAnnotation;
import org.mule.runtime.extension.api.declaration.type.annotation.DefaultEncodingAnnotation;
import org.mule.runtime.extension.api.declaration.type.annotation.NullSafeTypeAnnotation;
import org.mule.runtime.extension.api.exception.IllegalModelDefinitionException;
import org.mule.runtime.extension.api.util.ExtensionMetadataTypeUtils;
import org.mule.runtime.module.extension.internal.loader.ParameterGroupDescriptor;
import org.mule.runtime.module.extension.internal.loader.java.property.DefaultEncodingModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.property.NullSafeModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.property.ParameterGroupModelProperty;
import org.mule.runtime.module.extension.internal.runtime.objectbuilder.DefaultObjectBuilder;
import com.google.common.base.Joiner;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
/**
* Contains behavior to obtain a ResolverSet for a set of parameters values and a {@link ParameterizedModel}.
*
* @since 4.0
*/
public final class ParametersResolver implements ObjectTypeParametersResolver {
private final MuleContext muleContext;
private final Map<String, ?> parameters;
private ParametersResolver(MuleContext muleContext, Map<String, ?> parameters) {
this.muleContext = muleContext;
this.parameters = parameters;
}
public static ParametersResolver fromValues(Map<String, ?> parameters, MuleContext muleContext) {
return new ParametersResolver(muleContext, parameters);
}
public static ParametersResolver fromDefaultValues(ParameterizedModel parameterizedModel,
MuleContext muleContext) {
Map<String, Object> parameterValues = new HashMap<>();
for (ParameterModel model : parameterizedModel.getAllParameterModels()) {
parameterValues.put(model.getName(), model.getDefaultValue());
}
return new ParametersResolver(muleContext, parameterValues);
}
/**
* Constructs a {@link ResolverSet} from the parameters, using {@link #toValueResolver(Object, Set)} to process the values.
*
* @return a {@link ResolverSet}
*/
public ResolverSet getParametersAsResolverSet(ParameterizedModel model, MuleContext muleContext) throws ConfigurationException {
List<ParameterGroupModel> inlineGroups = getInlineGroups(model);
ResolverSet resolverSet =
getParametersAsResolverSet(model, getFlatParameters(inlineGroups, model.getAllParameterModels()), muleContext);
for (ParameterGroupModel group : inlineGroups) {
getInlineGroupResolver(group, resolverSet, muleContext);
}
return resolverSet;
}
/**
* Constructs a {@link ResolverSet} from the parameters, using {@link #toValueResolver(Object, Set)} to process the values.
*
* @return a {@link ResolverSet}
*/
public ResolverSet getParametersAsHashedResolverSet(ParameterizedModel model, MuleContext muleContext)
throws ConfigurationException {
List<ParameterGroupModel> inlineGroups = getInlineGroups(model);
ResolverSet resolverSet =
getParametersAsHashedResolverSet(model, getFlatParameters(inlineGroups, model.getAllParameterModels()), muleContext);
for (ParameterGroupModel group : inlineGroups) {
getInlineGroupResolver(group, resolverSet, muleContext);
}
return resolverSet;
}
private void getInlineGroupResolver(ParameterGroupModel group, ResolverSet resolverSet, MuleContext muleContext) {
Optional<ParameterGroupDescriptor> descriptor = group.getModelProperty(ParameterGroupModelProperty.class)
.map(ParameterGroupModelProperty::getDescriptor);
String groupKey = descriptor
.map(d -> getContainerName(d.getContainer()))
.orElseGet(group::getName);
if (parameters.containsKey(groupKey)) {
resolverSet.add(groupKey, toValueResolver(parameters.get(groupKey), group.getModelProperties()));
} else if (descriptor.isPresent()) {
resolverSet.add(groupKey,
NullSafeValueResolverWrapper.of(new StaticValueResolver<>(null), descriptor.get().getMetadataType(),
muleContext, this));
}
}
public ResolverSet getParametersAsResolverSet(ParameterizedModel model, List<ParameterModel> parameterModels,
MuleContext muleContext)
throws ConfigurationException {
ResolverSet resolverSet = new ResolverSet(muleContext);
return getResolverSet(model, parameterModels, muleContext, resolverSet);
}
public ResolverSet getParametersAsHashedResolverSet(ParameterizedModel model, List<ParameterModel> parameterModels,
MuleContext muleContext)
throws ConfigurationException {
ResolverSet resolverSet = new HashedResolverSet(muleContext);
return getResolverSet(model, parameterModels, muleContext, resolverSet);
}
private ResolverSet getResolverSet(ParameterizedModel model, List<ParameterModel> parameterModels, MuleContext muleContext,
ResolverSet resolverSet)
throws ConfigurationException {
parameterModels.forEach(p -> {
final String parameterName = getMemberName(p, p.getName());
ValueResolver<?> resolver;
if (parameters.containsKey(parameterName)) {
resolver = toValueResolver(parameters.get(parameterName), p.getModelProperties());
} else {
resolver = getDefaultValueResolver(p.getModelProperty(DefaultEncodingModelProperty.class).isPresent(), () -> {
Object defaultValue = p.getDefaultValue();
if (defaultValue instanceof String) {
return getExpressionBasedValueResolver((String) defaultValue, p, muleContext);
} else if (defaultValue != null) {
return new StaticValueResolver<>(defaultValue);
}
return null;
});
}
if (isNullSafe(p)) {
ValueResolver<?> delegate = resolver != null ? resolver : new StaticValueResolver<>(null);
MetadataType type = p.getModelProperty(NullSafeModelProperty.class).get().defaultType();
resolver = NullSafeValueResolverWrapper.of(delegate, type, muleContext, this);
}
if (p.isOverrideFromConfig()) {
resolver = ConfigOverrideValueResolverWrapper.of(resolver != null ? resolver : new StaticValueResolver<>(null),
parameterName, muleContext);
}
if (resolver != null) {
resolverSet.add(parameterName, resolver);
} else if (p.isRequired()) {
throw new IllegalStateException(format("Parameter '%s' from the %s '%s' is required but is not set",
parameterName,
getComponentModelTypeName(model),
getModelName(model)));
}
});
checkParameterGroupExclusiveness(model, parameters.keySet());
return resolverSet;
}
private List<ParameterGroupModel> getInlineGroups(ParameterizedModel model) {
return model.getParameterGroupModels().stream()
.filter(ParameterGroupModel::isShowInDsl)
.collect(toList());
}
private List<ParameterModel> getFlatParameters(List<ParameterGroupModel> inlineGroups, List<ParameterModel> parameters) {
return parameters.stream()
.filter(p -> inlineGroups.stream().noneMatch(g -> g.getParameterModels().contains(p)))
.collect(toList());
}
/**
* {@inheritDoc}
*/
@Override
public void resolveParameterGroups(ObjectType objectType, DefaultObjectBuilder builder) {
Class<?> objectClass = getType(objectType);
objectType.getFields().stream()
.filter(ExtensionMetadataTypeUtils::isFlattenedParameterGroup)
.forEach(groupField -> {
if (!(groupField.getValue() instanceof ObjectType)) {
return;
}
final ObjectType groupType = (ObjectType) groupField.getValue();
final Field objectField = getField(objectClass, getLocalPart(groupField));
DefaultObjectBuilder groupBuilder = new DefaultObjectBuilder(getType(groupField.getValue()));
builder.addPropertyResolver(objectField.getName(), new ObjectBuilderValueResolver<>(groupBuilder, muleContext));
resolveParameters(groupType, groupBuilder);
resolveParameterGroups(groupType, groupBuilder);
});
}
/**
* {@inheritDoc}
*/
@Override
public void resolveParameters(ObjectType objectType, DefaultObjectBuilder builder) {
final Class<?> objectClass = getType(objectType);
final boolean isParameterGroup = isFlattenedParameterGroup(objectType);
objectType.getFields().forEach(field -> {
final String key = getLocalPart(field);
ValueResolver<?> valueResolver = null;
Field objectField = getField(objectClass, key);
if (parameters.containsKey(key)) {
valueResolver = toValueResolver(parameters.get(key));
} else if (!isParameterGroup) {
valueResolver = getDefaultValueResolver(field.getAnnotation(DefaultEncodingAnnotation.class).isPresent(),
() -> getDefaultValue(field).isPresent()
? getFieldDefaultValueValueResolver(field, muleContext)
: null);
}
Optional<NullSafeTypeAnnotation> nullSafe = field.getAnnotation(NullSafeTypeAnnotation.class);
if (nullSafe.isPresent()) {
ValueResolver<?> delegate = valueResolver != null ? valueResolver : new StaticValueResolver<>(null);
MetadataType type =
getMetadataType(nullSafe.get().getType(), ExtensionsTypeLoaderFactory.getDefault().createTypeLoader());
valueResolver = NullSafeValueResolverWrapper.of(delegate, type, muleContext, this);
}
if (field.getAnnotation(ConfigOverrideTypeAnnotation.class).isPresent()) {
valueResolver =
ConfigOverrideValueResolverWrapper.of(valueResolver != null ? valueResolver : new StaticValueResolver<>(null),
key, muleContext);
}
if (valueResolver != null) {
try {
initialiseIfNeeded(valueResolver, true, muleContext);
builder.addPropertyResolver(objectField.getName(), valueResolver);
} catch (InitialisationException e) {
throw new MuleRuntimeException(e);
}
} else if (field.isRequired() && !isFlattenedParameterGroup(field)) {
throw new IllegalStateException(format("The object '%s' requires the parameter '%s' but is not set",
objectClass.getSimpleName(), objectField.getName()));
}
});
}
private Field getField(Class<?> objectClass, String key) {
return getFieldByNameOrAlias(objectClass, key)
.orElseThrow(() -> new IllegalModelDefinitionException(format("Class '%s' does not contain field %s",
objectClass.getName(),
key)));
}
public void checkParameterGroupExclusiveness(ParameterizedModel model, Set<String> resolverKeys)
throws ConfigurationException {
for (ParameterGroupModel group : model.getParameterGroupModels()) {
for (ExclusiveParametersModel exclusiveModel : group.getExclusiveParametersModels()) {
Collection<String> definedExclusiveParameters = intersection(exclusiveModel.getExclusiveParameterNames(), resolverKeys);
if (definedExclusiveParameters.isEmpty() && exclusiveModel.isOneRequired()) {
throw new ConfigurationException((createStaticMessage(format(
"Parameter group '%s' requires that one of its optional parameters should be set but all of them are missing. "
+ "One of the following should be set: [%s]",
group.getName(),
Joiner.on(", ")
.join(exclusiveModel.getExclusiveParameterNames())))));
} else if (definedExclusiveParameters.size() > 1) {
throw new ConfigurationException(
createStaticMessage(format("In %s '%s', the following parameters cannot be set at the same time: [%s]",
getComponentModelTypeName(model), getModelName(model),
Joiner.on(", ").join(definedExclusiveParameters))));
}
}
}
}
/**
* Wraps the {@code value} into a {@link ValueResolver} of the proper type. For example, {@link Collection} and {@link Map}
* instances are exposed as {@link CollectionValueResolver} and {@link MapValueResolver} respectively.
* <p>
* If {@code value} is already a {@link ValueResolver} then it's returned as is.
* <p>
* Other values (including {@code null}) are wrapped in a {@link StaticValueResolver}.
*
* @param value the value to expose
* @return a {@link ValueResolver}
*/
private ValueResolver<?> toValueResolver(Object value) {
return toValueResolver(value, emptySet());
}
/**
* Wraps the {@code value} into a {@link ValueResolver} of the proper type. For example, {@link Collection} and {@link Map}
* instances are exposed as {@link CollectionValueResolver} and {@link MapValueResolver} respectively.
* <p>
* If {@code value} is already a {@link ValueResolver} then it's returned as is.
* <p>
* Other values (including {@code null}) are wrapped in a {@link StaticValueResolver}.
*
* @param value the value to expose
* @param modelProperties of the value's parameter
* @return a {@link ValueResolver}
*/
private ValueResolver<?> toValueResolver(Object value, Set<ModelProperty> modelProperties) {
ValueResolver<?> resolver;
if (value instanceof ValueResolver) {
resolver = (ValueResolver<?>) value;
} else if (value instanceof Collection) {
resolver = getCollectionResolver((Collection) value);
} else if (value instanceof Map) {
resolver = getMapResolver((Map<Object, Object>) value);
} else if (isParameterResolver(modelProperties)) {
resolver = new StaticValueResolver<>(new StaticParameterResolver<>(value));
} else if (isTypedValue(modelProperties)) {
resolver = new StaticValueResolver<>(new TypedValue<>(value, DataType.fromObject(value)));
} else {
resolver = new StaticValueResolver<>(value);
}
return resolver;
}
private ValueResolver<?> getMapResolver(Map<Object, Object> value) {
Map<ValueResolver<Object>, ValueResolver<Object>> normalizedMap = new LinkedHashMap<>(value.size());
value.forEach((key, entryValue) -> normalizedMap.put((ValueResolver<Object>) toValueResolver(key),
(ValueResolver<Object>) toValueResolver(entryValue)));
return MapValueResolver.of(value.getClass(), copyOf(normalizedMap.keySet()), copyOf(normalizedMap.values()), muleContext);
}
private ValueResolver<?> getCollectionResolver(Collection<?> collection) {
return CollectionValueResolver.of(collection.getClass(),
collection.stream().map(p -> toValueResolver(p)).collect(new ImmutableListCollector<>()));
}
/**
* Gets a {@link ValueResolver} for the parameter if it has an associated a default value or encoding.
*
* @param hasDefaultEncoding whether the parameter has to use runtime's default encoding or not
* @return {@link Supplier} for obtaining the the proper {@link ValueResolver} for the default value, {@code null} if there is
* no default.
*/
private ValueResolver<?> getDefaultValueResolver(boolean hasDefaultEncoding, Supplier<ValueResolver<?>> supplier) {
return hasDefaultEncoding ? new StaticValueResolver<>(muleContext.getConfiguration().getDefaultEncoding()) : supplier.get();
}
}