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