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