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