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