/*
* 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.validation;
import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import static org.mule.runtime.module.extension.internal.loader.validation.ModelValidationUtils.validateConfigOverrideParametersNotAllowed;
import org.mule.runtime.api.connection.CachedConnectionProvider;
import org.mule.runtime.api.connection.ConnectionProvider;
import org.mule.runtime.api.meta.model.ComponentModel;
import org.mule.runtime.api.meta.model.EnrichableModel;
import org.mule.runtime.api.meta.model.ExtensionModel;
import org.mule.runtime.api.meta.model.config.ConfigurationModel;
import org.mule.runtime.api.meta.model.connection.ConnectionProviderModel;
import org.mule.runtime.api.meta.model.connection.HasConnectionProviderModels;
import org.mule.runtime.api.meta.model.operation.OperationModel;
import org.mule.runtime.api.meta.model.parameter.ParameterModel;
import org.mule.runtime.api.meta.model.parameter.ParameterizedModel;
import org.mule.runtime.api.meta.model.source.SourceModel;
import org.mule.runtime.api.meta.model.util.ExtensionWalker;
import org.mule.runtime.api.meta.model.util.IdempotentExtensionWalker;
import org.mule.runtime.extension.api.annotation.param.ConfigOverride;
import org.mule.runtime.extension.api.connectivity.TransactionalConnection;
import org.mule.runtime.extension.api.loader.ExtensionModelValidator;
import org.mule.runtime.extension.api.loader.Problem;
import org.mule.runtime.extension.api.loader.ProblemsReporter;
import org.mule.runtime.module.extension.internal.loader.java.property.ConnectivityModelProperty;
import org.mule.runtime.module.extension.internal.loader.java.property.ImplementingTypeModelProperty;
import org.mule.runtime.module.extension.internal.util.MuleExtensionUtils;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* {@link ExtensionModelValidator} which applies to {@link ExtensionModel}s which either contains {@link ConnectionProviderModel}s,
* {@link OperationModel}s which require a connection or both.
* <p>
* This validator makes sure that:
* <ul>
* <li>All operations require the same type of connections</li>
* <li>All the {@link ConnectionProvider}s return connections of the same type as expected by the {@link OperationModel}s</li>
* <li>Transactional connections can not be produced by {@link CachedConnectionProvider}s</li>
* </ul>
*
* @since 4.0
*/
public final class ConnectionProviderModelValidator implements ExtensionModelValidator {
@Override
public void validate(ExtensionModel extensionModel, ProblemsReporter problemsReporter) {
Set<ConnectionProviderModel> globalConnectionProviders = new HashSet<>();
Multimap<ConfigurationModel, ConnectionProviderModel> configLevelConnectionProviders = HashMultimap.create();
new ExtensionWalker() {
@Override
public void onConnectionProvider(HasConnectionProviderModels owner, ConnectionProviderModel model) {
validateTransactions(model, problemsReporter);
if (owner instanceof ConfigurationModel) {
configLevelConnectionProviders.put((ConfigurationModel) owner, model);
} else {
globalConnectionProviders.add(model);
}
validateConfigOverrideParametersNotAllowed(model, problemsReporter, "Connection");
}
}.walk(extensionModel);
validateGlobalConnectionTypes(extensionModel, globalConnectionProviders, problemsReporter);
validateConfigLevelConnectionTypes(configLevelConnectionProviders, problemsReporter);
}
private void validateTransactions(ConnectionProviderModel connectionProviderModel, ProblemsReporter problemsReporter) {
Class<ConnectionProvider> providerType = (Class<ConnectionProvider>) connectionProviderModel
.getModelProperty(ImplementingTypeModelProperty.class).map(ImplementingTypeModelProperty::getType).orElse(null);
final Class<?> connectionType = MuleExtensionUtils.getConnectionType(connectionProviderModel);
if (providerType != null && CachedConnectionProvider.class.isAssignableFrom(providerType)
&& TransactionalConnection.class.isAssignableFrom(connectionType)) {
problemsReporter
.addError(new Problem(connectionProviderModel, format("Cached connection provider '%s' provides transactional "
+ "connections. Transactional connections cannot be produced by cached providers, since the "
+ "same connection cannot join two different transactions at once",
connectionProviderModel.getName())));
}
}
private void validateGlobalConnectionTypes(ExtensionModel extensionModel,
Set<ConnectionProviderModel> globalConnectionProviders,
ProblemsReporter problemsReporter) {
if (globalConnectionProviders.isEmpty()) {
return;
}
for (ConnectionProviderModel connectionProviderModel : globalConnectionProviders) {
final Class<?> connectionType = MuleExtensionUtils.getConnectionType(connectionProviderModel);
new IdempotentExtensionWalker() {
@Override
protected void onOperation(OperationModel operationModel) {
validateConnectionTypes(connectionProviderModel, operationModel, connectionType, problemsReporter);
}
@Override
protected void onSource(SourceModel sourceModel) {
validateConnectionTypes(connectionProviderModel, sourceModel, connectionType, problemsReporter);
}
}.walk(extensionModel);
}
}
private void validateConfigLevelConnectionTypes(Multimap<ConfigurationModel, ConnectionProviderModel> configLevelConnectionProviders,
ProblemsReporter problemsReporter) {
configLevelConnectionProviders.asMap().forEach((configModel, providerModels) -> {
for (ConnectionProviderModel providerModel : providerModels) {
Class<?> connectionType = MuleExtensionUtils.getConnectionType(providerModel);
configModel.getOperationModels()
.forEach(operationModel -> validateConnectionTypes(providerModel, operationModel, connectionType, problemsReporter));
}
});
}
private <T> Optional<Class<T>> getConnectionType(EnrichableModel model) {
Optional<ConnectivityModelProperty> connectivityProperty = model.getModelProperty(ConnectivityModelProperty.class);
if (!connectivityProperty.isPresent() && model instanceof ParameterizedModel) {
connectivityProperty = ((ParameterizedModel) model).getAllParameterModels().stream()
.map(p -> p.getModelProperty(ConnectivityModelProperty.class).orElse(null)).filter(p -> p != null).findFirst();
}
return connectivityProperty.map(property -> (Class<T>) property.getConnectionType());
}
private void validateConnectionTypes(ConnectionProviderModel providerModel,
ComponentModel componentModel, Class<?> providerConnectionType,
ProblemsReporter problemsReporter) {
getConnectionType(componentModel).ifPresent(connectionType -> {
if (!connectionType.isAssignableFrom(providerConnectionType)) {
problemsReporter
.addError(new Problem(providerModel,
format("Component '%s' requires a connection of type '%s'. However, it also defines connection provider "
+ "'%s' which yields connections of incompatible type '%s'",
componentModel.getName(), connectionType.getName(), providerModel.getName(),
providerConnectionType.getName())));
}
});
}
}