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