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: 45506 $ 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 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 }