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