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