/*
* 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.enricher;
import static java.util.stream.Collectors.toSet;
import static org.mule.runtime.api.meta.model.error.ErrorModelBuilder.newError;
import static org.mule.runtime.internal.dsl.DslConstants.CORE_PREFIX;
import static org.mule.runtime.module.extension.internal.loader.enricher.ModuleErrors.ANY;
import static org.mule.runtime.module.extension.internal.loader.enricher.ModuleErrors.CONNECTIVITY;
import static org.mule.runtime.module.extension.internal.loader.enricher.ModuleErrors.RETRY_EXHAUSTED;
import org.mule.runtime.api.meta.model.error.ErrorModel;
import org.mule.runtime.api.meta.model.error.ErrorModelBuilder;
import org.mule.runtime.extension.api.error.ErrorTypeDefinition;
import org.mule.runtime.extension.api.error.MuleErrors;
import org.mule.runtime.extension.api.exception.IllegalModelDefinitionException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.jgrapht.DirectedGraph;
import org.jgrapht.Graph;
import org.jgrapht.alg.CycleDetector;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultDirectedWeightedGraph;
import org.jgrapht.traverse.TopologicalOrderIterator;
/**
* Extension's {@link ErrorModel} factory.
* Given an {@link Enum} implementing {@link ErrorTypeDefinition} and the namespace of the extension validates
* consistency and generates the correspondent {@link ErrorModel errorModels}.
*
* @since 4.0
*/
public class ErrorsModelFactory {
private static final String MULE = CORE_PREFIX.toUpperCase();
private final String extensionNamespace;
private final Map<String, ErrorModel> errorModelMap;
/**
* Creates a new instance of the factory
*
* @param errorTypesEnum an {@link ErrorTypeDefinition} implementation indicating all the errors from an extension
* @param extensionNamespace the namespace for the {@link ErrorModel} to be generated
*/
public ErrorsModelFactory(ErrorTypeDefinition<?>[] errorTypesEnum, String extensionNamespace)
throws IllegalModelDefinitionException {
this.extensionNamespace = extensionNamespace.toUpperCase();
final DirectedGraph<ErrorTypeDefinition, Pair<ErrorTypeDefinition, ErrorTypeDefinition>> graph = toGraph(errorTypesEnum);
errorModelMap = new HashMap<>();
initErrorModelMap(errorModelMap);
new TopologicalOrderIterator<>(graph).forEachRemaining(errorType -> {
ErrorModel errorModel = toErrorModel(errorType, errorModelMap);
errorModelMap.put(errorModel.toString(), errorModel);
});
addConnectivityErrors(errorModelMap);
}
/**
* @return A {@link Set} of converted {@link ErrorModel}s generated from the given {@link ErrorTypeDefinition} array
*/
public Set<ErrorModel> getErrorModels() {
return errorModelMap.values().stream().collect(toSet());
}
/**
* From a {@link ErrorTypeDefinition} gives the {@link ErrorModel} representation
*
* @param errorTypeDefinition to use to find the {@link ErrorModel} representation
* @return The correspondent {@link ErrorModel} for a given {@link ErrorTypeDefinition}
*/
ErrorModel getErrorModel(ErrorTypeDefinition errorTypeDefinition) {
return errorModelMap.get(toIdentifier(errorTypeDefinition));
}
private DefaultDirectedGraph<ErrorTypeDefinition, Pair<ErrorTypeDefinition, ErrorTypeDefinition>> toGraph(ErrorTypeDefinition<?>[] errorTypesEnum) {
final DefaultDirectedGraph<ErrorTypeDefinition, Pair<ErrorTypeDefinition, ErrorTypeDefinition>> graph =
new DefaultDirectedWeightedGraph<>(ImmutablePair::new);
Stream.of(errorTypesEnum).forEach(error -> addType(error, graph));
detectCycleReferences(graph);
return graph;
}
/**
* @param errorTypeDefinition
* @param errorModelMap
* @return
*/
private ErrorModel toErrorModel(ErrorTypeDefinition<?> errorTypeDefinition, Map<String, ErrorModel> errorModelMap) {
if (errorModelMap.containsKey(toIdentifier(errorTypeDefinition))) {
return errorModelMap.get(toIdentifier(errorTypeDefinition));
} else {
ErrorModelBuilder builder = newError(errorTypeDefinition.getType(), getErrorNamespace(errorTypeDefinition));
builder.withParent(toErrorModel(errorTypeDefinition.getParent().orElse(ANY), errorModelMap));
ErrorModel errorModel = builder.build();
errorModelMap.put(toIdentifier(errorTypeDefinition), errorModel);
return errorModel;
}
}
private String toIdentifier(ErrorTypeDefinition errorTypeDefinition) {
return getErrorNamespace(errorTypeDefinition) + ":" + errorTypeDefinition.getType();
}
private String getErrorNamespace(ErrorTypeDefinition errorType) {
return errorType instanceof MuleErrors ? MULE : extensionNamespace;
}
private void addType(ErrorTypeDefinition<?> errorType,
Graph<ErrorTypeDefinition, Pair<ErrorTypeDefinition, ErrorTypeDefinition>> graph) {
graph.addVertex(errorType);
ErrorTypeDefinition parentErrorType = errorType.getParent().orElse(ANY);
graph.addVertex(parentErrorType);
graph.addEdge(errorType, parentErrorType);
}
private void detectCycleReferences(DefaultDirectedGraph graph) {
CycleDetector<?, ?> cycleDetector = new CycleDetector<>(graph);
if (cycleDetector.detectCycles()) {
throw new IllegalModelDefinitionException("Cyclic Error Types reference detected, offending types: "
+ cycleDetector.findCycles());
}
}
private void addConnectivityErrors(Map<String, ErrorModel> errorModelMap) {
ErrorModel connectivityError = toErrorModel(CONNECTIVITY, errorModelMap);
String key = connectivityError.toString();
if (!errorModelMap.containsKey(key)) {
errorModelMap.put(key, connectivityError);
}
ErrorModel retryExhaustedError = toErrorModel(RETRY_EXHAUSTED, errorModelMap);
String retry = retryExhaustedError.toString();
if (!errorModelMap.containsKey(retry)) {
errorModelMap.put(retry, retryExhaustedError);
}
}
private void initErrorModelMap(Map<String, ErrorModel> errorModelMap) {
errorModelMap.put(toIdentifier(MuleErrors.ANY), ErrorModelBuilder.newError(ANY.getType(), MULE).build());
}
}