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