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}