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.Iterator; 021import java.util.List; 022 023import org.apache.camel.Endpoint; 024import org.apache.camel.model.AdviceWithDefinition; 025import org.apache.camel.model.ChoiceDefinition; 026import org.apache.camel.model.EndpointRequiredDefinition; 027import org.apache.camel.model.FromDefinition; 028import org.apache.camel.model.InterceptDefinition; 029import org.apache.camel.model.InterceptSendToEndpointDefinition; 030import org.apache.camel.model.OnCompletionDefinition; 031import org.apache.camel.model.OnExceptionDefinition; 032import org.apache.camel.model.PipelineDefinition; 033import org.apache.camel.model.ProcessorDefinition; 034import org.apache.camel.model.ProcessorDefinitionHelper; 035import org.apache.camel.model.RouteDefinition; 036import org.apache.camel.model.TransactedDefinition; 037import org.apache.camel.support.PatternHelper; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041/** 042 * {@link AdviceWithTask} tasks which are used by the 043 * {@link AdviceWithRouteBuilder}. 044 */ 045public final class AdviceWithTasks { 046 047 private static final Logger LOG = LoggerFactory.getLogger(AdviceWithTasks.class); 048 049 private AdviceWithTasks() { 050 // utility class 051 } 052 053 /** 054 * Match by is used for pluggable match by logic. 055 */ 056 private interface MatchBy { 057 058 String getId(); 059 060 boolean match(ProcessorDefinition<?> processor); 061 } 062 063 /** 064 * Will match by id of the processor. 065 */ 066 private static final class MatchById implements MatchBy { 067 068 private final String id; 069 070 private MatchById(String id) { 071 this.id = id; 072 } 073 074 @Override 075 public String getId() { 076 return id; 077 } 078 079 @Override 080 public boolean match(ProcessorDefinition<?> processor) { 081 if (id.equals("*")) { 082 // make sure the processor which id isn't be set is matched. 083 return true; 084 } 085 return PatternHelper.matchPattern(processor.getId(), id); 086 } 087 } 088 089 /** 090 * Will match by the to string representation of the processor. 091 */ 092 private static final class MatchByToString implements MatchBy { 093 094 private final String toString; 095 096 private MatchByToString(String toString) { 097 this.toString = toString; 098 } 099 100 @Override 101 public String getId() { 102 return toString; 103 } 104 105 @Override 106 public boolean match(ProcessorDefinition<?> processor) { 107 return PatternHelper.matchPattern(processor.toString(), toString); 108 } 109 } 110 111 /** 112 * Will match by the sending to endpoint uri representation of the 113 * processor. 114 */ 115 private static final class MatchByToUri implements MatchBy { 116 117 private final String toUri; 118 119 private MatchByToUri(String toUri) { 120 this.toUri = toUri; 121 } 122 123 @Override 124 public String getId() { 125 return toUri; 126 } 127 128 @Override 129 public boolean match(ProcessorDefinition<?> processor) { 130 if (processor instanceof EndpointRequiredDefinition) { 131 String uri = ((EndpointRequiredDefinition)processor).getEndpointUri(); 132 return PatternHelper.matchPattern(uri, toUri); 133 } 134 return false; 135 } 136 } 137 138 /** 139 * Will match by the type of the processor. 140 */ 141 private static final class MatchByType implements MatchBy { 142 143 private final Class<?> type; 144 145 private MatchByType(Class<?> type) { 146 this.type = type; 147 } 148 149 @Override 150 public String getId() { 151 return type.getSimpleName(); 152 } 153 154 @Override 155 public boolean match(ProcessorDefinition<?> processor) { 156 return type.isAssignableFrom(processor.getClass()); 157 } 158 } 159 160 public static AdviceWithTask replaceByToString(final RouteDefinition route, final String toString, final ProcessorDefinition<?> replace, boolean selectFirst, 161 boolean selectLast, int selectFrom, int selectTo, int maxDeep) { 162 MatchBy matchBy = new MatchByToString(toString); 163 return doReplace(route, matchBy, replace, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 164 } 165 166 public static AdviceWithTask replaceByToUri(final RouteDefinition route, final String toUri, final ProcessorDefinition<?> replace, boolean selectFirst, boolean selectLast, 167 int selectFrom, int selectTo, int maxDeep) { 168 MatchBy matchBy = new MatchByToUri(toUri); 169 return doReplace(route, matchBy, replace, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 170 } 171 172 public static AdviceWithTask replaceById(final RouteDefinition route, final String id, final ProcessorDefinition<?> replace, boolean selectFirst, boolean selectLast, 173 int selectFrom, int selectTo, int maxDeep) { 174 MatchBy matchBy = new MatchById(id); 175 return doReplace(route, matchBy, replace, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 176 } 177 178 public static AdviceWithTask replaceByType(final RouteDefinition route, final Class<?> type, final ProcessorDefinition<?> replace, boolean selectFirst, boolean selectLast, 179 int selectFrom, int selectTo, int maxDeep) { 180 MatchBy matchBy = new MatchByType(type); 181 return doReplace(route, matchBy, replace, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 182 } 183 184 private static AdviceWithTask doReplace(final RouteDefinition route, final MatchBy matchBy, final ProcessorDefinition<?> replace, boolean selectFirst, boolean selectLast, 185 int selectFrom, int selectTo, int maxDeep) { 186 return new AdviceWithTask() { 187 public void task() throws Exception { 188 Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 189 boolean match = false; 190 while (it.hasNext()) { 191 ProcessorDefinition<?> output = it.next(); 192 if (matchBy.match(output)) { 193 List<ProcessorDefinition<?>> outputs = getOutputs(output, route); 194 if (outputs != null) { 195 int index = outputs.indexOf(output); 196 if (index != -1) { 197 match = true; 198 // flattern as replace uses a pipeline as 199 // temporary holder 200 ProcessorDefinition<?> flattern = flatternOutput(replace); 201 outputs.add(index + 1, flattern); 202 Object old = outputs.remove(index); 203 // must set parent on the node we added in the 204 // route 205 ProcessorDefinition parent = output.getParent() != null ? output.getParent() : route; 206 flattern.setParent(parent); 207 LOG.info("AdviceWith ({}) : [{}] --> replace [{}]", matchBy.getId(), old, flattern); 208 } 209 } 210 } 211 } 212 213 if (!match) { 214 throw new IllegalArgumentException("There are no outputs which matches: " + matchBy.getId() + " in the route: " + route); 215 } 216 } 217 }; 218 } 219 220 public static AdviceWithTask removeByToString(final RouteDefinition route, final String toString, boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, 221 int maxDeep) { 222 MatchBy matchBy = new MatchByToString(toString); 223 return doRemove(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 224 } 225 226 public static AdviceWithTask removeByToUri(final RouteDefinition route, final String toUri, boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, 227 int maxDeep) { 228 MatchBy matchBy = new MatchByToUri(toUri); 229 return doRemove(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 230 } 231 232 public static AdviceWithTask removeById(final RouteDefinition route, final String id, boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) { 233 MatchBy matchBy = new MatchById(id); 234 return doRemove(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 235 } 236 237 public static AdviceWithTask removeByType(final RouteDefinition route, final Class<?> type, boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, 238 int maxDeep) { 239 MatchBy matchBy = new MatchByType(type); 240 return doRemove(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 241 } 242 243 private static AdviceWithTask doRemove(final RouteDefinition route, final MatchBy matchBy, boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) { 244 return new AdviceWithTask() { 245 public void task() throws Exception { 246 boolean match = false; 247 Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 248 while (it.hasNext()) { 249 ProcessorDefinition<?> output = it.next(); 250 if (matchBy.match(output)) { 251 List<ProcessorDefinition<?>> outputs = getOutputs(output, route); 252 if (outputs != null) { 253 int index = outputs.indexOf(output); 254 if (index != -1) { 255 match = true; 256 Object old = outputs.remove(index); 257 LOG.info("AdviceWith ({}) : [{}] --> remove", matchBy.getId(), old); 258 } 259 } 260 } 261 } 262 263 if (!match) { 264 throw new IllegalArgumentException("There are no outputs which matches: " + matchBy.getId() + " in the route: " + route); 265 } 266 } 267 }; 268 } 269 270 public static AdviceWithTask beforeByToString(final RouteDefinition route, final String toString, final ProcessorDefinition<?> before, boolean selectFirst, boolean selectLast, 271 int selectFrom, int selectTo, int maxDeep) { 272 MatchBy matchBy = new MatchByToString(toString); 273 return doBefore(route, matchBy, before, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 274 } 275 276 public static AdviceWithTask beforeByToUri(final RouteDefinition route, final String toUri, final ProcessorDefinition<?> before, boolean selectFirst, boolean selectLast, 277 int selectFrom, int selectTo, int maxDeep) { 278 MatchBy matchBy = new MatchByToUri(toUri); 279 return doBefore(route, matchBy, before, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 280 } 281 282 public static AdviceWithTask beforeById(final RouteDefinition route, final String id, final ProcessorDefinition<?> before, boolean selectFirst, boolean selectLast, 283 int selectFrom, int selectTo, int maxDeep) { 284 MatchBy matchBy = new MatchById(id); 285 return doBefore(route, matchBy, before, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 286 } 287 288 public static AdviceWithTask beforeByType(final RouteDefinition route, final Class<?> type, final ProcessorDefinition<?> before, boolean selectFirst, boolean selectLast, 289 int selectFrom, int selectTo, int maxDeep) { 290 MatchBy matchBy = new MatchByType(type); 291 return doBefore(route, matchBy, before, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 292 } 293 294 private static AdviceWithTask doBefore(final RouteDefinition route, final MatchBy matchBy, final ProcessorDefinition<?> before, boolean selectFirst, boolean selectLast, 295 int selectFrom, int selectTo, int maxDeep) { 296 return new AdviceWithTask() { 297 public void task() throws Exception { 298 boolean match = false; 299 Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 300 while (it.hasNext()) { 301 ProcessorDefinition<?> output = it.next(); 302 if (matchBy.match(output)) { 303 List<ProcessorDefinition<?>> outputs = getOutputs(output, route); 304 if (outputs != null) { 305 int index = outputs.indexOf(output); 306 if (index != -1) { 307 match = true; 308 // flattern as before uses a pipeline as 309 // temporary holder 310 ProcessorDefinition<?> flattern = flatternOutput(before); 311 Object existing = outputs.get(index); 312 outputs.add(index, flattern); 313 // must set parent on the node we added in the 314 // route 315 ProcessorDefinition parent = output.getParent() != null ? output.getParent() : route; 316 flattern.setParent(parent); 317 LOG.info("AdviceWith ({}) : [{}] --> before [{}]", matchBy.getId(), existing, flattern); 318 } 319 } 320 } 321 } 322 323 if (!match) { 324 throw new IllegalArgumentException("There are no outputs which matches: " + matchBy.getId() + " in the route: " + route); 325 } 326 } 327 }; 328 } 329 330 public static AdviceWithTask afterByToString(final RouteDefinition route, final String toString, final ProcessorDefinition<?> after, boolean selectFirst, boolean selectLast, 331 int selectFrom, int selectTo, int maxDeep) { 332 MatchBy matchBy = new MatchByToString(toString); 333 return doAfter(route, matchBy, after, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 334 } 335 336 public static AdviceWithTask afterByToUri(final RouteDefinition route, final String toUri, final ProcessorDefinition<?> after, boolean selectFirst, boolean selectLast, 337 int selectFrom, int selectTo, int maxDeep) { 338 MatchBy matchBy = new MatchByToUri(toUri); 339 return doAfter(route, matchBy, after, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 340 } 341 342 public static AdviceWithTask afterById(final RouteDefinition route, final String id, final ProcessorDefinition<?> after, boolean selectFirst, boolean selectLast, 343 int selectFrom, int selectTo, int maxDeep) { 344 MatchBy matchBy = new MatchById(id); 345 return doAfter(route, matchBy, after, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 346 } 347 348 public static AdviceWithTask afterByType(final RouteDefinition route, final Class<?> type, final ProcessorDefinition<?> after, boolean selectFirst, boolean selectLast, 349 int selectFrom, int selectTo, int maxDeep) { 350 MatchBy matchBy = new MatchByType(type); 351 return doAfter(route, matchBy, after, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 352 } 353 354 private static AdviceWithTask doAfter(final RouteDefinition route, final MatchBy matchBy, final ProcessorDefinition<?> after, boolean selectFirst, boolean selectLast, 355 int selectFrom, int selectTo, int maxDeep) { 356 return new AdviceWithTask() { 357 public void task() throws Exception { 358 boolean match = false; 359 Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep); 360 while (it.hasNext()) { 361 ProcessorDefinition<?> output = it.next(); 362 if (matchBy.match(output)) { 363 List<ProcessorDefinition<?>> outputs = getOutputs(output, route); 364 if (outputs != null) { 365 int index = outputs.indexOf(output); 366 if (index != -1) { 367 match = true; 368 // flattern as after uses a pipeline as 369 // temporary holder 370 ProcessorDefinition<?> flattern = flatternOutput(after); 371 Object existing = outputs.get(index); 372 outputs.add(index + 1, flattern); 373 // must set parent on the node we added in the 374 // route 375 ProcessorDefinition parent = output.getParent() != null ? output.getParent() : route; 376 flattern.setParent(parent); 377 LOG.info("AdviceWith ({}) : [{}] --> after [{}]", matchBy.getId(), existing, flattern); 378 } 379 } 380 } 381 } 382 383 if (!match) { 384 throw new IllegalArgumentException("There are no outputs which matches: " + matchBy.getId() + " in the route: " + route); 385 } 386 } 387 }; 388 } 389 390 /** 391 * Gets the outputs to use with advice with from the given child/parent 392 * <p/> 393 * This implementation deals with that outputs can be abstract and retrieves 394 * the <i>correct</i> parent output. 395 * 396 * @param node the node 397 * @return <tt>null</tt> if not outputs to be used 398 */ 399 private static List<ProcessorDefinition<?>> getOutputs(ProcessorDefinition<?> node, RouteDefinition route) { 400 if (node == null) { 401 return null; 402 } 403 // for intercept/onException/onCompletion then we want to work on the 404 // route outputs as they are top-level 405 if (node instanceof InterceptDefinition) { 406 return route.getOutputs(); 407 } else if (node instanceof InterceptSendToEndpointDefinition) { 408 return route.getOutputs(); 409 } else if (node instanceof OnExceptionDefinition) { 410 return route.getOutputs(); 411 } else if (node instanceof OnCompletionDefinition) { 412 return route.getOutputs(); 413 } 414 415 ProcessorDefinition<?> parent = node.getParent(); 416 if (parent == null) { 417 return null; 418 } 419 // for CBR then use the outputs from the node itself 420 // so we work on the right branch in the CBR (when/otherwise) 421 if (parent instanceof ChoiceDefinition) { 422 return node.getOutputs(); 423 } 424 List<ProcessorDefinition<?>> outputs = parent.getOutputs(); 425 if (outputs.size() == 1 && outputs.get(0).isAbstract()) { 426 // if the output is abstract then get its output, as 427 outputs = outputs.get(0).getOutputs(); 428 } 429 return outputs; 430 } 431 432 public static AdviceWithTask replaceFromWith(final RouteDefinition route, final String uri) { 433 return new AdviceWithTask() { 434 public void task() throws Exception { 435 FromDefinition from = route.getInput(); 436 LOG.info("AdviceWith replace input from [{}] --> [{}]", from.getEndpointUri(), uri); 437 from.setEndpoint(null); 438 from.setUri(uri); 439 } 440 }; 441 } 442 443 public static AdviceWithTask replaceFrom(final RouteDefinition route, final Endpoint endpoint) { 444 return new AdviceWithTask() { 445 public void task() throws Exception { 446 FromDefinition from = route.getInput(); 447 LOG.info("AdviceWith replace input from [{}] --> [{}]", from.getEndpointUri(), endpoint.getEndpointUri()); 448 from.setUri(null); 449 from.setEndpoint(endpoint); 450 } 451 }; 452 } 453 454 /** 455 * Create iterator which walks the route, and only returns nodes which 456 * matches the given set of criteria. 457 * 458 * @param route the route 459 * @param matchBy match by which must match 460 * @param selectFirst optional to select only the first 461 * @param selectLast optional to select only the last 462 * @param selectFrom optional to select index/range 463 * @param selectTo optional to select index/range 464 * @param maxDeep maximum levels deep (is unbounded by default) 465 * @return the iterator 466 */ 467 private static Iterator<ProcessorDefinition<?>> createMatchByIterator(final RouteDefinition route, final MatchBy matchBy, final boolean selectFirst, final boolean selectLast, 468 final int selectFrom, final int selectTo, int maxDeep) { 469 470 // first iterator and apply match by 471 List<ProcessorDefinition<?>> matched = new ArrayList<>(); 472 473 List<ProcessorDefinition<?>> outputs = new ArrayList<>(); 474 475 // if we are in first|last mode then we should 476 // skip abstract nodes in the beginning as they are cross cutting 477 // functionality such as onException, onCompletion etc 478 // and the user want to select first or last outputs in the route (not 479 // cross cutting functionality) 480 boolean skip = selectFirst || selectLast; 481 482 for (ProcessorDefinition output : route.getOutputs()) { 483 // special for transacted, which we need to unwrap 484 if (output instanceof TransactedDefinition) { 485 outputs.addAll(output.getOutputs()); 486 } else if (skip) { 487 boolean invalid = outputs.isEmpty() && output.isAbstract(); 488 if (!invalid) { 489 outputs.add(output); 490 } 491 } else { 492 outputs.add(output); 493 } 494 } 495 496 @SuppressWarnings("rawtypes") 497 Iterator<ProcessorDefinition> itAll = ProcessorDefinitionHelper.filterTypeInOutputs(outputs, ProcessorDefinition.class, maxDeep); 498 while (itAll.hasNext()) { 499 ProcessorDefinition<?> next = itAll.next(); 500 501 if (matchBy.match(next)) { 502 matched.add(next); 503 } 504 } 505 506 // and then apply the selector iterator 507 return createSelectorIterator(matched, selectFirst, selectLast, selectFrom, selectTo); 508 } 509 510 private static Iterator<ProcessorDefinition<?>> createSelectorIterator(final List<ProcessorDefinition<?>> list, final boolean selectFirst, final boolean selectLast, 511 final int selectFrom, final int selectTo) { 512 return new Iterator<ProcessorDefinition<?>>() { 513 private int current; 514 private boolean done; 515 516 @Override 517 public boolean hasNext() { 518 if (list.isEmpty() || done) { 519 return false; 520 } 521 522 if (selectFirst) { 523 done = true; 524 // spool to first 525 current = 0; 526 return true; 527 } 528 529 if (selectLast) { 530 done = true; 531 // spool to last 532 current = list.size() - 1; 533 return true; 534 } 535 536 if (selectFrom >= 0 && selectTo >= 0) { 537 // check for out of bounds 538 if (selectFrom >= list.size() || selectTo >= list.size()) { 539 return false; 540 } 541 if (current < selectFrom) { 542 // spool to beginning of range 543 current = selectFrom; 544 } 545 return current >= selectFrom && current <= selectTo; 546 } 547 548 return current < list.size(); 549 } 550 551 @Override 552 public ProcessorDefinition<?> next() { 553 ProcessorDefinition<?> answer = list.get(current); 554 current++; 555 return answer; 556 } 557 558 @Override 559 public void remove() { 560 // noop 561 } 562 }; 563 } 564 565 private static ProcessorDefinition<?> flatternOutput(ProcessorDefinition<?> output) { 566 if (output instanceof AdviceWithDefinition) { 567 AdviceWithDefinition advice = (AdviceWithDefinition)output; 568 if (advice.getOutputs().size() == 1) { 569 return advice.getOutputs().get(0); 570 } else { 571 // it should be a pipeline 572 PipelineDefinition pipe = new PipelineDefinition(); 573 pipe.setOutputs(advice.getOutputs()); 574 return pipe; 575 } 576 } 577 return output; 578 } 579 580}