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.spring;
018    
019    import java.io.IOException;
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    import java.util.LinkedList;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Set;
026    import java.util.concurrent.CountDownLatch;
027    import java.util.concurrent.TimeUnit;
028    import java.util.concurrent.atomic.AtomicBoolean;
029    
030    import org.apache.camel.CamelContext;
031    import org.apache.camel.ProducerTemplate;
032    import org.apache.camel.builder.RouteBuilder;
033    import org.apache.camel.impl.ServiceSupport;
034    import org.apache.camel.model.RouteType;
035    import org.apache.camel.processor.interceptor.Debugger;
036    import org.apache.camel.util.ObjectHelper;
037    import org.apache.camel.view.RouteDotGenerator;
038    import org.apache.commons.logging.Log;
039    import org.apache.commons.logging.LogFactory;
040    import org.springframework.context.ApplicationContext;
041    import org.springframework.context.support.AbstractApplicationContext;
042    import org.springframework.context.support.ClassPathXmlApplicationContext;
043    
044    /**
045     * A command line tool for booting up a CamelContext using an optional Spring
046     * ApplicationContext
047     *
048     * @version $Revision: 42486 $
049     */
050    public class Main extends ServiceSupport {
051        private static final Log LOG = LogFactory.getLog(Main.class);
052        private String applicationContextUri = "META-INF/spring/*.xml";
053        private AbstractApplicationContext applicationContext;
054        private List<Option> options = new ArrayList<Option>();
055        private CountDownLatch latch = new CountDownLatch(1);
056        private AtomicBoolean completed = new AtomicBoolean(false);
057        private long duration = -1;
058        private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
059        private String dotOutputDir;
060        private boolean aggregateDot;
061        private boolean debug;
062        private List<RouteBuilder> routeBuilders = new ArrayList<RouteBuilder>();
063        private List<SpringCamelContext> camelContexts = new ArrayList<SpringCamelContext>();
064        private AbstractApplicationContext parentApplicationContext;
065        private String parentApplicationContextUri;
066        private ProducerTemplate camelTemplate;
067    
068        public Main() {
069            addOption(new Option("h", "help", "Displays the help screen") {
070                protected void doProcess(String arg, LinkedList<String> remainingArgs) {
071                    showOptions();
072                    completed();
073                }
074            });
075    
076            addOption(new ParameterOption("a", "applicationContext",
077                    "Sets the classpath based spring ApplicationContext", "applicationContext") {
078                protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
079                    setApplicationContextUri(parameter);
080                }
081            });
082    
083            addOption(new ParameterOption("o", "outdir",
084                    "Sets the DOT output directory where the visual representations of the routes are generated",
085                    "dot") {
086                protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
087                    setDotOutputDir(parameter);
088                }
089            });
090            addOption(new ParameterOption("ad", "aggregate-dot",
091                    "Aggregates all routes (in addition to individual route generation) into one context to create one monolithic DOT file for visual representations the entire system.",
092                    "aggregate-dot") {
093                protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
094                    setAggregateDot("true".equals(parameter));
095                }
096            });
097            addOption(new ParameterOption("d", "duration",
098                    "Sets the time duration that the applicaiton will run for, by default in milliseconds. You can use '10s' for 10 seconds etc",
099                    "duration") {
100                protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
101                    String value = parameter.toUpperCase();
102                    if (value.endsWith("S")) {
103                        value = value.substring(0, value.length() - 1);
104                        setTimeUnit(TimeUnit.SECONDS);
105                    }
106                    setDuration(Integer.parseInt(value));
107                }
108            });
109    
110            addOption(new Option("x", "debug", "Enables the debugger") {
111                protected void doProcess(String arg, LinkedList<String> remainingArgs) {
112                    enableDebugging();
113                }
114            });
115        }
116    
117        public static void main(String... args) {
118            new Main().run(args);
119        }
120    
121        /**
122         * Parses the command line arguments then runs the program
123         */
124        public void run(String[] args) {
125            parseArguments(args);
126            run();
127        }
128    
129        /**
130         * Runs this process with the given arguments
131         */
132        public void run() {
133            if (!completed.get()) {
134                try {
135                    start();
136                    waitUntilCompleted();
137                    stop();
138                } catch (Exception e) {
139                    LOG.error("Failed: " + e, e);
140                }
141            }
142        }
143    
144        /**
145         * Marks this process as being completed
146         */
147        public void completed() {
148            completed.set(true);
149            latch.countDown();
150        }
151    
152        public void addRouteBuilder(RouteBuilder routeBuilder) {
153            getRouteBuilders().add(routeBuilder);
154        }
155    
156        /**
157         * Displays the command line options
158         */
159        public void showOptions() {
160            System.out.println("Apache Camel Runner takes the following options");
161            System.out.println();
162    
163            for (Option option : options) {
164                System.out.println("  " + option.getAbbreviation() + " or " + option.getFullName() + " = "
165                        + option.getDescription());
166            }
167        }
168    
169        /**
170         * Parses the commandl ine arguments
171         */
172        public void parseArguments(String[] arguments) {
173            LinkedList<String> args = new LinkedList<String>(Arrays.asList(arguments));
174    
175            boolean valid = true;
176            while (!args.isEmpty()) {
177                String arg = args.removeFirst();
178    
179                boolean handled = false;
180                for (Option option : options) {
181                    if (option.processOption(arg, args)) {
182                        handled = true;
183                        break;
184                    }
185                }
186                if (!handled) {
187                    System.out.println("Unknown option: " + arg);
188                    System.out.println();
189                    valid = false;
190                    break;
191                }
192            }
193            if (!valid) {
194                showOptions();
195                completed();
196            }
197        }
198    
199        public void addOption(Option option) {
200            options.add(option);
201        }
202    
203        public abstract class Option {
204            private String abbreviation;
205            private String fullName;
206            private String description;
207    
208            protected Option(String abbreviation, String fullName, String description) {
209                this.abbreviation = "-" + abbreviation;
210                this.fullName = "-" + fullName;
211                this.description = description;
212            }
213    
214            public boolean processOption(String arg, LinkedList<String> remainingArgs) {
215                if (arg.equalsIgnoreCase(abbreviation) || fullName.startsWith(arg)) {
216                    doProcess(arg, remainingArgs);
217                    return true;
218                }
219                return false;
220            }
221    
222            public String getAbbreviation() {
223                return abbreviation;
224            }
225    
226            public String getDescription() {
227                return description;
228            }
229    
230            public String getFullName() {
231                return fullName;
232            }
233    
234            protected abstract void doProcess(String arg, LinkedList<String> remainingArgs);
235        }
236    
237        public abstract class ParameterOption extends Option {
238            private String parameterName;
239    
240            protected ParameterOption(String abbreviation, String fullName, String description,
241                    String parameterName) {
242                super(abbreviation, fullName, description);
243                this.parameterName = parameterName;
244            }
245    
246            protected void doProcess(String arg, LinkedList<String> remainingArgs) {
247                if (remainingArgs.isEmpty()) {
248                    System.err.println("Expected fileName for ");
249                    showOptions();
250                    completed();
251                } else {
252                    String parameter = remainingArgs.removeFirst();
253                    doProcess(arg, parameter, remainingArgs);
254                }
255            }
256    
257            protected abstract void doProcess(String arg, String parameter, LinkedList<String> remainingArgs);
258        }
259    
260        // Properties
261        // -------------------------------------------------------------------------
262        public AbstractApplicationContext getApplicationContext() {
263            return applicationContext;
264        }
265    
266        public void setApplicationContext(AbstractApplicationContext applicationContext) {
267            this.applicationContext = applicationContext;
268        }
269    
270    
271        public String getApplicationContextUri() {
272            return applicationContextUri;
273        }
274    
275        public void setApplicationContextUri(String applicationContextUri) {
276            this.applicationContextUri = applicationContextUri;
277        }
278        public AbstractApplicationContext getParentApplicationContext() {
279            if (parentApplicationContext == null) {
280                if (parentApplicationContextUri != null) {
281                    parentApplicationContext = new ClassPathXmlApplicationContext(parentApplicationContextUri);
282                    parentApplicationContext.start();
283                }
284            }
285            return parentApplicationContext;
286        }
287    
288        public void setParentApplicationContext(AbstractApplicationContext parentApplicationContext) {
289            this.parentApplicationContext = parentApplicationContext;
290        }
291    
292        public String getParentApplicationContextUri() {
293            return parentApplicationContextUri;
294        }
295    
296        public void setParentApplicationContextUri(String parentApplicationContextUri) {
297            this.parentApplicationContextUri = parentApplicationContextUri;
298        }
299    
300        public List<SpringCamelContext> getCamelContexts() {
301            return camelContexts;
302        }
303    
304        public long getDuration() {
305            return duration;
306        }
307    
308        /**
309         * Sets the duration to run the application for in milliseconds until it
310         * should be terminated. Defaults to -1. Any value <= 0 will run forever.
311         *
312         * @param duration
313         */
314        public void setDuration(long duration) {
315            this.duration = duration;
316        }
317    
318        public TimeUnit getTimeUnit() {
319            return timeUnit;
320        }
321    
322        /**
323         * Sets the time unit duration
324         */
325        public void setTimeUnit(TimeUnit timeUnit) {
326            this.timeUnit = timeUnit;
327        }
328    
329        public String getDotOutputDir() {
330            return dotOutputDir;
331        }
332    
333        /**
334         * Sets the output directory of the generated DOT Files to show the visual
335         * representation of the routes. A null value disables the dot file
336         * generation
337         */
338        public void setDotOutputDir(String dotOutputDir) {
339            this.dotOutputDir = dotOutputDir;
340        }
341    
342        public List<RouteBuilder> getRouteBuilders() {
343            return routeBuilders;
344        }
345    
346        public void setRouteBuilders(List<RouteBuilder> routeBuilders) {
347            this.routeBuilders = routeBuilders;
348        }
349    
350        public void setAggregateDot(boolean aggregateDot) {
351            this.aggregateDot = aggregateDot;
352        }
353    
354        public boolean isAggregateDot() {
355            return aggregateDot;
356        }
357    
358        public boolean isDebug() {
359            return debug;
360        }
361    
362        public void enableDebugging() {
363            this.debug = true;
364            setParentApplicationContextUri("/META-INF/services/org/apache/camel/spring/debug.xml");
365        }
366    
367        /**
368         * Returns the currently active debugger if one is enabled
369         *
370         * @return the current debugger or null if none is active
371         * @see #enableDebugging()
372         */
373        public Debugger getDebugger() {
374            for (SpringCamelContext camelContext : camelContexts) {
375                Debugger debugger = Debugger.getDebugger(camelContext);
376                if (debugger != null) {
377                    return debugger;
378                }
379            }
380            return null;
381        }
382    
383        public List<RouteType> getRouteDefinitions() {
384            List<RouteType> answer = new ArrayList<RouteType>();
385            for (SpringCamelContext camelContext : camelContexts) {
386                answer.addAll(camelContext.getRouteDefinitions());
387            }
388            return answer;
389        }
390    
391        /**
392         * Returns a {@link ProducerTemplate} from the Spring {@link ApplicationContext} instances
393         * or lazily creates a new one dynamically
394         *
395         * @return
396         */
397        public ProducerTemplate getCamelTemplate() {
398            if (camelTemplate == null) {
399                camelTemplate = findOrCreateCamelTemplate();
400            }
401            return camelTemplate;
402        }
403    
404        // Implementation methods
405        // -------------------------------------------------------------------------
406        protected ProducerTemplate findOrCreateCamelTemplate() {
407            String[] names = getApplicationContext().getBeanNamesForType(ProducerTemplate.class);
408            if (names != null && names.length > 0) {
409                return (ProducerTemplate) getApplicationContext().getBean(names[0], ProducerTemplate.class);
410            }
411            for (SpringCamelContext camelContext : camelContexts) {
412                return camelContext.createProducerTemplate();
413            }
414            throw new IllegalArgumentException("No CamelContexts are available so cannot create a ProducerTemplate!");
415        }
416    
417        protected void doStart() throws Exception {
418            LOG.info("Apache Camel " + getVersion() + " starting");
419            if (applicationContext == null) {
420                applicationContext = createDefaultApplicationContext();
421            }
422            applicationContext.start();
423    
424            postProcessContext();
425        }
426    
427        protected AbstractApplicationContext createDefaultApplicationContext() {
428            String[] args = getApplicationContextUri().split(";");
429            ApplicationContext parentContext = getParentApplicationContext();
430            if (parentContext != null) {
431                return new ClassPathXmlApplicationContext(args, parentContext);
432            } else {
433                return new ClassPathXmlApplicationContext(args);
434            }
435        }
436    
437        protected void doStop() throws Exception {
438            LOG.info("Apache Camel terminating");
439    
440            if (applicationContext != null) {
441                applicationContext.close();
442            }
443        }
444    
445        protected void waitUntilCompleted() {
446            while (!completed.get()) {
447                try {
448                    if (duration > 0) {
449                        TimeUnit unit = getTimeUnit();
450                        LOG.info("Waiting for: " + duration + " " + unit);
451                        latch.await(duration, unit);
452                        completed.set(true);
453                    } else {
454                        latch.await();
455                    }
456                } catch (InterruptedException e) {
457                    LOG.debug("Caught: " + e);
458                }
459            }
460        }
461    
462        protected void postProcessContext() throws Exception {
463            Map<String, SpringCamelContext> map = applicationContext.getBeansOfType(SpringCamelContext.class);
464            Set<Map.Entry<String, SpringCamelContext>> entries = map.entrySet();
465            int size = entries.size();
466            for (Map.Entry<String, SpringCamelContext> entry : entries) {
467                String name = entry.getKey();
468                SpringCamelContext camelContext = entry.getValue();
469                camelContexts.add(camelContext);
470                generateDot(name, camelContext, size);
471                postProcesCamelContext(camelContext);
472            }
473    
474            if (isAggregateDot()) {
475                generateDot("aggregate", aggregateSpringCamelContext(applicationContext), 1);
476            }
477        }
478    
479        protected void generateDot(String name, SpringCamelContext camelContext, int size) throws IOException {
480            String outputDir = dotOutputDir;
481            if (ObjectHelper.isNotNullAndNonEmpty(outputDir)) {
482                if (size > 1) {
483                    outputDir += "/" + name;
484                }
485                RouteDotGenerator generator = new RouteDotGenerator(outputDir);
486                LOG.info("Generating DOT file for routes: " + outputDir + " for: " + camelContext + " with name: " + name);
487                generator.drawRoutes(camelContext);
488            }
489        }
490    
491        /**
492         * Used for aggregate dot generation
493         *
494         * @param applicationContext
495         * @return
496         * @throws Exception
497         */
498        private static SpringCamelContext aggregateSpringCamelContext(ApplicationContext applicationContext) throws Exception {
499            SpringCamelContext aggregateCamelContext = new SpringCamelContext() {
500                /**
501                 *  Don't actually start this, it is merely fabricated for dot generation.
502                 * @see org.apache.camel.impl.DefaultCamelContext#shouldStartRoutes()
503                 */
504                protected boolean shouldStartRoutes() {
505    
506                    return false;
507                }
508            };
509    
510            // look up all configured camel contexts
511            String[] names = applicationContext.getBeanNamesForType(SpringCamelContext.class);
512            for (String name : names) {
513    
514                SpringCamelContext next = (SpringCamelContext) applicationContext.getBean(name, SpringCamelContext.class);
515                //            aggregateCamelContext.addRoutes( next.getRoutes() );
516                aggregateCamelContext.addRouteDefinitions(next.getRouteDefinitions());
517            }
518            // Don't actually start this, it is merely fabricated for dot generation.
519            //        answer.setApplicationContext( applicationContext );
520            //        answer.afterPropertiesSet();
521            return aggregateCamelContext;
522        }
523    
524        protected void postProcesCamelContext(CamelContext camelContext) throws Exception {
525            for (RouteBuilder routeBuilder : routeBuilders) {
526                camelContext.addRoutes(routeBuilder);
527            }
528        }
529    
530        protected String getVersion() {
531            Package aPackage = Package.getPackage("org.apache.camel");
532            if (aPackage != null) {
533                String version = aPackage.getImplementationVersion();
534                if (version == null) {
535                    version = aPackage.getSpecificationVersion();
536                    if (version == null) {
537                        version = "";
538                    }
539                }
540                return version;
541            }
542            return "";
543        }
544    }