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