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.spring;
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 org.apache.camel.CamelContext;
031 import org.apache.camel.ProducerTemplate;
032 import org.apache.camel.builder.RouteBuilder;
033 import org.apache.camel.impl.ServiceSupport;
034 import org.apache.camel.model.RouteType;
035 import org.apache.camel.processor.interceptor.Debugger;
036 import org.apache.camel.spring.handler.ModelFileGenerator;
037 import org.apache.camel.util.ObjectHelper;
038 import org.apache.camel.view.RouteDotGenerator;
039 import org.apache.commons.logging.Log;
040 import org.apache.commons.logging.LogFactory;
041 import org.springframework.context.ApplicationContext;
042 import org.springframework.context.support.AbstractApplicationContext;
043 import org.springframework.context.support.ClassPathXmlApplicationContext;
044 import org.springframework.context.support.FileSystemXmlApplicationContext;
045
046 /**
047 * A command line tool for booting up a CamelContext using an optional Spring
048 * ApplicationContext
049 *
050 * @version $Revision: 52544 $
051 */
052 public class Main extends ServiceSupport {
053 private static final Log LOG = LogFactory.getLog(Main.class);
054 private static Main instance;
055
056 private String applicationContextUri = "META-INF/spring/*.xml";
057 private String fileApplicationContextUri;
058 private AbstractApplicationContext applicationContext;
059 private List<Option> options = new ArrayList<Option>();
060 private CountDownLatch latch = new CountDownLatch(1);
061 private AtomicBoolean completed = new AtomicBoolean(false);
062 private long duration = -1;
063 private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
064 private String dotOutputDir;
065 private String routesOutputFile;
066 private boolean aggregateDot;
067 private boolean debug;
068 private boolean trace;
069 private List<RouteBuilder> routeBuilders = new ArrayList<RouteBuilder>();
070 private List<SpringCamelContext> camelContexts = new ArrayList<SpringCamelContext>();
071 private AbstractApplicationContext parentApplicationContext;
072 private String parentApplicationContextUri;
073 private ProducerTemplate camelTemplate;
074
075 public Main() {
076 addOption(new Option("h", "help", "Displays the help screen") {
077 protected void doProcess(String arg, LinkedList<String> remainingArgs) {
078 showOptions();
079 completed();
080 }
081 });
082
083 addOption(new ParameterOption("a", "applicationContext",
084 "Sets the classpath based spring ApplicationContext", "applicationContext") {
085 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
086 setApplicationContextUri(parameter);
087 }
088 });
089
090 addOption(new ParameterOption("fa", "fileApplicationContext",
091 "Sets the filesystem based spring ApplicationContext", "fileApplicationContext") {
092 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
093 setFileApplicationContextUri(parameter);
094 }
095 });
096
097 addOption(new ParameterOption("o", "outdir",
098 "Sets the DOT output directory where the visual representations of the routes are generated",
099 "dot") {
100 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
101 setDotOutputDir(parameter);
102 }
103 });
104 addOption(new ParameterOption("ad", "aggregate-dot",
105 "Aggregates all routes (in addition to individual route generation) into one context to create one monolithic DOT file for visual representations the entire system.",
106 "aggregate-dot") {
107 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
108 setAggregateDot("true".equals(parameter));
109 }
110 });
111 addOption(new ParameterOption("d", "duration",
112 "Sets the time duration that the applicaiton will run for, by default in milliseconds. You can use '10s' for 10 seconds etc",
113 "duration") {
114 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
115 String value = parameter.toUpperCase();
116 if (value.endsWith("S")) {
117 value = value.substring(0, value.length() - 1);
118 setTimeUnit(TimeUnit.SECONDS);
119 }
120 setDuration(Integer.parseInt(value));
121 }
122 });
123
124 addOption(new Option("x", "debug", "Enables the debugger") {
125 protected void doProcess(String arg, LinkedList<String> remainingArgs) {
126 enableDebug();
127 }
128 });
129 addOption(new Option("t", "trace", "Enables tracing") {
130 protected void doProcess(String arg, LinkedList<String> remainingArgs) {
131 enableTrace();
132 }
133 });
134 addOption(new ParameterOption("out", "output", "Output all routes to the specified XML file", "filename") {
135 protected void doProcess(String arg, String parameter,
136 LinkedList<String> remainingArgs) {
137 setRoutesOutputFile(parameter);
138 }
139 });
140 }
141
142 public static void main(String... args) {
143 Main main = new Main();
144 instance = main;
145 main.run(args);
146 }
147
148 /**
149 * Returns the currently executing main
150 *
151 * @return the current running instance
152 */
153 public static Main getInstance() {
154 return instance;
155 }
156
157 /**
158 * Parses the command line arguments then runs the program
159 */
160 public void run(String[] args) {
161 parseArguments(args);
162 run();
163 }
164
165 /**
166 * Runs this process with the given arguments
167 */
168 public void run() {
169 if (!completed.get()) {
170 try {
171 start();
172 waitUntilCompleted();
173 stop();
174 } catch (Exception e) {
175 LOG.error("Failed: " + e, e);
176 }
177 }
178 }
179
180 /**
181 * Marks this process as being completed
182 */
183 public void completed() {
184 completed.set(true);
185 latch.countDown();
186 }
187
188 public void addRouteBuilder(RouteBuilder routeBuilder) {
189 getRouteBuilders().add(routeBuilder);
190 }
191
192 /**
193 * Displays the command line options
194 */
195 public void showOptions() {
196 System.out.println("Apache Camel Runner takes the following options");
197 System.out.println();
198
199 for (Option option : options) {
200 System.out.println(" " + option.getAbbreviation() + " or " + option.getFullName() + " = "
201 + option.getDescription());
202 }
203 }
204
205 /**
206 * Parses the command line arguments
207 */
208 public void parseArguments(String[] arguments) {
209 LinkedList<String> args = new LinkedList<String>(Arrays.asList(arguments));
210
211 boolean valid = true;
212 while (!args.isEmpty()) {
213 String arg = args.removeFirst();
214
215 boolean handled = false;
216 for (Option option : options) {
217 if (option.processOption(arg, args)) {
218 handled = true;
219 break;
220 }
221 }
222 if (!handled) {
223 System.out.println("Unknown option: " + arg);
224 System.out.println();
225 valid = false;
226 break;
227 }
228 }
229 if (!valid) {
230 showOptions();
231 completed();
232 }
233 }
234
235 public void addOption(Option option) {
236 options.add(option);
237 }
238
239 public abstract class Option {
240 private String abbreviation;
241 private String fullName;
242 private String description;
243
244 protected Option(String abbreviation, String fullName, String description) {
245 this.abbreviation = "-" + abbreviation;
246 this.fullName = "-" + fullName;
247 this.description = description;
248 }
249
250 public boolean processOption(String arg, LinkedList<String> remainingArgs) {
251 if (arg.equalsIgnoreCase(abbreviation) || fullName.startsWith(arg)) {
252 doProcess(arg, remainingArgs);
253 return true;
254 }
255 return false;
256 }
257
258 public String getAbbreviation() {
259 return abbreviation;
260 }
261
262 public String getDescription() {
263 return description;
264 }
265
266 public String getFullName() {
267 return fullName;
268 }
269
270 protected abstract void doProcess(String arg, LinkedList<String> remainingArgs);
271 }
272
273 public abstract class ParameterOption extends Option {
274 private String parameterName;
275
276 protected ParameterOption(String abbreviation, String fullName, String description,
277 String parameterName) {
278 super(abbreviation, fullName, description);
279 this.parameterName = parameterName;
280 }
281
282 protected void doProcess(String arg, LinkedList<String> remainingArgs) {
283 if (remainingArgs.isEmpty()) {
284 System.err.println("Expected fileName for ");
285 showOptions();
286 completed();
287 } else {
288 String parameter = remainingArgs.removeFirst();
289 doProcess(arg, parameter, remainingArgs);
290 }
291 }
292
293 protected abstract void doProcess(String arg, String parameter, LinkedList<String> remainingArgs);
294 }
295
296 // Properties
297 // -------------------------------------------------------------------------
298 public AbstractApplicationContext getApplicationContext() {
299 return applicationContext;
300 }
301
302 public void setApplicationContext(AbstractApplicationContext applicationContext) {
303 this.applicationContext = applicationContext;
304 }
305
306 public String getApplicationContextUri() {
307 return applicationContextUri;
308 }
309
310 public void setApplicationContextUri(String applicationContextUri) {
311 this.applicationContextUri = applicationContextUri;
312 }
313
314 public String getFileApplicationContextUri() {
315 return fileApplicationContextUri;
316 }
317
318 public void setFileApplicationContextUri(String fileApplicationContextUri) {
319 this.fileApplicationContextUri = fileApplicationContextUri;
320 }
321
322 public AbstractApplicationContext getParentApplicationContext() {
323 if (parentApplicationContext == null) {
324 if (parentApplicationContextUri != null) {
325 parentApplicationContext = new ClassPathXmlApplicationContext(parentApplicationContextUri);
326 parentApplicationContext.start();
327 }
328 }
329 return parentApplicationContext;
330 }
331
332 public void setParentApplicationContext(AbstractApplicationContext parentApplicationContext) {
333 this.parentApplicationContext = parentApplicationContext;
334 }
335
336 public String getParentApplicationContextUri() {
337 return parentApplicationContextUri;
338 }
339
340 public void setParentApplicationContextUri(String parentApplicationContextUri) {
341 this.parentApplicationContextUri = parentApplicationContextUri;
342 }
343
344 public List<SpringCamelContext> getCamelContexts() {
345 return camelContexts;
346 }
347
348 public long getDuration() {
349 return duration;
350 }
351
352 /**
353 * Sets the duration to run the application for in milliseconds until it
354 * should be terminated. Defaults to -1. Any value <= 0 will run forever.
355 *
356 * @param duration
357 */
358 public void setDuration(long duration) {
359 this.duration = duration;
360 }
361
362 public TimeUnit getTimeUnit() {
363 return timeUnit;
364 }
365
366 /**
367 * Sets the time unit duration
368 */
369 public void setTimeUnit(TimeUnit timeUnit) {
370 this.timeUnit = timeUnit;
371 }
372
373 public String getDotOutputDir() {
374 return dotOutputDir;
375 }
376
377 /**
378 * Sets the output directory of the generated DOT Files to show the visual
379 * representation of the routes. A null value disables the dot file
380 * generation
381 */
382 public void setDotOutputDir(String dotOutputDir) {
383 this.dotOutputDir = dotOutputDir;
384 }
385
386 public List<RouteBuilder> getRouteBuilders() {
387 return routeBuilders;
388 }
389
390 public void setRouteBuilders(List<RouteBuilder> routeBuilders) {
391 this.routeBuilders = routeBuilders;
392 }
393
394 public void setAggregateDot(boolean aggregateDot) {
395 this.aggregateDot = aggregateDot;
396 }
397
398 public boolean isAggregateDot() {
399 return aggregateDot;
400 }
401
402 public boolean isDebug() {
403 return debug;
404 }
405
406 public void enableDebug() {
407 this.debug = true;
408 setParentApplicationContextUri("/META-INF/services/org/apache/camel/spring/debug.xml");
409 }
410
411 public boolean isTrace() {
412 return trace;
413 }
414
415 public void enableTrace() {
416 this.trace = true;
417 setParentApplicationContextUri("/META-INF/services/org/apache/camel/spring/trace.xml");
418 }
419
420 public void setRoutesOutputFile(String routesOutputFile) {
421 this.routesOutputFile = routesOutputFile;
422 }
423
424 public String getRoutesOutputFile() {
425 return routesOutputFile;
426 }
427
428 /**
429 * Returns the currently active debugger if one is enabled
430 *
431 * @return the current debugger or null if none is active
432 * @see #enableDebug()
433 */
434 public Debugger getDebugger() {
435 for (SpringCamelContext camelContext : camelContexts) {
436 Debugger debugger = Debugger.getDebugger(camelContext);
437 if (debugger != null) {
438 return debugger;
439 }
440 }
441 return null;
442 }
443
444 public List<RouteType> getRouteDefinitions() {
445 List<RouteType> answer = new ArrayList<RouteType>();
446 for (SpringCamelContext camelContext : camelContexts) {
447 answer.addAll(camelContext.getRouteDefinitions());
448 }
449 return answer;
450 }
451
452 /**
453 * Returns a {@link ProducerTemplate} from the Spring {@link ApplicationContext} instances
454 * or lazily creates a new one dynamically
455 */
456 public ProducerTemplate getCamelTemplate() {
457 if (camelTemplate == null) {
458 camelTemplate = findOrCreateCamelTemplate();
459 }
460 return camelTemplate;
461 }
462
463 // Implementation methods
464 // -------------------------------------------------------------------------
465 protected ProducerTemplate findOrCreateCamelTemplate() {
466 String[] names = getApplicationContext().getBeanNamesForType(ProducerTemplate.class);
467 if (names != null && names.length > 0) {
468 return (ProducerTemplate) getApplicationContext().getBean(names[0], ProducerTemplate.class);
469 }
470 for (SpringCamelContext camelContext : camelContexts) {
471 return camelContext.createProducerTemplate();
472 }
473 throw new IllegalArgumentException("No CamelContexts are available so cannot create a ProducerTemplate!");
474 }
475
476 protected void doStart() throws Exception {
477 LOG.info("Apache Camel " + getVersion() + " starting");
478 if (applicationContext == null) {
479 applicationContext = createDefaultApplicationContext();
480 }
481 applicationContext.start();
482
483 postProcessContext();
484 }
485
486 protected AbstractApplicationContext createDefaultApplicationContext() {
487 // file based
488 if (getFileApplicationContextUri() != null) {
489 String[] args = getFileApplicationContextUri().split(";");
490
491 ApplicationContext parentContext = getParentApplicationContext();
492 if (parentContext != null) {
493 return new FileSystemXmlApplicationContext(args, parentContext);
494 } else {
495 return new FileSystemXmlApplicationContext(args);
496 }
497 }
498
499 // default to classpath based
500 String[] args = getApplicationContextUri().split(";");
501 ApplicationContext parentContext = getParentApplicationContext();
502 if (parentContext != null) {
503 return new ClassPathXmlApplicationContext(args, parentContext);
504 } else {
505 return new ClassPathXmlApplicationContext(args);
506 }
507 }
508
509 protected void doStop() throws Exception {
510 LOG.info("Apache Camel terminating");
511
512 if (applicationContext != null) {
513 applicationContext.close();
514 }
515 }
516
517 protected void waitUntilCompleted() {
518 while (!completed.get()) {
519 try {
520 if (duration > 0) {
521 TimeUnit unit = getTimeUnit();
522 LOG.info("Waiting for: " + duration + " " + unit);
523 latch.await(duration, unit);
524 completed.set(true);
525 } else {
526 latch.await();
527 }
528 } catch (InterruptedException e) {
529 LOG.debug("Caught: " + e);
530 }
531 }
532 }
533
534 protected void postProcessContext() throws Exception {
535 Map<String, SpringCamelContext> map = applicationContext.getBeansOfType(SpringCamelContext.class);
536 Set<Map.Entry<String, SpringCamelContext>> entries = map.entrySet();
537 int size = entries.size();
538 for (Map.Entry<String, SpringCamelContext> entry : entries) {
539 String name = entry.getKey();
540 SpringCamelContext camelContext = entry.getValue();
541 camelContexts.add(camelContext);
542 generateDot(name, camelContext, size);
543 postProcesCamelContext(camelContext);
544 }
545
546 if (isAggregateDot()) {
547 generateDot("aggregate", aggregateSpringCamelContext(applicationContext), 1);
548 }
549
550 if (!"".equals(getRoutesOutputFile())) {
551 outputRoutesToFile();
552 }
553 }
554
555 private void outputRoutesToFile() throws IOException {
556 if (ObjectHelper.isNotNullAndNonEmpty(getRoutesOutputFile())) {
557 LOG.info("Generating routes as XML in the file named: " + getRoutesOutputFile());
558 ModelFileGenerator generator = new ModelFileGenerator();
559 generator.marshalRoutesUsingJaxb(getRoutesOutputFile(), getRouteDefinitions());
560 }
561 }
562
563 protected void generateDot(String name, SpringCamelContext camelContext, int size) throws IOException {
564 String outputDir = dotOutputDir;
565 if (ObjectHelper.isNotNullAndNonEmpty(outputDir)) {
566 if (size > 1) {
567 outputDir += "/" + name;
568 }
569 RouteDotGenerator generator = new RouteDotGenerator(outputDir);
570 LOG.info("Generating DOT file for routes: " + outputDir + " for: " + camelContext + " with name: " + name);
571 generator.drawRoutes(camelContext);
572 }
573 }
574
575 /**
576 * Used for aggregate dot generation
577 *
578 * @param applicationContext
579 * @return
580 * @throws Exception
581 */
582 private static SpringCamelContext aggregateSpringCamelContext(ApplicationContext applicationContext) throws Exception {
583 SpringCamelContext aggregateCamelContext = new SpringCamelContext() {
584 /**
585 * Don't actually start this, it is merely fabricated for dot generation.
586 * @see org.apache.camel.impl.DefaultCamelContext#shouldStartRoutes()
587 */
588 protected boolean shouldStartRoutes() {
589
590 return false;
591 }
592 };
593
594 // look up all configured camel contexts
595 String[] names = applicationContext.getBeanNamesForType(SpringCamelContext.class);
596 for (String name : names) {
597
598 SpringCamelContext next = (SpringCamelContext) applicationContext.getBean(name, SpringCamelContext.class);
599 // aggregateCamelContext.addRoutes( next.getRoutes() );
600 aggregateCamelContext.addRouteDefinitions(next.getRouteDefinitions());
601 }
602 // Don't actually start this, it is merely fabricated for dot generation.
603 // answer.setApplicationContext( applicationContext );
604 // answer.afterPropertiesSet();
605 return aggregateCamelContext;
606 }
607
608 protected void postProcesCamelContext(CamelContext camelContext) throws Exception {
609 for (RouteBuilder routeBuilder : routeBuilders) {
610 camelContext.addRoutes(routeBuilder);
611 }
612 }
613
614 protected String getVersion() {
615 Package aPackage = Package.getPackage("org.apache.camel");
616 if (aPackage != null) {
617 String version = aPackage.getImplementationVersion();
618 if (version == null) {
619 version = aPackage.getSpecificationVersion();
620 if (version == null) {
621 version = "";
622 }
623 }
624 return version;
625 }
626 return "";
627 }
628 }