/*
* 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.metadata;
import static org.mule.metadata.api.utils.MetadataTypeUtils.isVoid;
import static org.mule.runtime.api.metadata.resolving.FailureCode.NO_DYNAMIC_TYPE_AVAILABLE;
import static org.mule.runtime.api.metadata.resolving.FailureCode.UNKNOWN;
import static org.mule.runtime.api.metadata.resolving.MetadataFailure.Builder.newFailure;
import static org.mule.runtime.api.metadata.resolving.MetadataResult.failure;
import static org.mule.runtime.api.metadata.resolving.MetadataResult.success;
import static org.mule.runtime.extension.api.util.ExtensionMetadataTypeUtils.getId;
import org.mule.metadata.api.model.ArrayType;
import org.mule.metadata.api.model.MetadataType;
import org.mule.metadata.message.MessageMetadataTypeBuilder;
import org.mule.runtime.api.message.Message;
import org.mule.runtime.api.meta.model.ComponentModel;
import org.mule.runtime.api.meta.model.OutputModel;
import org.mule.runtime.api.metadata.MetadataContext;
import org.mule.runtime.api.metadata.MetadataKey;
import org.mule.runtime.api.metadata.MetadataResolvingException;
import org.mule.runtime.api.metadata.descriptor.OutputMetadataDescriptor;
import org.mule.runtime.api.metadata.descriptor.TypeMetadataDescriptor;
import org.mule.runtime.api.metadata.resolving.InputTypeResolver;
import org.mule.runtime.api.metadata.resolving.MetadataFailure;
import org.mule.runtime.api.metadata.resolving.MetadataResult;
import org.mule.runtime.api.metadata.resolving.NamedTypeResolver;
import org.mule.runtime.api.metadata.resolving.OutputTypeResolver;
import org.mule.runtime.extension.api.metadata.MetadataResolverUtils;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Optional;
/**
* Metadata service delegate implementations that handles the resolution
* of a {@link ComponentModel} {@link OutputMetadataDescriptor}
*
* @since 4.0
*/
class MetadataOutputDelegate extends BaseMetadataDelegate {
MetadataOutputDelegate(ComponentModel componentModel) {
super(componentModel);
}
Optional<String> getCategoryName() {
return MetadataResolverUtils.getCategoryName(resolverFactory);
}
/**
* Creates an {@link OutputMetadataDescriptor} representing the Component's output metadata using the
* {@link OutputTypeResolver}, if one is available to resolve the output {@link MetadataType}.
*
* @param context current {@link MetadataContext} that will be used by the {@link InputTypeResolver}
* @param key {@link MetadataKey} of the type which's structure has to be resolved
* @return Success with an {@link OutputMetadataDescriptor} representing the Component's output metadata, resolved using the
* {@link OutputTypeResolver} if one is available to resolve its {@link MetadataType}. Failure if the dynamic
* resolution fails for any reason.
*/
MetadataResult<OutputMetadataDescriptor> getOutputMetadataDescriptor(MetadataContext context, Object key) {
MetadataResult<MetadataType> output = getOutputMetadata(context, key);
MetadataResult<MetadataType> attributes = getOutputAttributesMetadata(context, key);
MetadataResult<TypeMetadataDescriptor> outputDescriptor =
toMetadataDescriptorResult(component.getOutput().getType(), component.getOutput().hasDynamicType(), output);
MetadataResult<TypeMetadataDescriptor> attributesDescriptor =
toMetadataDescriptorResult(component.getOutputAttributes().getType(), false, attributes);
OutputMetadataDescriptor descriptor = OutputMetadataDescriptor.builder()
.withReturnType(outputDescriptor.get())
.withAttributesType(attributesDescriptor.get())
.build();
if (!output.isSuccess() || !attributes.isSuccess()) {
List<MetadataFailure> failures = ImmutableList.<MetadataFailure>builder()
.addAll(output.getFailures())
.addAll(attributes.getFailures())
.build();
return failure(descriptor, failures);
}
return success(descriptor);
}
Optional<NamedTypeResolver> getOutputResolver() {
return getOptionalResolver(resolverFactory.getOutputResolver());
}
Optional<NamedTypeResolver> getOutputAttributesResolver() {
return getOptionalResolver(resolverFactory.getOutputAttributesResolver());
}
/**
* Given a {@link MetadataKey} of a type and a {@link MetadataContext}, resolves the {@link MetadataType} of the Components's
* output using the {@link OutputTypeResolver} associated to the current component.
*
* @param context {@link MetadataContext} of the Metadata resolution
* @param key {@link MetadataKey} of the type which's structure has to be resolved
* @return a {@link MetadataResult} with the {@link MetadataType} of the component's output
*/
private MetadataResult<MetadataType> getOutputMetadata(final MetadataContext context, final Object key) {
OutputModel output = component.getOutput();
if (isVoid(output.getType()) || !output.hasDynamicType()) {
return success(output.getType());
}
try {
MetadataType metadata = resolverFactory.getOutputResolver().getOutputType(context, key);
if (isMetadataResolvedCorrectly(metadata, false)) {
return success(adaptToListIfNecessary(metadata, key, context));
}
MetadataFailure failure = newFailure()
.withMessage("Error resolving Output Payload metadata")
.withFailureCode(NO_DYNAMIC_TYPE_AVAILABLE)
.withReason(NULL_TYPE_ERROR)
.onOutputPayload();
return failure(output.getType(), failure);
} catch (Exception e) {
return failure(output.getType(), newFailure(e).onOutputAttributes());
}
}
/**
* Given a {@link MetadataKey} of a type and a {@link MetadataContext}, resolves the {@link MetadataType} of the Components's
* output {@link Message#getAttributes()} using the {@link OutputTypeResolver} associated to the current component.
*
* @param context {@link MetadataContext} of the Metadata resolution
* @param key {@link MetadataKey} of the type which's structure has to be resolved
* @return a {@link MetadataResult} with the {@link MetadataType} of the components output {@link Message#getAttributes()}
*/
private MetadataResult<MetadataType> getOutputAttributesMetadata(final MetadataContext context, Object key) {
OutputModel attributes = component.getOutputAttributes();
if (isVoid(attributes.getType()) || !attributes.hasDynamicType()) {
return success(attributes.getType());
}
try {
MetadataType metadata = resolverFactory.getOutputAttributesResolver().getAttributesType(context, key);
if (isMetadataResolvedCorrectly(metadata, false)) {
return success(metadata);
}
MetadataFailure failure = newFailure()
.withMessage("Error resolving Output Attributes metadata")
.withFailureCode(NO_DYNAMIC_TYPE_AVAILABLE)
.withReason(NULL_TYPE_ERROR)
.onOutputAttributes();
return failure(attributes.getType(), failure);
} catch (Exception e) {
return failure(attributes.getType(), newFailure(e).onOutputAttributes());
}
}
private MetadataResult<TypeMetadataDescriptor> toMetadataDescriptorResult(MetadataType type,
boolean isDynamic,
MetadataResult<MetadataType> result) {
MetadataType resultingType = result.get() == null ? type : result.get();
TypeMetadataDescriptor descriptor = TypeMetadataDescriptor.builder()
.withType(resultingType)
.dynamic(isDynamic)
.build();
return result.isSuccess() ? success(descriptor) : failure(descriptor, result.getFailures());
}
private MetadataType adaptToListIfNecessary(MetadataType resolvedType, Object key, MetadataContext metadataContext)
throws MetadataResolvingException {
if (!(component.getOutput().getType() instanceof ArrayType)) {
return resolvedType;
}
MetadataType outputType = ((ArrayType) component.getOutput().getType()).getType();
String typeId = getId(outputType);
if (Message.class.getName().equals(typeId)) {
resolvedType = wrapInMessageType(resolvedType, key, metadataContext);
return metadataContext.getTypeBuilder().arrayType().id(typeId).of(resolvedType).build();
}
return resolvedType;
}
private MetadataType wrapInMessageType(MetadataType type, Object key, MetadataContext context)
throws MetadataResolvingException {
MetadataResult<MetadataType> attributes = getOutputAttributesMetadata(context, key);
if (attributes.isSuccess()) {
return new MessageMetadataTypeBuilder()
.payload(type)
.attributes(attributes.get())
.build();
} else {
throw new MetadataResolvingException("Could not resolve attributes of List<Message> output",
attributes.getFailures().stream()
.map(MetadataFailure::getFailureCode)
.findFirst()
.orElse(UNKNOWN));
}
}
}