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