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}