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 */ 017package org.apache.camel.main; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.LinkedList; 023import java.util.List; 024import java.util.Locale; 025import java.util.Map; 026import java.util.Set; 027import java.util.concurrent.CountDownLatch; 028import java.util.concurrent.TimeUnit; 029import java.util.concurrent.atomic.AtomicBoolean; 030import javax.xml.bind.JAXBException; 031 032import org.apache.camel.CamelContext; 033import org.apache.camel.ProducerTemplate; 034import org.apache.camel.builder.RouteBuilder; 035import org.apache.camel.impl.DefaultCamelContext; 036import org.apache.camel.impl.DefaultModelJAXBContextFactory; 037import org.apache.camel.model.ModelCamelContext; 038import org.apache.camel.model.RouteDefinition; 039import org.apache.camel.spi.ModelJAXBContextFactory; 040import org.apache.camel.support.ServiceSupport; 041import org.apache.camel.util.ObjectHelper; 042import org.apache.camel.util.ServiceHelper; 043import org.apache.camel.view.ModelFileGenerator; 044import org.apache.camel.view.RouteDotGenerator; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048/** 049 * Base class for main implementations to allow starting up a JVM with Camel embedded. 050 * 051 * @version 052 */ 053public abstract class MainSupport extends ServiceSupport { 054 protected static final Logger LOG = LoggerFactory.getLogger(MainSupport.class); 055 protected String dotOutputDir; 056 protected final List<Option> options = new ArrayList<Option>(); 057 protected final CountDownLatch latch = new CountDownLatch(1); 058 protected final AtomicBoolean completed = new AtomicBoolean(false); 059 protected long duration = -1; 060 protected TimeUnit timeUnit = TimeUnit.MILLISECONDS; 061 protected String routesOutputFile; 062 protected boolean aggregateDot; 063 protected boolean trace; 064 protected List<RouteBuilder> routeBuilders = new ArrayList<RouteBuilder>(); 065 protected String routeBuilderClasses; 066 protected final List<CamelContext> camelContexts = new ArrayList<CamelContext>(); 067 protected ProducerTemplate camelTemplate; 068 069 /** 070 * A class for intercepting the hang up signal and do a graceful shutdown of the Camel. 071 */ 072 private static final class HangupInterceptor extends Thread { 073 Logger log = LoggerFactory.getLogger(this.getClass()); 074 MainSupport mainInstance; 075 076 public HangupInterceptor(MainSupport main) { 077 mainInstance = main; 078 } 079 080 @Override 081 public void run() { 082 log.info("Received hang up - stopping the main instance."); 083 try { 084 mainInstance.stop(); 085 } catch (Exception ex) { 086 log.warn("Error during stopping the main instance.", ex); 087 } 088 } 089 } 090 091 protected MainSupport() { 092 addOption(new Option("h", "help", "Displays the help screen") { 093 protected void doProcess(String arg, LinkedList<String> remainingArgs) { 094 showOptions(); 095 completed(); 096 } 097 }); 098 addOption(new ParameterOption("r", "routers", 099 "Sets the router builder classes which will be loaded while starting the camel context", 100 "routerBuilderClasses") { 101 @Override 102 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 103 setRouteBuilderClasses(parameter); 104 } 105 }); 106 addOption(new ParameterOption("o", "outdir", 107 "Sets the DOT output directory where the visual representations of the routes are generated", 108 "dot") { 109 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 110 setDotOutputDir(parameter); 111 } 112 }); 113 addOption(new ParameterOption("ad", "aggregate-dot", 114 "Aggregates all routes (in addition to individual route generation) into one context to create one monolithic DOT file for visual representations the entire system.", 115 "aggregate-dot") { 116 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 117 setAggregateDot("true".equals(parameter)); 118 } 119 }); 120 addOption(new ParameterOption("d", "duration", 121 "Sets the time duration that the application will run for, by default in milliseconds. You can use '10s' for 10 seconds etc", 122 "duration") { 123 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 124 String value = parameter.toUpperCase(Locale.ENGLISH); 125 if (value.endsWith("S")) { 126 value = value.substring(0, value.length() - 1); 127 setTimeUnit(TimeUnit.SECONDS); 128 } 129 setDuration(Integer.parseInt(value)); 130 } 131 }); 132 addOption(new Option("t", "trace", "Enables tracing") { 133 protected void doProcess(String arg, LinkedList<String> remainingArgs) { 134 enableTrace(); 135 } 136 }); 137 addOption(new ParameterOption("out", "output", "Output all routes to the specified XML file", "filename") { 138 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 139 setRoutesOutputFile(parameter); 140 } 141 }); 142 } 143 144 /** 145 * Runs this process with the given arguments, and will wait until completed, or the JVM terminates. 146 */ 147 public void run() throws Exception { 148 if (!completed.get()) { 149 // if we have an issue starting then propagate the exception to caller 150 start(); 151 try { 152 afterStart(); 153 waitUntilCompleted(); 154 internalBeforeStop(); 155 beforeStop(); 156 stop(); 157 } catch (Exception e) { 158 // however while running then just log errors 159 LOG.error("Failed: " + e, e); 160 } 161 } 162 } 163 164 /** 165 * Enables the hangup support. Gracefully stops by calling stop() on a 166 * Hangup signal. 167 */ 168 public void enableHangupSupport() { 169 HangupInterceptor interceptor = new HangupInterceptor(this); 170 Runtime.getRuntime().addShutdownHook(interceptor); 171 } 172 173 /** 174 * Callback to run custom logic after CamelContext has been started. 175 */ 176 protected void afterStart() throws Exception { 177 // noop 178 } 179 180 /** 181 * Callback to run custom logic before CamelContext is being stopped. 182 */ 183 protected void beforeStop() throws Exception { 184 // noop 185 } 186 187 private void internalBeforeStop() { 188 try { 189 if (camelTemplate != null) { 190 ServiceHelper.stopService(camelTemplate); 191 camelTemplate = null; 192 } 193 } catch (Exception e) { 194 LOG.debug("Error stopping camelTemplate due " + e.getMessage() + ". This exception is ignored.", e); 195 } 196 } 197 198 /** 199 * Marks this process as being completed. 200 */ 201 public void completed() { 202 completed.set(true); 203 latch.countDown(); 204 } 205 206 /** 207 * Displays the command line options. 208 */ 209 public void showOptions() { 210 showOptionsHeader(); 211 212 for (Option option : options) { 213 System.out.println(option.getInformation()); 214 } 215 } 216 217 /** 218 * Parses the command line arguments. 219 */ 220 public void parseArguments(String[] arguments) { 221 LinkedList<String> args = new LinkedList<String>(Arrays.asList(arguments)); 222 223 boolean valid = true; 224 while (!args.isEmpty()) { 225 String arg = args.removeFirst(); 226 227 boolean handled = false; 228 for (Option option : options) { 229 if (option.processOption(arg, args)) { 230 handled = true; 231 break; 232 } 233 } 234 if (!handled) { 235 System.out.println("Unknown option: " + arg); 236 System.out.println(); 237 valid = false; 238 break; 239 } 240 } 241 if (!valid) { 242 showOptions(); 243 completed(); 244 } 245 } 246 247 public void addOption(Option option) { 248 options.add(option); 249 } 250 251 public long getDuration() { 252 return duration; 253 } 254 255 /** 256 * Sets the duration to run the application for in milliseconds until it 257 * should be terminated. Defaults to -1. Any value <= 0 will run forever. 258 */ 259 public void setDuration(long duration) { 260 this.duration = duration; 261 } 262 263 public TimeUnit getTimeUnit() { 264 return timeUnit; 265 } 266 267 /** 268 * Sets the time unit duration. 269 */ 270 public void setTimeUnit(TimeUnit timeUnit) { 271 this.timeUnit = timeUnit; 272 } 273 274 public String getDotOutputDir() { 275 return dotOutputDir; 276 } 277 278 public void setRouteBuilderClasses(String builders) { 279 this.routeBuilderClasses = builders; 280 } 281 282 public String getRouteBuilderClasses() { 283 return routeBuilderClasses; 284 } 285 286 /** 287 * Sets the output directory of the generated DOT Files to show the visual 288 * representation of the routes. A null value disables the dot file 289 * generation. 290 */ 291 public void setDotOutputDir(String dotOutputDir) { 292 this.dotOutputDir = dotOutputDir; 293 } 294 295 public void setAggregateDot(boolean aggregateDot) { 296 this.aggregateDot = aggregateDot; 297 } 298 299 public boolean isAggregateDot() { 300 return aggregateDot; 301 } 302 303 public boolean isTrace() { 304 return trace; 305 } 306 307 public void enableTrace() { 308 this.trace = true; 309 for (CamelContext context : camelContexts) { 310 context.setTracing(true); 311 } 312 } 313 314 public void setRoutesOutputFile(String routesOutputFile) { 315 this.routesOutputFile = routesOutputFile; 316 } 317 318 public String getRoutesOutputFile() { 319 return routesOutputFile; 320 } 321 322 protected void doStop() throws Exception { 323 LOG.info("Apache Camel " + getVersion() + " stopping"); 324 // call completed to properly stop as we count down the waiting latch 325 completed(); 326 } 327 328 protected void doStart() throws Exception { 329 LOG.info("Apache Camel " + getVersion() + " starting"); 330 } 331 332 protected void waitUntilCompleted() { 333 while (!completed.get()) { 334 try { 335 if (duration > 0) { 336 TimeUnit unit = getTimeUnit(); 337 LOG.info("Waiting for: " + duration + " " + unit); 338 latch.await(duration, unit); 339 completed.set(true); 340 } else { 341 latch.await(); 342 } 343 } catch (InterruptedException e) { 344 Thread.currentThread().interrupt(); 345 } 346 } 347 } 348 349 /** 350 * Parses the command line arguments then runs the program. 351 */ 352 public void run(String[] args) throws Exception { 353 parseArguments(args); 354 run(); 355 } 356 357 /** 358 * Displays the header message for the command line options. 359 */ 360 public void showOptionsHeader() { 361 System.out.println("Apache Camel Runner takes the following options"); 362 System.out.println(); 363 } 364 365 public List<CamelContext> getCamelContexts() { 366 return camelContexts; 367 } 368 369 public List<RouteBuilder> getRouteBuilders() { 370 return routeBuilders; 371 } 372 373 public void setRouteBuilders(List<RouteBuilder> routeBuilders) { 374 this.routeBuilders = routeBuilders; 375 } 376 377 public List<RouteDefinition> getRouteDefinitions() { 378 List<RouteDefinition> answer = new ArrayList<RouteDefinition>(); 379 for (CamelContext camelContext : camelContexts) { 380 answer.addAll(((ModelCamelContext)camelContext).getRouteDefinitions()); 381 } 382 return answer; 383 } 384 385 public ProducerTemplate getCamelTemplate() throws Exception { 386 if (camelTemplate == null) { 387 camelTemplate = findOrCreateCamelTemplate(); 388 } 389 return camelTemplate; 390 } 391 392 protected abstract ProducerTemplate findOrCreateCamelTemplate(); 393 394 protected abstract Map<String, CamelContext> getCamelContextMap(); 395 396 protected void postProcessContext() throws Exception { 397 Map<String, CamelContext> map = getCamelContextMap(); 398 Set<Map.Entry<String, CamelContext>> entries = map.entrySet(); 399 int size = entries.size(); 400 for (Map.Entry<String, CamelContext> entry : entries) { 401 String name = entry.getKey(); 402 CamelContext camelContext = entry.getValue(); 403 camelContexts.add(camelContext); 404 generateDot(name, camelContext, size); 405 postProcessCamelContext(camelContext); 406 } 407 408 if (isAggregateDot()) { 409 generateDot("aggregate", aggregateCamelContext(), 1); 410 } 411 412 if (!"".equals(getRoutesOutputFile())) { 413 outputRoutesToFile(); 414 } 415 } 416 417 protected void outputRoutesToFile() throws IOException, JAXBException { 418 if (ObjectHelper.isNotEmpty(getRoutesOutputFile())) { 419 LOG.info("Generating routes as XML in the file named: " + getRoutesOutputFile()); 420 ModelFileGenerator generator = createModelFileGenerator(); 421 generator.marshalRoutesUsingJaxb(getRoutesOutputFile(), getRouteDefinitions()); 422 } 423 } 424 425 protected ModelFileGenerator createModelFileGenerator() throws JAXBException { 426 return new ModelFileGenerator(getModelJAXBContextFactory().newJAXBContext()); 427 } 428 429 public ModelJAXBContextFactory getModelJAXBContextFactory() { 430 return new DefaultModelJAXBContextFactory(); 431 } 432 433 protected void generateDot(String name, CamelContext camelContext, int size) throws IOException { 434 String outputDir = dotOutputDir; 435 if (ObjectHelper.isNotEmpty(outputDir)) { 436 if (size > 1) { 437 outputDir += "/" + name; 438 } 439 RouteDotGenerator generator = new RouteDotGenerator(outputDir); 440 LOG.info("Generating DOT file for routes: " + outputDir + " for: " + camelContext + " with name: " + name); 441 generator.drawRoutes(camelContext); 442 } 443 } 444 445 /** 446 * Used for aggregate dot generation, generate a single camel context containing all of the available contexts. 447 */ 448 private CamelContext aggregateCamelContext() throws Exception { 449 if (camelContexts.size() == 1) { 450 return camelContexts.get(0); 451 } else { 452 ModelCamelContext answer = new DefaultCamelContext(); 453 for (CamelContext camelContext : camelContexts) { 454 answer.addRouteDefinitions(((ModelCamelContext)camelContext).getRouteDefinitions()); 455 } 456 return answer; 457 } 458 } 459 460 protected void loadRouteBuilders(CamelContext camelContext) throws Exception { 461 if (routeBuilderClasses != null) { 462 // get the list of route builder classes 463 String[] routeClasses = routeBuilderClasses.split(","); 464 for (String routeClass : routeClasses) { 465 Class<?> routeClazz = camelContext.getClassResolver().resolveClass(routeClass); 466 RouteBuilder builder = (RouteBuilder) routeClazz.newInstance(); 467 getRouteBuilders().add(builder); 468 } 469 } 470 } 471 472 protected void postProcessCamelContext(CamelContext camelContext) throws Exception { 473 // try to load the route builders from the routeBuilderClasses 474 loadRouteBuilders(camelContext); 475 for (RouteBuilder routeBuilder : routeBuilders) { 476 camelContext.addRoutes(routeBuilder); 477 } 478 } 479 480 public void addRouteBuilder(RouteBuilder routeBuilder) { 481 getRouteBuilders().add(routeBuilder); 482 } 483 484 public abstract class Option { 485 private String abbreviation; 486 private String fullName; 487 private String description; 488 489 protected Option(String abbreviation, String fullName, String description) { 490 this.abbreviation = "-" + abbreviation; 491 this.fullName = "-" + fullName; 492 this.description = description; 493 } 494 495 public boolean processOption(String arg, LinkedList<String> remainingArgs) { 496 if (arg.equalsIgnoreCase(abbreviation) || fullName.startsWith(arg)) { 497 doProcess(arg, remainingArgs); 498 return true; 499 } 500 return false; 501 } 502 503 public String getAbbreviation() { 504 return abbreviation; 505 } 506 507 public String getDescription() { 508 return description; 509 } 510 511 public String getFullName() { 512 return fullName; 513 } 514 515 public String getInformation() { 516 return " " + getAbbreviation() + " or " + getFullName() + " = " + getDescription(); 517 } 518 519 protected abstract void doProcess(String arg, LinkedList<String> remainingArgs); 520 } 521 522 public abstract class ParameterOption extends Option { 523 private String parameterName; 524 525 protected ParameterOption(String abbreviation, String fullName, String description, String parameterName) { 526 super(abbreviation, fullName, description); 527 this.parameterName = parameterName; 528 } 529 530 protected void doProcess(String arg, LinkedList<String> remainingArgs) { 531 if (remainingArgs.isEmpty()) { 532 System.err.println("Expected fileName for "); 533 showOptions(); 534 completed(); 535 } else { 536 String parameter = remainingArgs.removeFirst(); 537 doProcess(arg, parameter, remainingArgs); 538 } 539 } 540 541 public String getInformation() { 542 return " " + getAbbreviation() + " or " + getFullName() + " <" + parameterName + "> = " + getDescription(); 543 } 544 545 protected abstract void doProcess(String arg, String parameter, LinkedList<String> remainingArgs); 546 } 547}