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