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