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                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) {
119            MatchBy matchBy = new MatchByToString(toString);
120            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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) {
126            MatchBy matchBy = new MatchById(id);
127            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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) {
133            MatchBy matchBy = new MatchByType(type);
134            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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                            ProcessorDefinition<?> parent = output.getParent();
147                            if (parent != null) {
148                                int index = parent.getOutputs().indexOf(output);
149                                if (index != -1) {
150                                    match = true;
151                                    parent.getOutputs().add(index + 1, replace);
152                                    Object old = parent.getOutputs().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) {
168            MatchBy matchBy = new MatchByToString(toString);
169            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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) {
175            MatchBy matchBy = new MatchById(id);
176            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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) {
182            MatchBy matchBy = new MatchByType(type);
183            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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                            ProcessorDefinition<?> parent = output.getParent();
196                            if (parent != null) {
197                                int index = parent.getOutputs().indexOf(output);
198                                if (index != -1) {
199                                    match = true;
200                                    Object old = parent.getOutputs().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) {
216            MatchBy matchBy = new MatchByToString(toString);
217            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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) {
223            MatchBy matchBy = new MatchById(id);
224            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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) {
230            MatchBy matchBy = new MatchByType(type);
231            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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                            ProcessorDefinition<?> parent = output.getParent();
244                            if (parent != null) {
245                                int index = parent.getOutputs().indexOf(output);
246                                if (index != -1) {
247                                    match = true;
248                                    Object existing = parent.getOutputs().get(index);
249                                    parent.getOutputs().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) {
265            MatchBy matchBy = new MatchByToString(toString);
266            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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) {
272            MatchBy matchBy = new MatchById(id);
273            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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) {
279            MatchBy matchBy = new MatchByType(type);
280            Iterator<ProcessorDefinition<?>> it = AdviceWithTasks.createMatchByIterator(route, matchBy, selectFirst, selectLast, selectFrom, selectTo);
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    
293                            ProcessorDefinition<?> parent = output.getParent();
294                            if (parent != null) {
295                                int index = parent.getOutputs().indexOf(output);
296                                if (index != -1) {
297                                    match = true;
298                                    Object existing = parent.getOutputs().get(index);
299                                    parent.getOutputs().add(index + 1, after);
300                                    LOG.info("AdviceWith (" + matchBy.getId() + ") : [" + existing + "] --> after [" + after + "]");
301                                }
302                            }
303                        }
304                    }
305    
306                    if (!match) {
307                        throw new IllegalArgumentException("There are no outputs which matches: " + matchBy.getId() + " in the route: " + route);
308                    }
309                }
310            };
311        }
312    
313        public static AdviceWithTask replaceFromWith(final RouteDefinition route, final String uri) {
314            return new AdviceWithTask() {
315                public void task() throws Exception {
316                    FromDefinition from = route.getInputs().get(0);
317                    LOG.info("AdviceWith replace input from [{}] --> [{}]", from.getUriOrRef(), uri);
318                    from.setEndpoint(null);
319                    from.setRef(null);
320                    from.setUri(uri);
321                }
322            };
323        }
324    
325        public static AdviceWithTask replaceFrom(final RouteDefinition route, final Endpoint endpoint) {
326            return new AdviceWithTask() {
327                public void task() throws Exception {
328                    FromDefinition from = route.getInputs().get(0);
329                    LOG.info("AdviceWith replace input from [{}] --> [{}]", from.getUriOrRef(), endpoint.getEndpointUri());
330                    from.setRef(null);
331                    from.setUri(null);
332                    from.setEndpoint(endpoint);
333                }
334            };
335        }
336    
337        /**
338         * Create iterator which walks the route, and only returns nodes which matches the given set of criteria.
339         *
340         * @param route        the route
341         * @param matchBy      match by which must match
342         * @param selectFirst  optional to select only the first
343         * @param selectLast   optional to select only the last
344         * @param selectFrom   optional to select index/range
345         * @param selectTo     optional to select index/range
346         * 
347         * @return the iterator
348         */
349        private static Iterator<ProcessorDefinition<?>> createMatchByIterator(final RouteDefinition route, final MatchBy matchBy,
350                                                                   final boolean selectFirst, final boolean selectLast,
351                                                                   final int selectFrom, final int selectTo) {
352    
353            // first iterator and apply match by
354            List<ProcessorDefinition<?>> matched = new ArrayList<ProcessorDefinition<?>>();
355    
356            @SuppressWarnings("rawtypes")
357            Iterator<ProcessorDefinition> itAll = ProcessorDefinitionHelper.filterTypeInOutputs(route.getOutputs(), ProcessorDefinition.class);
358            while (itAll.hasNext()) {
359                ProcessorDefinition<?> next = itAll.next();
360                if (matchBy.match(next)) {
361                    matched.add(next);
362                }
363            }
364    
365            // and then apply the selector iterator
366            return createSelectorIterator(matched, selectFirst, selectLast, selectFrom, selectTo);
367        }
368    
369        private static Iterator<ProcessorDefinition<?>> createSelectorIterator(final List<ProcessorDefinition<?>> list, final boolean selectFirst,
370                                                                            final boolean selectLast, final int selectFrom, final int selectTo) {
371            return new Iterator<ProcessorDefinition<?>>() {
372                private int current;
373                private boolean done;
374    
375                @Override
376                public boolean hasNext() {
377                    if (list.isEmpty() || done) {
378                        return false;
379                    }
380    
381                    if (selectFirst) {
382                        done = true;
383                        // spool to first
384                        current = 0;
385                        return true;
386                    }
387    
388                    if (selectLast) {
389                        done = true;
390                        // spool to last
391                        current = list.size() - 1;
392                        return true;
393                    }
394    
395                    if (selectFrom >= 0 && selectTo >= 0) {
396                        // check for out of bounds
397                        if (selectFrom >= list.size() || selectTo >= list.size()) {
398                            return false;
399                        }
400                        if (current < selectFrom) {
401                            // spool to beginning of range
402                            current = selectFrom;
403                        }
404                        return current >= selectFrom && current <= selectTo;
405                    }
406    
407                    return current < list.size();
408                }
409    
410                @Override
411                public ProcessorDefinition<?> next() {
412                    ProcessorDefinition<?> answer = list.get(current);
413                    current++;
414                    return answer;
415                }
416    
417                @Override
418                public void remove() {
419                    // noop
420                }
421            };
422        }
423    
424    }