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