org.apache.camel.dsl.yaml.YamlRoutesBuilderLoader Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.dsl.yaml;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.ErrorHandlerFactory;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.api.management.ManagedResource;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.RouteConfigurationBuilder;
import org.apache.camel.component.properties.PropertiesLocation;
import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
import org.apache.camel.dsl.yaml.common.exception.InvalidNodeTypeException;
import org.apache.camel.dsl.yaml.deserializers.OutputAwareFromDefinition;
import org.apache.camel.model.InterceptDefinition;
import org.apache.camel.model.InterceptFromDefinition;
import org.apache.camel.model.InterceptSendToEndpointDefinition;
import org.apache.camel.model.KameletDefinition;
import org.apache.camel.model.OnCompletionDefinition;
import org.apache.camel.model.OnExceptionDefinition;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.model.RouteConfigurationDefinition;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.model.RouteTemplateDefinition;
import org.apache.camel.model.TemplatedRouteDefinition;
import org.apache.camel.model.ToDefinition;
import org.apache.camel.model.errorhandler.DeadLetterChannelDefinition;
import org.apache.camel.model.errorhandler.DefaultErrorHandlerDefinition;
import org.apache.camel.model.errorhandler.NoErrorHandlerDefinition;
import org.apache.camel.model.rest.RestConfigurationDefinition;
import org.apache.camel.model.rest.RestDefinition;
import org.apache.camel.model.rest.VerbDefinition;
import org.apache.camel.spi.CamelContextCustomizer;
import org.apache.camel.spi.DataType;
import org.apache.camel.spi.DependencyStrategy;
import org.apache.camel.spi.Resource;
import org.apache.camel.spi.annotations.RoutesLoader;
import org.apache.camel.support.ObjectHelper;
import org.apache.camel.support.PluginHelper;
import org.apache.camel.support.PropertyBindingSupport;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.StringQuoteHelper;
import org.apache.camel.util.URISupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snakeyaml.engine.v2.api.LoadSettings;
import org.snakeyaml.engine.v2.api.YamlUnicodeReader;
import org.snakeyaml.engine.v2.composer.Composer;
import org.snakeyaml.engine.v2.nodes.MappingNode;
import org.snakeyaml.engine.v2.nodes.Node;
import org.snakeyaml.engine.v2.nodes.NodeTuple;
import org.snakeyaml.engine.v2.nodes.NodeType;
import org.snakeyaml.engine.v2.nodes.SequenceNode;
import org.snakeyaml.engine.v2.parser.Parser;
import org.snakeyaml.engine.v2.parser.ParserImpl;
import org.snakeyaml.engine.v2.scanner.StreamReader;
import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asMap;
import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asMappingNode;
import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asSequenceNode;
import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asStringList;
import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asText;
import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.isSequenceNode;
import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.nodeAt;
import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.setDeserializationContext;
@ManagedResource(description = "Managed YAML RoutesBuilderLoader")
@RoutesLoader(YamlRoutesBuilderLoader.EXTENSION)
public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
public static final String EXTENSION = "yaml";
public static final String[] SUPPORTED_EXTENSION = { EXTENSION, "camel.yaml", "pipe.yaml" };
private static final String DEPRECATED_EXTENSION = "camelk.yaml";
private static final Logger LOG = LoggerFactory.getLogger(YamlRoutesBuilderLoader.class);
private final AtomicBoolean deprecatedWarnLogged = new AtomicBoolean();
private final AtomicBoolean deprecatedBindingWarnLogged = new AtomicBoolean();
// API versions for Camel-K Integration and Pipe
// we are lenient so lets just assume we can work with any of the v1 even if they evolve
private static final String INTEGRATION_VERSION = "camel.apache.org/v1";
@Deprecated
private static final String BINDING_VERSION = "camel.apache.org/v1alpha1";
private static final String PIPE_VERSION = "camel.apache.org/v1";
private static final String STRIMZI_VERSION = "kafka.strimzi.io/v1beta2";
private static final String KNATIVE_MESSAGING_VERSION = "messaging.knative.dev/v1";
private static final String KNATIVE_EVENTING_VERSION = "eventing.knative.dev/v1";
private static final String KNATIVE_EVENT_TYPE = "org.apache.camel.event";
private final Map preparseDone = new ConcurrentHashMap<>();
public YamlRoutesBuilderLoader() {
super(EXTENSION);
}
YamlRoutesBuilderLoader(String extension) {
super(extension);
}
@Override
public boolean isSupportedExtension(String extension) {
// this builder can support multiple extensions
if (DEPRECATED_EXTENSION.equals(extension)) {
if (deprecatedWarnLogged.compareAndSet(false, true)) {
LOG.warn("File extension camelk.yaml is deprecated. Use camel.yaml instead.");
}
return true;
}
return Arrays.asList(SUPPORTED_EXTENSION).contains(extension);
}
protected RouteBuilder builder(final YamlDeserializationContext ctx, final Node root) {
// we need to keep track of already configured items as the yaml-dsl returns a
// RouteConfigurationBuilder that is capable of both route and route configurations
// which can lead to the same items being processed twice
final Set indexes = new HashSet<>();
return new RouteConfigurationBuilder() {
@Override
public void configure() throws Exception {
setDeserializationContext(root, ctx);
Object target = preConfigureNode(root, ctx, false);
if (target == null) {
return;
}
Iterator> it = ObjectHelper.createIterator(target);
while (it.hasNext()) {
target = it.next();
if (target instanceof Node && isSequenceNode((Node) target)) {
SequenceNode seq = asSequenceNode((Node) target);
for (Node node : seq.getValue()) {
int idx = -1;
if (node.getStartMark().isPresent()) {
idx = node.getStartMark().get().getIndex();
}
if (idx == -1 || !indexes.contains(idx)) {
Object item = ctx.mandatoryResolve(node).construct(node);
boolean accepted = doConfigure(item);
if (accepted && idx != -1) {
indexes.add(idx);
}
}
}
} else {
doConfigure(target);
}
}
// knowing this is the last time an YAML may have been parsed, we can clear the cache
// (route may get reloaded later)
Resource resource = ctx.getResource();
if (resource != null) {
preparseDone.remove(resource.getLocation());
}
beansDeserializer.clearCache();
}
private boolean doConfigure(Object item) throws Exception {
if (item instanceof OutputAwareFromDefinition) {
RouteDefinition route = new RouteDefinition();
route.setInput(((OutputAwareFromDefinition) item).getDelegate());
route.setOutputs(((OutputAwareFromDefinition) item).getOutputs());
CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
getRouteCollection().route(route);
return true;
} else if (item instanceof RouteDefinition) {
CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
getRouteCollection().route((RouteDefinition) item);
return true;
} else if (item instanceof CamelContextCustomizer) {
((CamelContextCustomizer) item).configure(getCamelContext());
return true;
} else if (item instanceof InterceptFromDefinition) {
if (!getRouteCollection().getRoutes().isEmpty()) {
throw new IllegalArgumentException(
"interceptFrom must be defined before any routes in the RouteBuilder");
}
CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
getRouteCollection().getInterceptFroms().add((InterceptFromDefinition) item);
return true;
} else if (item instanceof InterceptDefinition) {
if (!getRouteCollection().getRoutes().isEmpty()) {
throw new IllegalArgumentException(
"intercept must be defined before any routes in the RouteBuilder");
}
CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
getRouteCollection().getIntercepts().add((InterceptDefinition) item);
return true;
} else if (item instanceof InterceptSendToEndpointDefinition) {
if (!getRouteCollection().getRoutes().isEmpty()) {
throw new IllegalArgumentException(
"interceptSendToEndpoint must be defined before any routes in the RouteBuilder");
}
CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
getRouteCollection().getInterceptSendTos().add((InterceptSendToEndpointDefinition) item);
return true;
} else if (item instanceof OnCompletionDefinition) {
if (!getRouteCollection().getRoutes().isEmpty()) {
throw new IllegalArgumentException(
"onCompletion must be defined before any routes in the RouteBuilder");
}
CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
getRouteCollection().getOnCompletions().add((OnCompletionDefinition) item);
return true;
} else if (item instanceof OnExceptionDefinition) {
if (!getRouteCollection().getRoutes().isEmpty()) {
throw new IllegalArgumentException(
"onException must be defined before any routes in the RouteBuilder");
}
CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
getRouteCollection().getOnExceptions().add((OnExceptionDefinition) item);
return true;
} else if (item instanceof ErrorHandlerFactory) {
if (!getRouteCollection().getRoutes().isEmpty()) {
throw new IllegalArgumentException(
"errorHandler must be defined before any routes in the RouteBuilder");
}
errorHandler((ErrorHandlerFactory) item);
return true;
} else if (item instanceof RouteTemplateDefinition) {
CamelContextAware.trySetCamelContext(getRouteTemplateCollection(), getCamelContext());
getRouteTemplateCollection().routeTemplate((RouteTemplateDefinition) item);
return true;
} else if (item instanceof TemplatedRouteDefinition) {
CamelContextAware.trySetCamelContext(getTemplatedRouteCollection(), getCamelContext());
getTemplatedRouteCollection().templatedRoute((TemplatedRouteDefinition) item);
return true;
} else if (item instanceof RestDefinition) {
RestDefinition definition = (RestDefinition) item;
for (VerbDefinition verb : definition.getVerbs()) {
verb.setRest(definition);
}
CamelContextAware.trySetCamelContext(getRestCollection(), getCamelContext());
getRestCollection().rest(definition);
return true;
} else if (item instanceof RestConfigurationDefinition) {
((RestConfigurationDefinition) item).asRestConfiguration(
getCamelContext(),
getCamelContext().getRestConfiguration());
return true;
}
return false;
}
@Override
public void configuration() throws Exception {
setDeserializationContext(root, ctx);
Object target = preConfigureNode(root, ctx, false);
if (target == null) {
return;
}
Iterator> it = ObjectHelper.createIterator(target);
while (it.hasNext()) {
target = it.next();
if (target instanceof Node && isSequenceNode((Node) target)) {
SequenceNode seq = asSequenceNode((Node) target);
for (Node node : seq.getValue()) {
int idx = -1;
if (node.getStartMark().isPresent()) {
idx = node.getStartMark().get().getIndex();
}
if (idx == -1 || !indexes.contains(idx)) {
if (node.getNodeType() == NodeType.MAPPING) {
MappingNode mn = asMappingNode(node);
for (NodeTuple nt : mn.getValue()) {
String key = asText(nt.getKeyNode());
// only accept route-configuration
if ("route-configuration".equals(key) || "routeConfiguration".equals(key)) {
Object item = ctx.mandatoryResolve(node).construct(node);
boolean accepted = doConfiguration(item);
if (accepted && idx != -1) {
indexes.add(idx);
}
}
}
}
}
}
} else {
doConfiguration(target);
}
}
}
private boolean doConfiguration(Object item) {
if (item instanceof RouteConfigurationDefinition) {
CamelContextAware.trySetCamelContext(getRouteConfigurationCollection(), getCamelContext());
getRouteConfigurationCollection().routeConfiguration((RouteConfigurationDefinition) item);
return true;
}
return false;
}
};
}
private Object preConfigureNode(Node root, YamlDeserializationContext ctx, boolean preParse) {
// backwards compatible fixes
Object target = root;
// check if the yaml is a camel-k yaml with embedded binding/routes (called flow(s))
if (Objects.equals(root.getNodeType(), NodeType.MAPPING)) {
final MappingNode mn = YamlDeserializerSupport.asMappingNode(root);
// camel-k: integration
boolean integration = anyTupleMatches(mn.getValue(), "apiVersion", v -> v.startsWith(INTEGRATION_VERSION)) &&
anyTupleMatches(mn.getValue(), "kind", "Integration");
// camel-k: kamelet binding (deprecated)
boolean binding = anyTupleMatches(mn.getValue(), "apiVersion", v -> v.startsWith(BINDING_VERSION)) &&
anyTupleMatches(mn.getValue(), "kind", "KameletBinding");
// camel-k: pipe
boolean pipe = anyTupleMatches(mn.getValue(), "apiVersion", v -> v.startsWith(PIPE_VERSION)) &&
anyTupleMatches(mn.getValue(), "kind", "Pipe");
if (integration) {
target = preConfigureIntegration(root, ctx, target, preParse);
} else if (binding || pipe) {
if (binding && deprecatedBindingWarnLogged.compareAndSet(false, true)) {
LOG.warn("CamelK kind=KameletBinding is deprecated. Use CamelK kind=Pipe instead.");
}
target = preConfigurePipe(root, ctx, target, preParse);
}
}
// only detect beans during pre-parsing
if (preParse && Objects.equals(root.getNodeType(), NodeType.SEQUENCE)) {
final List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy