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