org.apache.camel.model.RouteDefinitionHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of camel-core Show documentation
Show all versions of camel-core Show documentation
The Core Camel Java DSL based router
/**
* 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.model;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.ErrorHandlerBuilder;
import org.apache.camel.util.CamelContextHelper;
import org.apache.camel.util.EndpointHelper;
import org.apache.camel.util.IntrospectionSupport;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.URISupport;
import static org.apache.camel.model.ProcessorDefinitionHelper.filterTypeInOutputs;
/**
* Helper for {@link RouteDefinition}
*
* Utility methods to help preparing {@link RouteDefinition} before they are added to
* {@link org.apache.camel.CamelContext}.
*/
@SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
public final class RouteDefinitionHelper {
private RouteDefinitionHelper() {
}
/**
* Gather all the endpoint uri's the route is using from the EIPs that has a static endpoint defined.
*
* @param route the route
* @param includeInputs whether to include inputs
* @param includeOutputs whether to include outputs
* @return the endpoints uris
*/
public static Set gatherAllStaticEndpointUris(CamelContext camelContext, RouteDefinition route, boolean includeInputs, boolean includeOutputs) {
return gatherAllEndpointUris(camelContext, route, includeInputs, includeOutputs, false);
}
/**
* Gather all the endpoint uri's the route is using from the EIPs that has a static or dynamic endpoint defined.
*
* @param route the route
* @param includeInputs whether to include inputs
* @param includeOutputs whether to include outputs
* @param includeDynamic whether to include dynamic outputs which has been in use during routing at runtime, gathered from the {@link org.apache.camel.spi.RuntimeEndpointRegistry}.
* @return the endpoints uris
*/
public static Set gatherAllEndpointUris(CamelContext camelContext, RouteDefinition route, boolean includeInputs, boolean includeOutputs, boolean includeDynamic) {
Set answer = new LinkedHashSet();
if (includeInputs) {
for (FromDefinition from : route.getInputs()) {
String uri = normalizeUri(from.getEndpointUri());
if (uri != null) {
answer.add(uri);
}
}
}
if (includeOutputs) {
Iterator it = filterTypeInOutputs(route.getOutputs(), EndpointRequiredDefinition.class);
while (it.hasNext()) {
String uri = normalizeUri(it.next().getEndpointUri());
if (uri != null) {
answer.add(uri);
}
}
if (includeDynamic && camelContext.getRuntimeEndpointRegistry() != null) {
List endpoints = camelContext.getRuntimeEndpointRegistry().getEndpointsPerRoute(route.getId(), false);
for (String uri : endpoints) {
if (uri != null) {
answer.add(uri);
}
}
}
}
return answer;
}
private static String normalizeUri(String uri) {
try {
return URISupport.normalizeUri(uri);
} catch (UnsupportedEncodingException e) {
// ignore
} catch (URISyntaxException e) {
// ignore
}
return null;
}
/**
* Force assigning ids to the routes
*
* @param context the camel context
* @param routes the routes
* @throws Exception is thrown if error force assign ids to the routes
*/
public static void forceAssignIds(CamelContext context, List routes) throws Exception {
// handle custom assigned id's first, and then afterwards assign auto generated ids
Set customIds = new HashSet();
for (final RouteDefinition route : routes) {
// if there was a custom id assigned, then make sure to support property placeholders
if (route.hasCustomIdAssigned()) {
final String originalId = route.getId();
final String id = context.resolvePropertyPlaceholders(originalId);
// only set id if its changed, such as we did property placeholder
if (!originalId.equals(id)) {
route.setId(id);
ProcessorDefinitionHelper.addPropertyPlaceholdersChangeRevertAction(new Runnable() {
@Override
public void run() {
route.setId(originalId);
}
});
}
customIds.add(id);
}
}
// auto assign route ids
for (final RouteDefinition route : routes) {
if (route.getId() == null) {
// keep assigning id's until we find a free name
boolean done = false;
String id = null;
while (!done) {
id = context.getNodeIdFactory().createId(route);
done = !customIds.contains(id);
}
route.setId(id);
ProcessorDefinitionHelper.addPropertyPlaceholdersChangeRevertAction(new Runnable() {
@Override
public void run() {
route.setId(null);
route.setCustomId(false);
}
});
route.setCustomId(false);
customIds.add(route.getId());
}
}
}
/**
* Validates that the target route has no duplicate id's from any of the existing routes.
*
* @param target the target route
* @param routes the existing routes
* @return null if no duplicate id's detected, otherwise the first found duplicate id is returned.
*/
public static String validateUniqueIds(RouteDefinition target, List routes) {
Set routesIds = new LinkedHashSet();
// gather all ids for the existing route, but only include custom ids, and no abstract ids
// as abstract nodes is cross-cutting functionality such as interceptors etc
for (RouteDefinition route : routes) {
// skip target route as we gather ids in a separate set
if (route == target) {
continue;
}
ProcessorDefinitionHelper.gatherAllNodeIds(route, routesIds, true, false);
}
// gather all ids for the target route, but only include custom ids, and no abstract ids
// as abstract nodes is cross-cutting functionality such as interceptors etc
Set targetIds = new LinkedHashSet();
ProcessorDefinitionHelper.gatherAllNodeIds(target, targetIds, true, false);
// now check for clash with the target route
for (String id : targetIds) {
if (routesIds.contains(id)) {
return id;
}
}
return null;
}
public static void initParent(ProcessorDefinition parent) {
List> children = parent.getOutputs();
for (ProcessorDefinition child : children) {
child.setParent(parent);
if (child.getOutputs() != null && !child.getOutputs().isEmpty()) {
// recursive the children
initParent(child);
}
}
}
private static void initParentAndErrorHandlerBuilder(ProcessorDefinition parent) {
List> children = parent.getOutputs();
for (ProcessorDefinition child : children) {
child.setParent(parent);
if (child.getOutputs() != null && !child.getOutputs().isEmpty()) {
// recursive the children
initParentAndErrorHandlerBuilder(child);
}
}
}
public static void prepareRouteForInit(RouteDefinition route, List> abstracts,
List> lower) {
// filter the route into abstracts and lower
for (ProcessorDefinition output : route.getOutputs()) {
if (output.isAbstract()) {
abstracts.add(output);
} else {
lower.add(output);
}
}
}
/**
* Prepares the route.
*
* This method does not mark the route as prepared afterwards.
*
* @param context the camel context
* @param route the route
*/
public static void prepareRoute(ModelCamelContext context, RouteDefinition route) {
prepareRoute(context, route, null, null, null, null, null);
}
/**
* Prepares the route which supports context scoped features such as onException, interceptors and onCompletions
*
* This method does not mark the route as prepared afterwards.
*
* @param context the camel context
* @param route the route
* @param onExceptions optional list of onExceptions
* @param intercepts optional list of interceptors
* @param interceptFromDefinitions optional list of interceptFroms
* @param interceptSendToEndpointDefinitions optional list of interceptSendToEndpoints
* @param onCompletions optional list onCompletions
*/
public static void prepareRoute(ModelCamelContext context, RouteDefinition route,
List onExceptions,
List intercepts,
List interceptFromDefinitions,
List interceptSendToEndpointDefinitions,
List onCompletions) {
Runnable propertyPlaceholdersChangeReverter = ProcessorDefinitionHelper.createPropertyPlaceholdersChangeReverter();
try {
prepareRouteImp(context, route, onExceptions, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions, onCompletions);
} finally {
// Lets restore
propertyPlaceholdersChangeReverter.run();
}
}
/**
* Prepares the route which supports context scoped features such as onException, interceptors and onCompletions
*
* This method does not mark the route as prepared afterwards.
*
* @param context the camel context
* @param route the route
* @param onExceptions optional list of onExceptions
* @param intercepts optional list of interceptors
* @param interceptFromDefinitions optional list of interceptFroms
* @param interceptSendToEndpointDefinitions optional list of interceptSendToEndpoints
* @param onCompletions optional list onCompletions
*/
private static void prepareRouteImp(ModelCamelContext context, RouteDefinition route,
List onExceptions,
List intercepts,
List interceptFromDefinitions,
List interceptSendToEndpointDefinitions,
List onCompletions) {
// init the route inputs
initRouteInputs(context, route.getInputs());
// abstracts is the cross cutting concerns
List> abstracts = new ArrayList>();
// upper is the cross cutting concerns such as interceptors, error handlers etc
List> upper = new ArrayList>();
// lower is the regular route
List> lower = new ArrayList>();
RouteDefinitionHelper.prepareRouteForInit(route, abstracts, lower);
// parent and error handler builder should be initialized first
initParentAndErrorHandlerBuilder(context, route, abstracts, onExceptions);
// validate top-level violations
validateTopLevel(route.getOutputs());
// then interceptors
initInterceptors(context, route, abstracts, upper, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions);
// then on completion
initOnCompletions(abstracts, upper, onCompletions);
// then transactions
initTransacted(abstracts, lower);
// then on exception
initOnExceptions(abstracts, upper, onExceptions);
// rebuild route as upper + lower
route.clearOutput();
route.getOutputs().addAll(lower);
route.getOutputs().addAll(0, upper);
}
/**
* Sanity check the route, that it has input(s) and outputs.
*
* @param route the route
* @throws IllegalArgumentException is thrown if the route is invalid
*/
public static void sanityCheckRoute(RouteDefinition route) {
ObjectHelper.notNull(route, "route");
if (route.getInputs() == null || route.getInputs().isEmpty()) {
String msg = "Route has no inputs: " + route;
if (route.getId() != null) {
msg = "Route " + route.getId() + " has no inputs: " + route;
}
throw new IllegalArgumentException(msg);
}
if (route.getOutputs() == null || route.getOutputs().isEmpty()) {
String msg = "Route has no outputs: " + route;
if (route.getId() != null) {
msg = "Route " + route.getId() + " has no outputs: " + route;
}
throw new IllegalArgumentException(msg);
}
}
/**
* Validates that top-level only definitions is not added in the wrong places, such as nested
* inside a splitter etc.
*/
private static void validateTopLevel(List> children) {
for (ProcessorDefinition child : children) {
// validate that top-level is only added on the route (eg top level)
RouteDefinition route = ProcessorDefinitionHelper.getRoute(child);
boolean parentIsRoute = route != null && child.getParent() == route;
if (child.isTopLevelOnly() && !parentIsRoute) {
throw new IllegalArgumentException("The output must be added as top-level on the route. Try moving " + child + " to the top of route.");
}
if (child.getOutputs() != null && !child.getOutputs().isEmpty()) {
validateTopLevel(child.getOutputs());
}
}
}
private static void initRouteInputs(CamelContext camelContext, List inputs) {
// resolve property placeholders on route inputs which hasn't been done yet
for (FromDefinition input : inputs) {
try {
ProcessorDefinitionHelper.resolvePropertyPlaceholders(camelContext, input);
} catch (Exception e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
}
}
private static void initParentAndErrorHandlerBuilder(ModelCamelContext context, RouteDefinition route,
List> abstracts, List onExceptions) {
if (context != null) {
// let the route inherit the error handler builder from camel context if none already set
// must clone to avoid side effects while building routes using multiple RouteBuilders
ErrorHandlerBuilder builder = context.getErrorHandlerBuilder();
if (builder != null) {
builder = builder.cloneBuilder();
route.setErrorHandlerBuilderIfNull(builder);
}
}
// init parent and error handler builder on the route
initParentAndErrorHandlerBuilder(route);
// set the parent and error handler builder on the global on exceptions
if (onExceptions != null) {
for (OnExceptionDefinition global : onExceptions) {
initParentAndErrorHandlerBuilder(global);
}
}
}
private static void initOnExceptions(List> abstracts, List> upper,
List onExceptions) {
// add global on exceptions if any
if (onExceptions != null && !onExceptions.isEmpty()) {
for (OnExceptionDefinition output : onExceptions) {
// these are context scoped on exceptions so set this flag
output.setRouteScoped(false);
abstracts.add(output);
}
}
// now add onExceptions to the route
for (ProcessorDefinition output : abstracts) {
if (output instanceof OnExceptionDefinition) {
// on exceptions must be added at top, so the route flow is correct as
// on exceptions should be the first outputs
// find the index to add the on exception, it should be in the top
// but it should add itself after any existing onException
int index = 0;
for (int i = 0; i < upper.size(); i++) {
ProcessorDefinition up = upper.get(i);
if (!(up instanceof OnExceptionDefinition)) {
index = i;
break;
} else {
index++;
}
}
upper.add(index, output);
}
}
}
private static void initInterceptors(CamelContext context, RouteDefinition route,
List> abstracts, List> upper,
List intercepts,
List interceptFromDefinitions,
List interceptSendToEndpointDefinitions) {
// move the abstracts interceptors into the dedicated list
for (ProcessorDefinition processor : abstracts) {
if (processor instanceof InterceptSendToEndpointDefinition) {
if (interceptSendToEndpointDefinitions == null) {
interceptSendToEndpointDefinitions = new ArrayList();
}
interceptSendToEndpointDefinitions.add((InterceptSendToEndpointDefinition) processor);
} else if (processor instanceof InterceptFromDefinition) {
if (interceptFromDefinitions == null) {
interceptFromDefinitions = new ArrayList();
}
interceptFromDefinitions.add((InterceptFromDefinition) processor);
} else if (processor instanceof InterceptDefinition) {
if (intercepts == null) {
intercepts = new ArrayList();
}
intercepts.add((InterceptDefinition) processor);
}
}
doInitInterceptors(context, route, upper, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions);
}
private static void doInitInterceptors(CamelContext context, RouteDefinition route, List> upper,
List intercepts,
List interceptFromDefinitions,
List interceptSendToEndpointDefinitions) {
// configure intercept
if (intercepts != null && !intercepts.isEmpty()) {
for (InterceptDefinition intercept : intercepts) {
intercept.afterPropertiesSet();
// init the parent
initParent(intercept);
// add as first output so intercept is handled before the actual route and that gives
// us the needed head start to init and be able to intercept all the remaining processing steps
upper.add(0, intercept);
}
}
// configure intercept from
if (interceptFromDefinitions != null && !interceptFromDefinitions.isEmpty()) {
for (InterceptFromDefinition intercept : interceptFromDefinitions) {
// should we only apply interceptor for a given endpoint uri
boolean match = true;
if (intercept.getUri() != null) {
// the uri can have property placeholders so resolve them first
String pattern;
try {
pattern = context.resolvePropertyPlaceholders(intercept.getUri());
} catch (Exception e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
boolean isRefPattern = pattern.startsWith("ref*") || pattern.startsWith("ref:");
match = false;
for (FromDefinition input : route.getInputs()) {
// a bit more logic to lookup the endpoint as it can be uri/ref based
String uri = input.getUri();
// if the pattern is not a ref itself, then resolve the ref uris, so we can match the actual uri's with each other
if (!isRefPattern) {
if (uri != null && uri.startsWith("ref:")) {
// its a ref: so lookup the endpoint to get its url
String ref = uri.substring(4);
uri = CamelContextHelper.getMandatoryEndpoint(context, ref).getEndpointUri();
} else if (input.getRef() != null) {
// lookup the endpoint to get its url
uri = CamelContextHelper.getMandatoryEndpoint(context, input.getRef()).getEndpointUri();
}
}
if (EndpointHelper.matchEndpoint(context, uri, pattern)) {
match = true;
break;
}
}
}
if (match) {
intercept.afterPropertiesSet();
// init the parent
initParent(intercept);
// add as first output so intercept is handled before the actual route and that gives
// us the needed head start to init and be able to intercept all the remaining processing steps
upper.add(0, intercept);
}
}
}
// configure intercept send to endpoint
if (interceptSendToEndpointDefinitions != null && !interceptSendToEndpointDefinitions.isEmpty()) {
for (InterceptSendToEndpointDefinition intercept : interceptSendToEndpointDefinitions) {
intercept.afterPropertiesSet();
// init the parent
initParent(intercept);
// add as first output so intercept is handled before the actual route and that gives
// us the needed head start to init and be able to intercept all the remaining processing steps
upper.add(0, intercept);
}
}
}
private static void initOnCompletions(List> abstracts, List> upper,
List onCompletions) {
List completions = new ArrayList();
// find the route scoped onCompletions
for (ProcessorDefinition out : abstracts) {
if (out instanceof OnCompletionDefinition) {
completions.add((OnCompletionDefinition) out);
}
}
// only add global onCompletion if there are no route already
if (completions.isEmpty() && onCompletions != null) {
completions = onCompletions;
// init the parent
for (OnCompletionDefinition global : completions) {
initParent(global);
}
}
// are there any completions to init at all?
if (completions.isEmpty()) {
return;
}
upper.addAll(completions);
}
private static void initTransacted(List> abstracts, List> lower) {
TransactedDefinition transacted = null;
// add to correct type
for (ProcessorDefinition type : abstracts) {
if (type instanceof TransactedDefinition) {
if (transacted == null) {
transacted = (TransactedDefinition) type;
} else {
throw new IllegalArgumentException("The route can only have one transacted defined");
}
}
}
if (transacted != null) {
// the outputs should be moved to the transacted policy
transacted.getOutputs().addAll(lower);
// and add it as the single output
lower.clear();
lower.add(transacted);
}
}
/**
* Force assigning ids to the give node and all its children (recursively).
*
* This is needed when doing tracing or the likes, where each node should have its id assigned
* so the tracing can pin point exactly.
*
* @param context the camel context
* @param processor the node
*/
public static void forceAssignIds(CamelContext context, final ProcessorDefinition processor) {
// force id on the child
processor.idOrCreate(context.getNodeIdFactory());
// if there was a custom id assigned, then make sure to support property placeholders
if (processor.hasCustomIdAssigned()) {
try {
final String originalId = processor.getId();
String id = context.resolvePropertyPlaceholders(originalId);
// only set id if its changed, such as we did property placeholder
if (!originalId.equals(id)) {
processor.setId(id);
ProcessorDefinitionHelper.addPropertyPlaceholdersChangeRevertAction(new Runnable() {
@Override
public void run() {
processor.setId(originalId);
}
});
}
} catch (Exception e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
}
List> children = processor.getOutputs();
if (children != null && !children.isEmpty()) {
for (ProcessorDefinition child : children) {
forceAssignIds(context, child);
}
}
}
}