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