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