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