/*
* 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.loader.java;
import static java.lang.String.format;
import static org.mule.runtime.api.meta.ExpressionSupport.NOT_SUPPORTED;
import static org.mule.runtime.extension.api.util.ExtensionMetadataTypeUtils.isInputStream;
import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.getSourceReturnType;
import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.isLifecycle;
import org.mule.metadata.api.model.MetadataType;
import org.mule.runtime.api.meta.model.declaration.fluent.Declarer;
import org.mule.runtime.api.meta.model.declaration.fluent.ExtensionDeclarer;
import org.mule.runtime.api.meta.model.declaration.fluent.HasSourceDeclarer;
import org.mule.runtime.api.meta.model.declaration.fluent.ParameterDeclarer;
import org.mule.runtime.api.meta.model.declaration.fluent.ParameterizedDeclarer;
import org.mule.runtime.api.meta.model.declaration.fluent.SourceDeclarer;
import org.mule.runtime.extension.api.annotation.Streaming;
import org.mule.runtime.extension.api.annotation.source.EmitsResponse;
import org.mule.runtime.extension.api.exception.IllegalModelDefinitionException;
import org.mule.runtime.extension.api.exception.IllegalSourceModelDefinitionException;
import org.mule.runtime.module.extension.internal.loader.java.property.ImplementingTypeModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.property.SourceCallbackModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.property.SourceFactoryModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.type.ExtensionParameter;
import org.mule.runtime.module.extension.internal.loader.java.type.MethodElement;
import org.mule.runtime.module.extension.internal.loader.java.type.SourceElement;
import org.mule.runtime.module.extension.internal.loader.java.type.WithMessageSources;
import org.mule.runtime.module.extension.internal.loader.utils.ParameterDeclarationContext;
import org.mule.runtime.module.extension.internal.runtime.source.DefaultSourceFactory;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
/**
* Helper class for declaring sources through a {@link DefaultJavaModelLoaderDelegate}
* @since 4.0
*/
final class SourceModelLoaderDelegate extends AbstractModelLoaderDelegate {
private static final String SOURCE = "Source";
private final Map<Class<?>, SourceDeclarer> sourceDeclarers = new HashMap<>();
SourceModelLoaderDelegate(DefaultJavaModelLoaderDelegate delegate) {
super(delegate);
}
void declareMessageSources(ExtensionDeclarer extensionDeclarer, HasSourceDeclarer declarer,
WithMessageSources typeComponent) {
// TODO: MULE-9220: Add a Syntax validator which checks that a Source class doesn't try to declare operations, configs, etc
typeComponent.getSources().forEach(source -> declareMessageSource(extensionDeclarer, declarer, source, true));
}
void declareMessageSource(ExtensionDeclarer extensionDeclarer,
HasSourceDeclarer declarer,
SourceElement sourceType,
boolean supportsConfig) {
// TODO: MULE-9220 - Add a syntax validator which checks that the sourceType doesn't implement
if (isLifecycle(sourceType.getDeclaringClass())) {
throw new IllegalSourceModelDefinitionException(
format("Source class '%s' implements a lifecycle interface. Sources are not allowed to",
sourceType.getDeclaringClass().getName()));
}
final Optional<ExtensionParameter> configParameter = loader.getConfigParameter(sourceType);
final Optional<ExtensionParameter> connectionParameter = loader.getConnectionParameter(sourceType);
if (loader.isInvalidConfigSupport(supportsConfig, configParameter, connectionParameter)) {
throw new IllegalSourceModelDefinitionException(
format("Source '%s' is defined at the extension level but it requires a config parameter. "
+ "Remove such parameter or move the source to the proper config",
sourceType.getName()));
}
HasSourceDeclarer actualDeclarer =
(HasSourceDeclarer) loader.selectDeclarerBasedOnConfig(extensionDeclarer, (Declarer) declarer, configParameter,
connectionParameter);
SourceDeclarer source = sourceDeclarers.get(sourceType.getDeclaringClass());
if (source != null) {
actualDeclarer.withMessageSource(source);
return;
}
source = actualDeclarer.withMessageSource(sourceType.getAlias());
List<Type> sourceGenerics = sourceType.getSuperClassGenerics();
if (sourceGenerics.size() != 2) {
// TODO: MULE-9220: Add a syntax validator for this
throw new IllegalModelDefinitionException(format("Message source class '%s' was expected to have 2 generic types "
+ "(one for the Payload type and another for the Attributes type) but %d were found",
sourceType.getName(),
sourceGenerics.size()));
}
source
.hasResponse(sourceType.isAnnotatedWith(EmitsResponse.class))
.withModelProperty(new SourceFactoryModelProperty(new DefaultSourceFactory(sourceType.getDeclaringClass())))
.withModelProperty(new ImplementingTypeModelProperty(sourceType.getDeclaringClass()));
processComponentConnectivity(source, sourceType, sourceType);
resolveOutputTypes(source, sourceGenerics, sourceType);
loader.addExceptionEnricher(sourceType, source);
declareSourceParameters(sourceType, source);
declareSourceCallback(sourceType, source);
sourceDeclarers.put(sourceType.getDeclaringClass(), source);
}
private void resolveOutputTypes(SourceDeclarer source, List<Type> sourceGenerics, SourceElement sourceType) {
final MetadataType outputTtype = getSourceReturnType(sourceGenerics.get(0), getTypeLoader());
source.withOutput().ofType(outputTtype);
source.withOutputAttributes().ofType(getTypeLoader().load(sourceGenerics.get(1)));
source.supportsStreaming(isInputStream(outputTtype) || sourceType.getAnnotation(Streaming.class).isPresent());
}
/**
* Declares the parameters needed to generate messages
*/
private void declareSourceParameters(SourceElement sourceType, SourceDeclarer source) {
ParameterModelsLoaderDelegate parametersLoader = loader.getFieldParametersLoader();
ParameterDeclarationContext declarationContext = new ParameterDeclarationContext(SOURCE, source.getDeclaration());
List<ParameterDeclarer> parameters = parametersLoader.declare(source, sourceType.getParameters(), declarationContext);
parameters.forEach(p -> p.withExpressionSupport(NOT_SUPPORTED));
}
private void declareSourceCallback(SourceElement sourceType, SourceDeclarer source) {
final Optional<MethodElement> onResponseMethod = sourceType.getOnResponseMethod();
final Optional<MethodElement> onErrorMethod = sourceType.getOnErrorMethod();
// TODO: MULE-9220 add syntax validator to check that none of these use @UseConfig or @Connection
declareSourceCallbackParameters(source, onResponseMethod, source::onSuccess);
declareSourceCallbackParameters(source, onErrorMethod, source::onError);
source.withModelProperty(new SourceCallbackModelProperty(getMethod(onResponseMethod), getMethod(onErrorMethod)));
}
private void declareSourceCallbackParameters(SourceDeclarer source, Optional<MethodElement> sourceCallback,
Supplier<ParameterizedDeclarer> callback) {
sourceCallback.ifPresent(method -> {
ParameterDeclarationContext declarationContext = new ParameterDeclarationContext(SOURCE, source.getDeclaration());
loader.getMethodParametersLoader().declare(callback.get(), method.getParameters(), declarationContext);
});
}
private Optional<Method> getMethod(Optional<MethodElement> method) {
return method.map(MethodElement::getMethod);
}
}