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