001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.reifier;
018
019import java.util.ArrayList;
020import java.util.List;
021import java.util.StringTokenizer;
022
023import org.apache.camel.CamelContext;
024import org.apache.camel.Endpoint;
025import org.apache.camel.ExtendedCamelContext;
026import org.apache.camel.FailedToCreateRouteException;
027import org.apache.camel.NoSuchEndpointException;
028import org.apache.camel.Processor;
029import org.apache.camel.Route;
030import org.apache.camel.RuntimeCamelException;
031import org.apache.camel.builder.AdviceWithRouteBuilder;
032import org.apache.camel.builder.AdviceWithTask;
033import org.apache.camel.builder.EndpointConsumerBuilder;
034import org.apache.camel.builder.RouteBuilder;
035import org.apache.camel.model.Model;
036import org.apache.camel.model.ModelHelper;
037import org.apache.camel.model.ProcessorDefinition;
038import org.apache.camel.model.ProcessorDefinitionHelper;
039import org.apache.camel.model.PropertyDefinition;
040import org.apache.camel.model.RouteDefinition;
041import org.apache.camel.model.RoutesDefinition;
042import org.apache.camel.processor.ContractAdvice;
043import org.apache.camel.reifier.rest.RestBindingReifier;
044import org.apache.camel.spi.Contract;
045import org.apache.camel.spi.LifecycleStrategy;
046import org.apache.camel.spi.RouteContext;
047import org.apache.camel.spi.RoutePolicy;
048import org.apache.camel.spi.RoutePolicyFactory;
049import org.apache.camel.support.CamelContextHelper;
050import org.apache.camel.util.ObjectHelper;
051
052public class RouteReifier extends ProcessorReifier<RouteDefinition> {
053
054    public RouteReifier(ProcessorDefinition<?> definition) {
055        super((RouteDefinition)definition);
056    }
057
058    /**
059     * Advices this route with the route builder.
060     * <p/>
061     * <b>Important:</b> It is recommended to only advice a given route once
062     * (you can of course advice multiple routes). If you do it multiple times,
063     * then it may not work as expected, especially when any kind of error
064     * handling is involved. The Camel team plan for Camel 3.0 to support this
065     * as internal refactorings in the routing engine is needed to support this
066     * properly.
067     * <p/>
068     * You can use a regular {@link RouteBuilder} but the specialized
069     * {@link AdviceWithRouteBuilder} has additional features when using the
070     * <a href="http://camel.apache.org/advicewith.html">advice with</a>
071     * feature. We therefore suggest you to use the
072     * {@link AdviceWithRouteBuilder}.
073     * <p/>
074     * The advice process will add the interceptors, on exceptions, on
075     * completions etc. configured from the route builder to this route.
076     * <p/>
077     * This is mostly used for testing purpose to add interceptors and the likes
078     * to an existing route.
079     * <p/>
080     * Will stop and remove the old route from camel context and add and start
081     * this new advised route.
082     *
083     * @param definition the model definition
084     * @param camelContext the camel context
085     * @param builder the route builder
086     * @return a new route which is this route merged with the route builder
087     * @throws Exception can be thrown from the route builder
088     * @see AdviceWithRouteBuilder
089     */
090    public static RouteDefinition adviceWith(RouteDefinition definition, CamelContext camelContext, RouteBuilder builder) throws Exception {
091        return new RouteReifier(definition).adviceWith(camelContext, builder);
092    }
093
094    @Override
095    public Processor createProcessor(RouteContext routeContext) throws Exception {
096        throw new UnsupportedOperationException("Not implemented for RouteDefinition");
097    }
098
099    public Route createRoute(CamelContext camelContext, RouteContext routeContext) {
100        try {
101            return doCreateRoute(camelContext, routeContext);
102        } catch (FailedToCreateRouteException e) {
103            throw e;
104        } catch (Exception e) {
105            // wrap in exception which provide more details about which route
106            // was failing
107            throw new FailedToCreateRouteException(definition.getId(), definition.toString(), e);
108        }
109    }
110
111    public Endpoint resolveEndpoint(CamelContext camelContext, String uri) throws NoSuchEndpointException {
112        ObjectHelper.notNull(camelContext, "CamelContext");
113        return CamelContextHelper.getMandatoryEndpoint(camelContext, uri);
114    }
115
116    /**
117     * Advices this route with the route builder.
118     * <p/>
119     * <b>Important:</b> It is recommended to only advice a given route once
120     * (you can of course advice multiple routes). If you do it multiple times,
121     * then it may not work as expected, especially when any kind of error
122     * handling is involved. The Camel team plan for Camel 3.0 to support this
123     * as internal refactorings in the routing engine is needed to support this
124     * properly.
125     * <p/>
126     * You can use a regular {@link RouteBuilder} but the specialized
127     * {@link org.apache.camel.builder.AdviceWithRouteBuilder} has additional
128     * features when using the
129     * <a href="http://camel.apache.org/advicewith.html">advice with</a>
130     * feature. We therefore suggest you to use the
131     * {@link org.apache.camel.builder.AdviceWithRouteBuilder}.
132     * <p/>
133     * The advice process will add the interceptors, on exceptions, on
134     * completions etc. configured from the route builder to this route.
135     * <p/>
136     * This is mostly used for testing purpose to add interceptors and the likes
137     * to an existing route.
138     * <p/>
139     * Will stop and remove the old route from camel context and add and start
140     * this new advised route.
141     *
142     * @param camelContext the camel context
143     * @param builder the route builder
144     * @return a new route which is this route merged with the route builder
145     * @throws Exception can be thrown from the route builder
146     * @see AdviceWithRouteBuilder
147     */
148    @SuppressWarnings("deprecation")
149    public RouteDefinition adviceWith(CamelContext camelContext, RouteBuilder builder) throws Exception {
150        ObjectHelper.notNull(camelContext, "CamelContext");
151        ObjectHelper.notNull(builder, "RouteBuilder");
152
153        log.debug("AdviceWith route before: {}", this);
154
155        // inject this route into the advice route builder so it can access this
156        // route
157        // and offer features to manipulate the route directly
158        boolean logRoutesAsXml = true;
159        if (builder instanceof AdviceWithRouteBuilder) {
160            AdviceWithRouteBuilder arb = (AdviceWithRouteBuilder)builder;
161            arb.setOriginalRoute(definition);
162            logRoutesAsXml = arb.isLogRouteAsXml();
163        }
164
165        // configure and prepare the routes from the builder
166        RoutesDefinition routes = builder.configureRoutes(camelContext);
167
168        log.debug("AdviceWith routes: {}", routes);
169
170        // we can only advice with a route builder without any routes
171        if (!builder.getRouteCollection().getRoutes().isEmpty()) {
172            throw new IllegalArgumentException("You can only advice from a RouteBuilder which has no existing routes." + " Remove all routes from the route builder.");
173        }
174        // we can not advice with error handlers (if you added a new error
175        // handler in the route builder)
176        // we must check the error handler on builder is not the same as on
177        // camel context, as that would be the default
178        // context scoped error handler, in case no error handlers was
179        // configured
180        if (builder.getRouteCollection().getErrorHandlerFactory() != null
181            && camelContext.adapt(ExtendedCamelContext.class).getErrorHandlerFactory() != builder.getRouteCollection().getErrorHandlerFactory()) {
182            throw new IllegalArgumentException("You can not advice with error handlers. Remove the error handlers from the route builder.");
183        }
184
185        String beforeAsXml = null;
186        if (logRoutesAsXml && log.isInfoEnabled()) {
187            beforeAsXml = ModelHelper.dumpModelAsXml(camelContext, definition);
188        }
189
190        // stop and remove this existing route
191        camelContext.getExtension(Model.class).removeRouteDefinition(definition);
192
193        // any advice with tasks we should execute first?
194        if (builder instanceof AdviceWithRouteBuilder) {
195            List<AdviceWithTask> tasks = ((AdviceWithRouteBuilder)builder).getAdviceWithTasks();
196            for (AdviceWithTask task : tasks) {
197                task.task();
198            }
199        }
200
201        // now merge which also ensures that interceptors and the likes get
202        // mixed in correctly as well
203        RouteDefinition merged = routes.route(definition);
204
205        // add the new merged route
206        camelContext.getExtension(Model.class).getRouteDefinitions().add(0, merged);
207
208        // log the merged route at info level to make it easier to end users to
209        // spot any mistakes they may have made
210        if (log.isInfoEnabled()) {
211            log.info("AdviceWith route after: {}", merged);
212        }
213
214        if (logRoutesAsXml && log.isInfoEnabled()) {
215            String afterAsXml = ModelHelper.dumpModelAsXml(camelContext, merged);
216            log.info("Adviced route before/after as XML:\n{}\n{}", beforeAsXml, afterAsXml);
217        }
218
219        // If the camel context is started then we start the route
220        if (camelContext.isStarted()) {
221            camelContext.getExtension(Model.class).addRouteDefinition(merged);
222        }
223        return merged;
224    }
225
226    // Implementation methods
227    // -------------------------------------------------------------------------
228    protected Route doCreateRoute(CamelContext camelContext, RouteContext routeContext) throws Exception {
229        // configure error handler
230        routeContext.setErrorHandlerFactory(definition.getErrorHandlerFactory());
231
232        // configure tracing
233        if (definition.getTrace() != null) {
234            Boolean isTrace = CamelContextHelper.parseBoolean(camelContext, definition.getTrace());
235            if (isTrace != null) {
236                routeContext.setTracing(isTrace);
237                if (isTrace) {
238                    log.debug("Tracing is enabled on route: {}", definition.getId());
239                    // tracing is added in the DefaultChannel so we can enable
240                    // it on the fly
241                }
242            }
243        }
244
245        // configure message history
246        if (definition.getMessageHistory() != null) {
247            Boolean isMessageHistory = CamelContextHelper.parseBoolean(camelContext, definition.getMessageHistory());
248            if (isMessageHistory != null) {
249                routeContext.setMessageHistory(isMessageHistory);
250                if (isMessageHistory) {
251                    log.debug("Message history is enabled on route: {}", definition.getId());
252                }
253            }
254        }
255
256        // configure Log EIP mask
257        if (definition.getLogMask() != null) {
258            Boolean isLogMask = CamelContextHelper.parseBoolean(camelContext, definition.getLogMask());
259            if (isLogMask != null) {
260                routeContext.setLogMask(isLogMask);
261                if (isLogMask) {
262                    log.debug("Security mask for Logging is enabled on route: {}", definition.getId());
263                }
264            }
265        }
266
267        // configure stream caching
268        if (definition.getStreamCache() != null) {
269            Boolean isStreamCache = CamelContextHelper.parseBoolean(camelContext, definition.getStreamCache());
270            if (isStreamCache != null) {
271                routeContext.setStreamCaching(isStreamCache);
272                if (isStreamCache) {
273                    log.debug("StreamCaching is enabled on route: {}", definition.getId());
274                }
275            }
276        }
277
278        // configure delayer
279        if (definition.getDelayer() != null) {
280            Long delayer = CamelContextHelper.parseLong(camelContext, definition.getDelayer());
281            if (delayer != null) {
282                routeContext.setDelayer(delayer);
283                if (delayer > 0) {
284                    log.debug("Delayer is enabled with: {} ms. on route: {}", delayer, definition.getId());
285                } else {
286                    log.debug("Delayer is disabled on route: {}", definition.getId());
287                }
288            }
289        }
290
291        // configure route policy
292        if (definition.getRoutePolicies() != null && !definition.getRoutePolicies().isEmpty()) {
293            for (RoutePolicy policy : definition.getRoutePolicies()) {
294                log.debug("RoutePolicy is enabled: {} on route: {}", policy, definition.getId());
295                routeContext.getRoutePolicyList().add(policy);
296            }
297        }
298        if (definition.getRoutePolicyRef() != null) {
299            StringTokenizer policyTokens = new StringTokenizer(definition.getRoutePolicyRef(), ",");
300            while (policyTokens.hasMoreTokens()) {
301                String ref = policyTokens.nextToken().trim();
302                RoutePolicy policy = CamelContextHelper.mandatoryLookup(camelContext, ref, RoutePolicy.class);
303                log.debug("RoutePolicy is enabled: {} on route: {}", policy, definition.getId());
304                routeContext.getRoutePolicyList().add(policy);
305            }
306        }
307        if (camelContext.getRoutePolicyFactories() != null) {
308            for (RoutePolicyFactory factory : camelContext.getRoutePolicyFactories()) {
309                RoutePolicy policy = factory.createRoutePolicy(camelContext, definition.getId(), definition);
310                if (policy != null) {
311                    log.debug("RoutePolicy is enabled: {} on route: {}", policy, definition.getId());
312                    routeContext.getRoutePolicyList().add(policy);
313                }
314            }
315        }
316
317        // configure auto startup
318        Boolean isAutoStartup = CamelContextHelper.parseBoolean(camelContext, definition.getAutoStartup());
319        if (isAutoStartup != null) {
320            log.debug("Using AutoStartup {} on route: {}", isAutoStartup, definition.getId());
321            routeContext.setAutoStartup(isAutoStartup);
322        }
323
324        // configure startup order
325        if (definition.getStartupOrder() != null) {
326            routeContext.setStartupOrder(definition.getStartupOrder());
327        }
328
329        // configure shutdown
330        if (definition.getShutdownRoute() != null) {
331            log.debug("Using ShutdownRoute {} on route: {}", definition.getShutdownRoute(), definition.getId());
332            routeContext.setShutdownRoute(definition.getShutdownRoute());
333        }
334        if (definition.getShutdownRunningTask() != null) {
335            log.debug("Using ShutdownRunningTask {} on route: {}", definition.getShutdownRunningTask(), definition.getId());
336            routeContext.setShutdownRunningTask(definition.getShutdownRunningTask());
337        }
338
339        // should inherit the intercept strategies we have defined
340        routeContext.setInterceptStrategies(definition.getInterceptStrategies());
341
342        // resolve endpoint
343        Endpoint endpoint = definition.getInput().getEndpoint();
344        if (endpoint == null) {
345            EndpointConsumerBuilder def = definition.getInput().getEndpointConsumerBuilder();
346            if (def != null) {
347                endpoint = def.resolve(routeContext.getCamelContext());
348            } else {
349                endpoint = routeContext.resolveEndpoint(definition.getInput().getEndpointUri());
350            }
351        }
352        routeContext.setEndpoint(endpoint);
353
354        // notify route context created
355        for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
356            strategy.onRouteContextCreate(routeContext);
357        }
358
359        // validate route has output processors
360        if (!ProcessorDefinitionHelper.hasOutputs(definition.getOutputs(), true)) {
361            String at = definition.getInput().toString();
362            Exception cause = new IllegalArgumentException("Route " + definition.getId() + " has no output processors."
363                                                           + " You need to add outputs to the route such as to(\"log:foo\").");
364            throw new FailedToCreateRouteException(definition.getId(), definition.toString(), at, cause);
365        }
366
367        List<ProcessorDefinition<?>> list = new ArrayList<>(definition.getOutputs());
368        for (ProcessorDefinition<?> output : list) {
369            try {
370                ProcessorReifier.reifier(output).addRoutes(routeContext);
371            } catch (Exception e) {
372                throw new FailedToCreateRouteException(definition.getId(), definition.toString(), output.toString(), e);
373            }
374        }
375
376        if (definition.getRestBindingDefinition() != null) {
377            try {
378                routeContext.addAdvice(new RestBindingReifier(definition.getRestBindingDefinition()).createRestBindingAdvice(routeContext));
379            } catch (Exception e) {
380                throw RuntimeCamelException.wrapRuntimeCamelException(e);
381            }
382        }
383
384        // wrap in contract
385        if (definition.getInputType() != null || definition.getOutputType() != null) {
386            Contract contract = new Contract();
387            if (definition.getInputType() != null) {
388                contract.setInputType(definition.getInputType().getUrn());
389                contract.setValidateInput(definition.getInputType().isValidate());
390            }
391            if (definition.getOutputType() != null) {
392                contract.setOutputType(definition.getOutputType().getUrn());
393                contract.setValidateOutput(definition.getOutputType().isValidate());
394            }
395            routeContext.addAdvice(new ContractAdvice(contract));
396            // make sure to enable data type as its in use when using
397            // input/output types on routes
398            camelContext.setUseDataType(true);
399        }
400
401        // Set route properties
402        routeContext.addProperty(Route.ID_PROPERTY, definition.getId());
403        routeContext.addProperty(Route.CUSTOM_ID_PROPERTY, definition.hasCustomIdAssigned() ? "true" : "false");
404        routeContext.addProperty(Route.PARENT_PROPERTY, Integer.toHexString(definition.hashCode()));
405        routeContext.addProperty(Route.DESCRIPTION_PROPERTY, definition.getDescriptionText());
406        if (definition.getGroup() != null) {
407            routeContext.addProperty(Route.GROUP_PROPERTY, definition.getGroup());
408        }
409        String rest = Boolean.toString(definition.isRest() != null && definition.isRest());
410        routeContext.addProperty(Route.REST_PROPERTY, rest);
411
412        List<PropertyDefinition> properties = definition.getRouteProperties();
413        if (properties != null) {
414            final String[] reservedProperties = new String[] {Route.ID_PROPERTY, Route.CUSTOM_ID_PROPERTY, Route.PARENT_PROPERTY, Route.DESCRIPTION_PROPERTY, Route.GROUP_PROPERTY,
415                                                              Route.REST_PROPERTY};
416
417            for (PropertyDefinition prop : properties) {
418                try {
419                    final String key = CamelContextHelper.parseText(camelContext, prop.getKey());
420                    final String val = CamelContextHelper.parseText(camelContext, prop.getValue());
421
422                    for (String property : reservedProperties) {
423                        if (property.equalsIgnoreCase(key)) {
424                            throw new IllegalArgumentException("Cannot set route property " + property + " as it is a reserved property");
425                        }
426                    }
427
428                    routeContext.addProperty(key, val);
429                } catch (Exception e) {
430                    throw RuntimeCamelException.wrapRuntimeCamelException(e);
431                }
432            }
433        }
434
435        return routeContext.commit();
436    }
437
438}