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}