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.EndpointAware;
025import org.apache.camel.model.ChoiceDefinition;
026import org.apache.camel.model.EndpointRequiredDefinition;
027import org.apache.camel.model.FromDefinition;
028import org.apache.camel.model.ProcessorDefinition;
029import org.apache.camel.model.ProcessorDefinitionHelper;
030import org.apache.camel.model.RouteDefinition;
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        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
148        return doReplace(route, new MatchByToString(toString), replace, it);
149    }
150
151    public static AdviceWithTask replaceByToUri(final RouteDefinition route, final String toUri, final ProcessorDefinition<?> replace,
152                                                boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
153        MatchBy matchBy = new MatchByToUri(toUri);
154        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
155        return doReplace(route, new MatchByToUri(toUri), replace, it);
156    }
157
158    public static AdviceWithTask replaceById(final RouteDefinition route, final String id, final ProcessorDefinition<?> replace,
159                                             boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
160        MatchBy matchBy = new MatchById(id);
161        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
162        return doReplace(route, matchBy, replace, it);
163    }
164
165    public static AdviceWithTask replaceByType(final RouteDefinition route, final Class<?> type, final ProcessorDefinition<?> replace,
166                                               boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
167        MatchBy matchBy = new MatchByType(type);
168        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
169        return doReplace(route, matchBy, replace, it);
170    }
171
172    private static AdviceWithTask doReplace(final RouteDefinition route, final MatchBy matchBy, final ProcessorDefinition<?> replace,
173                                            final Iterator<ProcessorDefinition<?>> it) {
174        return new AdviceWithTask() {
175            public void task() throws Exception {
176                boolean match = false;
177                while (it.hasNext()) {
178                    ProcessorDefinition<?> output = it.next();
179                    if (matchBy.match(output)) {
180                        List<ProcessorDefinition<?>> outputs = getOutputs(output);
181                        if (outputs != null) {
182                            int index = outputs.indexOf(output);
183                            if (index != -1) {
184                                match = true;
185                                outputs.add(index + 1, replace);
186                                Object old = outputs.remove(index);
187                                LOG.info("AdviceWith (" + matchBy.getId() + ") : [" + old + "] --> replace [" + replace + "]");
188                            }
189                        }
190                    }
191                }
192
193                if (!match) {
194                    throw new IllegalArgumentException("There are no outputs which matches: " + matchBy.getId() + " in the route: " + route);
195                }
196            }
197        };
198    }
199
200    public static AdviceWithTask removeByToString(final RouteDefinition route, final String toString,
201                                                  boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
202        MatchBy matchBy = new MatchByToString(toString);
203        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
204        return doRemove(route, matchBy, it);
205    }
206
207    public static AdviceWithTask removeByToUri(final RouteDefinition route, final String toUri,
208                                               boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
209        MatchBy matchBy = new MatchByToUri(toUri);
210        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
211        return doRemove(route, matchBy, it);
212    }
213
214    public static AdviceWithTask removeById(final RouteDefinition route, final String id,
215                                            boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
216        MatchBy matchBy = new MatchById(id);
217        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
218        return doRemove(route, matchBy, it);
219    }
220
221    public static AdviceWithTask removeByType(final RouteDefinition route, final Class<?> type,
222                                              boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
223        MatchBy matchBy = new MatchByType(type);
224        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
225        return doRemove(route, matchBy, it);
226    }
227
228    private static AdviceWithTask doRemove(final RouteDefinition route, final MatchBy matchBy,
229                                           final Iterator<ProcessorDefinition<?>> it) {
230        return new AdviceWithTask() {
231            public void task() throws Exception {
232                boolean match = false;
233                while (it.hasNext()) {
234                    ProcessorDefinition<?> output = it.next();
235                    if (matchBy.match(output)) {
236                        List<ProcessorDefinition<?>> outputs = getOutputs(output);
237                        if (outputs != null) {
238                            int index = outputs.indexOf(output);
239                            if (index != -1) {
240                                match = true;
241                                Object old = outputs.remove(index);
242                                LOG.info("AdviceWith (" + matchBy.getId() + ") : [" + old + "] --> remove");
243                            }
244                        }
245                    }
246                }
247
248                if (!match) {
249                    throw new IllegalArgumentException("There are no outputs which matches: " + matchBy.getId() + " in the route: " + route);
250                }
251            }
252        };
253    }
254
255    public static AdviceWithTask beforeByToString(final RouteDefinition route, final String toString, final ProcessorDefinition<?> before,
256                                                  boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
257        MatchBy matchBy = new MatchByToString(toString);
258        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
259        return doBefore(route, matchBy, before, it);
260    }
261
262    public static AdviceWithTask beforeByToUri(final RouteDefinition route, final String toUri, final ProcessorDefinition<?> before,
263                                               boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
264        MatchBy matchBy = new MatchByToUri(toUri);
265        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
266        return doBefore(route, matchBy, before, it);
267    }
268
269    public static AdviceWithTask beforeById(final RouteDefinition route, final String id, final ProcessorDefinition<?> before,
270                                            boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
271        MatchBy matchBy = new MatchById(id);
272        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
273        return doBefore(route, matchBy, before, it);
274    }
275
276    public static AdviceWithTask beforeByType(final RouteDefinition route, final Class<?> type, final ProcessorDefinition<?> before,
277                                              boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
278        MatchBy matchBy = new MatchByType(type);
279        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
280        return doBefore(route, matchBy, before, it);
281    }
282
283    private static AdviceWithTask doBefore(final RouteDefinition route, final MatchBy matchBy, final ProcessorDefinition<?> before,
284                                           final Iterator<ProcessorDefinition<?>> it) {
285        return new AdviceWithTask() {
286            public void task() throws Exception {
287                boolean match = false;
288                while (it.hasNext()) {
289                    ProcessorDefinition<?> output = it.next();
290                    if (matchBy.match(output)) {
291                        List<ProcessorDefinition<?>> outputs = getOutputs(output);
292                        if (outputs != null) {
293                            int index = outputs.indexOf(output);
294                            if (index != -1) {
295                                match = true;
296                                Object existing = outputs.get(index);
297                                outputs.add(index, before);
298                                LOG.info("AdviceWith (" + matchBy.getId() + ") : [" + existing + "] --> before [" + before + "]");
299                            }
300                        }
301                    }
302                }
303
304                if (!match) {
305                    throw new IllegalArgumentException("There are no outputs which matches: " + matchBy.getId() + " in the route: " + route);
306                }
307            }
308        };
309    }
310
311    public static AdviceWithTask afterByToString(final RouteDefinition route, final String toString, final ProcessorDefinition<?> after,
312                                                 boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
313        MatchBy matchBy = new MatchByToString(toString);
314        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
315        return doAfter(route, matchBy, after, it);
316    }
317
318    public static AdviceWithTask afterByToUri(final RouteDefinition route, final String toUri, final ProcessorDefinition<?> after,
319                                              boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
320        MatchBy matchBy = new MatchByToUri(toUri);
321        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
322        return doAfter(route, matchBy, after, it);
323    }
324
325    public static AdviceWithTask afterById(final RouteDefinition route, final String id, final ProcessorDefinition<?> after,
326                                           boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
327        MatchBy matchBy = new MatchById(id);
328        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
329        return doAfter(route, matchBy, after, it);
330    }
331
332    public static AdviceWithTask afterByType(final RouteDefinition route, final Class<?> type, final ProcessorDefinition<?> after,
333                                             boolean selectFirst, boolean selectLast, int selectFrom, int selectTo, int maxDeep) {
334        MatchBy matchBy = new MatchByType(type);
335        Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo, maxDeep);
336        return doAfter(route, matchBy, after, it);
337    }
338
339    private static AdviceWithTask doAfter(final RouteDefinition route, final MatchBy matchBy, final ProcessorDefinition<?> after,
340                                          final Iterator<ProcessorDefinition<?>> it) {
341        return new AdviceWithTask() {
342            public void task() throws Exception {
343                boolean match = false;
344                while (it.hasNext()) {
345                    ProcessorDefinition<?> output = it.next();
346                    if (matchBy.match(output)) {
347                        List<ProcessorDefinition<?>> outputs = getOutputs(output);
348                        if (outputs != null) {
349                            int index = outputs.indexOf(output);
350                            if (index != -1) {
351                                match = true;
352                                Object existing = outputs.get(index);
353                                outputs.add(index + 1, after);
354                                LOG.info("AdviceWith (" + matchBy.getId() + ") : [" + existing + "] --> after [" + after + "]");
355                            }
356                        }
357                    }
358                }
359
360                if (!match) {
361                    throw new IllegalArgumentException("There are no outputs which matches: " + matchBy.getId() + " in the route: " + route);
362                }
363            }
364        };
365    }
366
367    /**
368     * Gets the outputs to use with advice with from the given child/parent
369     * <p/>
370     * This implementation deals with that outputs can be abstract and retrieves the <i>correct</i> parent output.
371     *
372     * @param node the node
373     * @return <tt>null</tt> if not outputs to be used
374     */
375    private static List<ProcessorDefinition<?>> getOutputs(ProcessorDefinition<?> node) {
376        if (node == null) {
377            return null;
378        }
379        ProcessorDefinition<?> parent = node.getParent();
380        if (parent == null) {
381            return null;
382        }
383        // for CBR then use the outputs from the node itself
384        // so we work on the right branch in the CBR (when/otherwise)
385        if (parent instanceof ChoiceDefinition) {
386            return node.getOutputs();
387        }
388        List<ProcessorDefinition<?>> outputs = parent.getOutputs();
389        if (outputs.size() == 1 && outputs.get(0).isAbstract()) {
390            // if the output is abstract then get its output, as
391            outputs = outputs.get(0).getOutputs();
392        }
393        return outputs;
394    }
395
396    public static AdviceWithTask replaceFromWith(final RouteDefinition route, final String uri) {
397        return new AdviceWithTask() {
398            public void task() throws Exception {
399                FromDefinition from = route.getInputs().get(0);
400                LOG.info("AdviceWith replace input from [{}] --> [{}]", from.getUriOrRef(), uri);
401                from.setEndpoint(null);
402                from.setRef(null);
403                from.setUri(uri);
404            }
405        };
406    }
407
408    public static AdviceWithTask replaceFrom(final RouteDefinition route, final Endpoint endpoint) {
409        return new AdviceWithTask() {
410            public void task() throws Exception {
411                FromDefinition from = route.getInputs().get(0);
412                LOG.info("AdviceWith replace input from [{}] --> [{}]", from.getUriOrRef(), endpoint.getEndpointUri());
413                from.setRef(null);
414                from.setUri(null);
415                from.setEndpoint(endpoint);
416            }
417        };
418    }
419
420    /**
421     * Create iterator which walks the route, and only returns nodes which matches the given set of criteria.
422     *
423     * @param route        the route
424     * @param matchBy      match by which must match
425     * @param selectFirst  optional to select only the first
426     * @param selectLast   optional to select only the last
427     * @param selectFrom   optional to select index/range
428     * @param selectTo     optional to select index/range
429     * @param maxDeep      maximum levels deep (is unbounded by default)
430     *
431     * @return the iterator
432     */
433    private static Iterator<ProcessorDefinition<?>> createMatchByIterator(final RouteDefinition route, final MatchBy matchBy,
434                                                               final boolean selectFirst, final boolean selectLast,
435                                                               final int selectFrom, final int selectTo, int maxDeep) {
436
437        // first iterator and apply match by
438        List<ProcessorDefinition<?>> matched = new ArrayList<ProcessorDefinition<?>>();
439
440        @SuppressWarnings("rawtypes")
441        Iterator<ProcessorDefinition> itAll = ProcessorDefinitionHelper.filterTypeInOutputs(route.getOutputs(), ProcessorDefinition.class, maxDeep);
442        while (itAll.hasNext()) {
443            ProcessorDefinition<?> next = itAll.next();
444            if (matchBy.match(next)) {
445                matched.add(next);
446            }
447        }
448
449        // and then apply the selector iterator
450        return createSelectorIterator(matched, selectFirst, selectLast, selectFrom, selectTo);
451    }
452
453    private static Iterator<ProcessorDefinition<?>> createSelectorIterator(final List<ProcessorDefinition<?>> list, final boolean selectFirst,
454                                                                           final boolean selectLast, final int selectFrom, final int selectTo) {
455        return new Iterator<ProcessorDefinition<?>>() {
456            private int current;
457            private boolean done;
458
459            @Override
460            public boolean hasNext() {
461                if (list.isEmpty() || done) {
462                    return false;
463                }
464
465                if (selectFirst) {
466                    done = true;
467                    // spool to first
468                    current = 0;
469                    return true;
470                }
471
472                if (selectLast) {
473                    done = true;
474                    // spool to last
475                    current = list.size() - 1;
476                    return true;
477                }
478
479                if (selectFrom >= 0 && selectTo >= 0) {
480                    // check for out of bounds
481                    if (selectFrom >= list.size() || selectTo >= list.size()) {
482                        return false;
483                    }
484                    if (current < selectFrom) {
485                        // spool to beginning of range
486                        current = selectFrom;
487                    }
488                    return current >= selectFrom && current <= selectTo;
489                }
490
491                return current < list.size();
492            }
493
494            @Override
495            public ProcessorDefinition<?> next() {
496                ProcessorDefinition<?> answer = list.get(current);
497                current++;
498                return answer;
499            }
500
501            @Override
502            public void remove() {
503                // noop
504            }
505        };
506    }
507
508}