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