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}