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