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