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.builder;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.apache.camel.CamelContext;
023import org.apache.camel.Endpoint;
024import org.apache.camel.ExtendedCamelContext;
025import org.apache.camel.impl.engine.InterceptSendToMockEndpointStrategy;
026import org.apache.camel.model.ModelCamelContext;
027import org.apache.camel.model.ProcessorDefinition;
028import org.apache.camel.model.RouteDefinition;
029import org.apache.camel.reifier.RouteReifier;
030import org.apache.camel.support.EndpointHelper;
031import org.apache.camel.support.PatternHelper;
032import org.apache.camel.util.ObjectHelper;
033import org.apache.camel.util.function.ThrowingConsumer;
034
035/**
036 * A {@link RouteBuilder} which has extended capabilities when using the
037 * <a href="http://camel.apache.org/advicewith.html">advice with</a> feature.
038 * <p/>
039 * <b>Important:</b> It is recommended to only advice a given route once (you
040 * can of course advice multiple routes). If you do it multiple times, then it
041 * may not work as expected, especially when any kind of error handling is
042 * involved.
043 */
044public abstract class AdviceWithRouteBuilder extends RouteBuilder {
045
046    private RouteDefinition originalRoute;
047    private final List<AdviceWithTask> adviceWithTasks = new ArrayList<>();
048    private boolean logRouteAsXml = true;
049
050    public AdviceWithRouteBuilder() {
051    }
052
053    public AdviceWithRouteBuilder(CamelContext context) {
054        super(context);
055    }
056
057    /**
058     * Advices this route with the route builder using a lambda expression. It
059     * can be used as following:
060     * 
061     * <pre>
062     * AdviceWithRouteBuilder.adviceWith(context, "myRoute", a ->
063     *     a.weaveAddLast().to("mock:result");
064     * </pre>
065     * <p/>
066     * <b>Important:</b> It is recommended to only advice a given route once
067     * (you can of course advice multiple routes). If you do it multiple times,
068     * then it may not work as expected, especially when any kind of error
069     * handling is involved. The Camel team plan for Camel 3.0 to support this
070     * as internal refactorings in the routing engine is needed to support this
071     * properly.
072     * <p/>
073     * The advice process will add the interceptors, on exceptions, on
074     * completions etc. configured from the route builder to this route.
075     * <p/>
076     * This is mostly used for testing purpose to add interceptors and the likes
077     * to an existing route.
078     * <p/>
079     * Will stop and remove the old route from camel context and add and start
080     * this new advised route.
081     *
082     * @param camelContext the camel context
083     * @param routeId either the route id as a string value, or <tt>null</tt> to
084     *            chose the 1st route, or you can specify a number for the n'th
085     *            route, or provide the route definition instance directly as well.
086     * @param builder the advice with route builder
087     * @return a new route which is this route merged with the route builder
088     * @throws Exception can be thrown from the route builder
089     */
090    public static RouteDefinition adviceWith(CamelContext camelContext, Object routeId, ThrowingConsumer<AdviceWithRouteBuilder, Exception> builder) throws Exception {
091        ModelCamelContext mcc = camelContext.adapt(ModelCamelContext.class);
092        if (mcc.getRouteDefinitions().isEmpty()) {
093            throw new IllegalArgumentException("Cannot advice route as there are no routes");
094        }
095
096        RouteDefinition rd;
097        if (routeId instanceof RouteDefinition) {
098            rd = (RouteDefinition) routeId;
099        } else {
100            String id = mcc.getTypeConverter().convertTo(String.class, routeId);
101            if (id != null) {
102                rd = mcc.getRouteDefinition(id);
103                if (rd == null) {
104                    // okay it may be a number
105                    Integer num = mcc.getTypeConverter().tryConvertTo(Integer.class, routeId);
106                    if (num != null) {
107                        rd = mcc.getRouteDefinitions().get(num);
108                    }
109                }
110                if (rd == null) {
111                    throw new IllegalArgumentException("Cannot advice route as route with id: " + routeId + " does not exists");
112                }
113            } else {
114                // grab first route
115                rd = mcc.getRouteDefinitions().get(0);
116            }
117        }
118
119        return RouteReifier.adviceWith(rd, camelContext, new AdviceWithRouteBuilder() {
120            @Override
121            public void configure() throws Exception {
122                if (builder instanceof AdviceWithRouteBuilder) {
123                    setLogRouteAsXml(((AdviceWithRouteBuilder) builder).isLogRouteAsXml());
124                }
125                builder.accept(this);
126            }
127        });
128    }
129
130    /**
131     * Sets the original route to be adviced.
132     *
133     * @param originalRoute the original route.
134     */
135    public void setOriginalRoute(RouteDefinition originalRoute) {
136        this.originalRoute = originalRoute;
137    }
138
139    /**
140     * Gets the original route to be adviced.
141     *
142     * @return the original route.
143     */
144    public RouteDefinition getOriginalRoute() {
145        return originalRoute;
146    }
147
148    /**
149     * Whether to log the adviced routes before/after as XML. This is usable to
150     * know how the route was adviced and changed. However marshalling the route
151     * model to XML costs CPU resources and you can then turn this off by not
152     * logging. This is default enabled.
153     */
154    public boolean isLogRouteAsXml() {
155        return logRouteAsXml;
156    }
157
158    /**
159     * Sets whether to log the adviced routes before/after as XML. This is
160     * usable to know how the route was adviced and changed. However marshalling
161     * the route model to XML costs CPU resources and you can then turn this off
162     * by not logging. This is default enabled.
163     */
164    public void setLogRouteAsXml(boolean logRouteAsXml) {
165        this.logRouteAsXml = logRouteAsXml;
166    }
167
168    /**
169     * Gets a list of additional tasks to execute after the {@link #configure()}
170     * method has been executed during the advice process.
171     *
172     * @return a list of additional {@link AdviceWithTask} tasks to be executed
173     *         during the advice process.
174     */
175    public List<AdviceWithTask> getAdviceWithTasks() {
176        return adviceWithTasks;
177    }
178
179    /**
180     * Mock all endpoints.
181     *
182     * @throws Exception can be thrown if error occurred
183     */
184    public void mockEndpoints() throws Exception {
185        getContext().adapt(ExtendedCamelContext.class).registerEndpointCallback(new InterceptSendToMockEndpointStrategy(null));
186    }
187
188    /**
189     * Mock all endpoints matching the given pattern.
190     *
191     * @param pattern the pattern(s).
192     * @throws Exception can be thrown if error occurred
193     * @see EndpointHelper#matchEndpoint(org.apache.camel.CamelContext, String,
194     *      String)
195     */
196    public void mockEndpoints(String... pattern) throws Exception {
197        for (String s : pattern) {
198            getContext().adapt(ExtendedCamelContext.class).registerEndpointCallback(new InterceptSendToMockEndpointStrategy(s));
199        }
200    }
201
202    /**
203     * Mock all endpoints matching the given pattern, and <b>skips</b> sending
204     * to the original endpoint (detour messages).
205     *
206     * @param pattern the pattern(s).
207     * @throws Exception can be thrown if error occurred
208     * @see EndpointHelper#matchEndpoint(org.apache.camel.CamelContext, String,
209     *      String)
210     */
211    public void mockEndpointsAndSkip(String... pattern) throws Exception {
212        for (String s : pattern) {
213            getContext().adapt(ExtendedCamelContext.class).registerEndpointCallback(new InterceptSendToMockEndpointStrategy(s, true));
214        }
215    }
216
217    /**
218     * Replaces the route from endpoint with a new uri
219     *
220     * @param uri uri of the new endpoint
221     */
222    public void replaceFromWith(String uri) {
223        ObjectHelper.notNull(originalRoute, "originalRoute", this);
224        getAdviceWithTasks().add(AdviceWithTasks.replaceFromWith(originalRoute, uri));
225    }
226
227    /**
228     * Replaces the route from endpoint with a new endpoint
229     *
230     * @param endpoint the new endpoint
231     */
232    public void replaceFromWith(Endpoint endpoint) {
233        ObjectHelper.notNull(originalRoute, "originalRoute", this);
234        getAdviceWithTasks().add(AdviceWithTasks.replaceFrom(originalRoute, endpoint));
235    }
236
237    /**
238     * Weaves by matching id of the nodes in the route (incl onException etc).
239     * <p/>
240     * Uses the {@link PatternHelper#matchPattern(String, String)} matching
241     * algorithm.
242     *
243     * @param pattern the pattern
244     * @return the builder
245     * @see PatternHelper#matchPattern(String, String)
246     */
247    public <T extends ProcessorDefinition<?>> AdviceWithBuilder<T> weaveById(String pattern) {
248        ObjectHelper.notNull(originalRoute, "originalRoute", this);
249        return new AdviceWithBuilder<>(this, pattern, null, null, null);
250    }
251
252    /**
253     * Weaves by matching the to string representation of the nodes in the route
254     * (incl onException etc).
255     * <p/>
256     * Uses the {@link PatternHelper#matchPattern(String, String)} matching
257     * algorithm.
258     *
259     * @param pattern the pattern
260     * @return the builder
261     * @see PatternHelper#matchPattern(String, String)
262     */
263    public <T extends ProcessorDefinition<?>> AdviceWithBuilder<T> weaveByToString(String pattern) {
264        ObjectHelper.notNull(originalRoute, "originalRoute", this);
265        return new AdviceWithBuilder<>(this, null, pattern, null, null);
266    }
267
268    /**
269     * Weaves by matching sending to endpoints with the given uri of the nodes
270     * in the route (incl onException etc).
271     * <p/>
272     * Uses the {@link PatternHelper#matchPattern(String, String)} matching
273     * algorithm.
274     *
275     * @param pattern the pattern
276     * @return the builder
277     * @see PatternHelper#matchPattern(String, String)
278     */
279    public <T extends ProcessorDefinition<?>> AdviceWithBuilder<T> weaveByToUri(String pattern) {
280        ObjectHelper.notNull(originalRoute, "originalRoute", this);
281        return new AdviceWithBuilder<>(this, null, null, pattern, null);
282    }
283
284    /**
285     * Weaves by matching type of the nodes in the route (incl onException etc).
286     *
287     * @param type the processor type
288     * @return the builder
289     */
290    public <T extends ProcessorDefinition<?>> AdviceWithBuilder<T> weaveByType(Class<T> type) {
291        ObjectHelper.notNull(originalRoute, "originalRoute", this);
292        return new AdviceWithBuilder<>(this, null, null, null, type);
293    }
294
295    /**
296     * Weaves by adding the nodes to the start of the route (excl onException
297     * etc).
298     *
299     * @return the builder
300     */
301    public <T extends ProcessorDefinition<?>> ProcessorDefinition<?> weaveAddFirst() {
302        ObjectHelper.notNull(originalRoute, "originalRoute", this);
303        return new AdviceWithBuilder<T>(this, "*", null, null, null).selectFirst().before();
304    }
305
306    /**
307     * Weaves by adding the nodes to the end of the route (excl onException
308     * etc).
309     *
310     * @return the builder
311     */
312    public <T extends ProcessorDefinition<?>> ProcessorDefinition<?> weaveAddLast() {
313        ObjectHelper.notNull(originalRoute, "originalRoute", this);
314        return new AdviceWithBuilder<T>(this, "*", null, null, null).maxDeep(1).selectLast().after();
315    }
316
317}