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.util.ArrayList; 020import java.util.Arrays; 021import java.util.LinkedList; 022import java.util.List; 023import java.util.Locale; 024import java.util.Map; 025import java.util.Set; 026import java.util.concurrent.CountDownLatch; 027import java.util.concurrent.TimeUnit; 028import java.util.concurrent.atomic.AtomicBoolean; 029import java.util.concurrent.atomic.AtomicInteger; 030 031import org.apache.camel.CamelContext; 032import org.apache.camel.ProducerTemplate; 033import org.apache.camel.builder.RouteBuilder; 034import org.apache.camel.impl.DefaultModelJAXBContextFactory; 035import org.apache.camel.model.ModelCamelContext; 036import org.apache.camel.model.RouteDefinition; 037import org.apache.camel.spi.ModelJAXBContextFactory; 038import org.apache.camel.support.ServiceSupport; 039import org.apache.camel.util.ServiceHelper; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043/** 044 * Base class for main implementations to allow starting up a JVM with Camel embedded. 045 * 046 * @version 047 */ 048public abstract class MainSupport extends ServiceSupport { 049 protected static final Logger LOG = LoggerFactory.getLogger(MainSupport.class); 050 protected static final int UNINITIALIZED_EXIT_CODE = Integer.MIN_VALUE; 051 protected static final int DEFAULT_EXIT_CODE = 0; 052 protected final List<MainListener> listeners = new ArrayList<MainListener>(); 053 protected final List<Option> options = new ArrayList<Option>(); 054 protected final CountDownLatch latch = new CountDownLatch(1); 055 protected final AtomicBoolean completed = new AtomicBoolean(false); 056 protected final AtomicInteger exitCode = new AtomicInteger(UNINITIALIZED_EXIT_CODE); 057 protected long duration = -1; 058 protected TimeUnit timeUnit = TimeUnit.MILLISECONDS; 059 protected boolean trace; 060 protected List<RouteBuilder> routeBuilders = new ArrayList<RouteBuilder>(); 061 protected String routeBuilderClasses; 062 protected final List<CamelContext> camelContexts = new ArrayList<CamelContext>(); 063 protected ProducerTemplate camelTemplate; 064 protected boolean hangupInterceptorEnabled = true; 065 protected int durationHitExitCode = DEFAULT_EXIT_CODE; 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 final MainSupport mainInstance; 073 074 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("d", "duration", 105 "Sets the time duration that the application will run for, by default in milliseconds. You can use '10s' for 10 seconds etc", 106 "duration") { 107 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 108 String value = parameter.toUpperCase(Locale.ENGLISH); 109 if (value.endsWith("S")) { 110 value = value.substring(0, value.length() - 1); 111 setTimeUnit(TimeUnit.SECONDS); 112 } 113 setDuration(Integer.parseInt(value)); 114 } 115 }); 116 addOption(new Option("t", "trace", "Enables tracing") { 117 protected void doProcess(String arg, LinkedList<String> remainingArgs) { 118 enableTrace(); 119 } 120 }); 121 addOption(new ParameterOption("e", "exitcode", 122 "Sets the exit code if duration was hit", 123 "exitcode") { 124 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 125 setDurationHitExitCode(Integer.parseInt(parameter)); 126 } 127 }); 128 } 129 130 /** 131 * Runs this process with the given arguments, and will wait until completed, or the JVM terminates. 132 */ 133 public void run() throws Exception { 134 if (!completed.get()) { 135 internalBeforeStart(); 136 // if we have an issue starting then propagate the exception to caller 137 beforeStart(); 138 start(); 139 try { 140 afterStart(); 141 waitUntilCompleted(); 142 internalBeforeStop(); 143 beforeStop(); 144 stop(); 145 afterStop(); 146 } catch (Exception e) { 147 // however while running then just log errors 148 LOG.error("Failed: " + e, e); 149 } 150 } 151 } 152 153 /** 154 * Disable the hangup support. No graceful stop by calling stop() on a 155 * Hangup signal. 156 */ 157 public void disableHangupSupport() { 158 hangupInterceptorEnabled = false; 159 } 160 161 /** 162 * Hangup support is enabled by default. 163 * 164 * @deprecated is enabled by default now, so no longer need to call this method. 165 */ 166 @Deprecated 167 public void enableHangupSupport() { 168 hangupInterceptorEnabled = true; 169 } 170 171 /** 172 * Adds a {@link org.apache.camel.main.MainListener} to receive callbacks when the main is started or stopping 173 * 174 * @param listener the listener 175 */ 176 public void addMainListener(MainListener listener) { 177 listeners.add(listener); 178 } 179 180 /** 181 * Removes the {@link org.apache.camel.main.MainListener} 182 * 183 * @param listener the listener 184 */ 185 public void removeMainListener(MainListener listener) { 186 listeners.remove(listener); 187 } 188 189 /** 190 * Callback to run custom logic before CamelContext is being started. 191 * <p/> 192 * It is recommended to use {@link org.apache.camel.main.MainListener} instead. 193 */ 194 protected void beforeStart() throws Exception { 195 for (MainListener listener : listeners) { 196 listener.beforeStart(this); 197 } 198 } 199 200 /** 201 * Callback to run custom logic after CamelContext has been started. 202 * <p/> 203 * It is recommended to use {@link org.apache.camel.main.MainListener} instead. 204 */ 205 protected void afterStart() throws Exception { 206 for (MainListener listener : listeners) { 207 listener.afterStart(this); 208 } 209 } 210 211 private void internalBeforeStart() { 212 if (hangupInterceptorEnabled) { 213 Runtime.getRuntime().addShutdownHook(new HangupInterceptor(this)); 214 } 215 } 216 217 /** 218 * Callback to run custom logic before CamelContext is being stopped. 219 * <p/> 220 * It is recommended to use {@link org.apache.camel.main.MainListener} instead. 221 */ 222 protected void beforeStop() throws Exception { 223 for (MainListener listener : listeners) { 224 listener.beforeStop(this); 225 } 226 } 227 228 /** 229 * Callback to run custom logic after CamelContext has been stopped. 230 * <p/> 231 * It is recommended to use {@link org.apache.camel.main.MainListener} instead. 232 */ 233 protected void afterStop() throws Exception { 234 for (MainListener listener : listeners) { 235 listener.afterStop(this); 236 } 237 } 238 239 private void internalBeforeStop() { 240 try { 241 if (camelTemplate != null) { 242 ServiceHelper.stopService(camelTemplate); 243 camelTemplate = null; 244 } 245 } catch (Exception e) { 246 LOG.debug("Error stopping camelTemplate due " + e.getMessage() + ". This exception is ignored.", e); 247 } 248 } 249 250 /** 251 * Marks this process as being completed. 252 */ 253 public void completed() { 254 completed.set(true); 255 exitCode.compareAndSet(UNINITIALIZED_EXIT_CODE, DEFAULT_EXIT_CODE); 256 latch.countDown(); 257 } 258 259 /** 260 * Displays the command line options. 261 */ 262 public void showOptions() { 263 showOptionsHeader(); 264 265 for (Option option : options) { 266 System.out.println(option.getInformation()); 267 } 268 } 269 270 /** 271 * Parses the command line arguments. 272 */ 273 public void parseArguments(String[] arguments) { 274 LinkedList<String> args = new LinkedList<String>(Arrays.asList(arguments)); 275 276 boolean valid = true; 277 while (!args.isEmpty()) { 278 String arg = args.removeFirst(); 279 280 boolean handled = false; 281 for (Option option : options) { 282 if (option.processOption(arg, args)) { 283 handled = true; 284 break; 285 } 286 } 287 if (!handled) { 288 System.out.println("Unknown option: " + arg); 289 System.out.println(); 290 valid = false; 291 break; 292 } 293 } 294 if (!valid) { 295 showOptions(); 296 completed(); 297 } 298 } 299 300 public void addOption(Option option) { 301 options.add(option); 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 public void setDuration(long duration) { 313 this.duration = duration; 314 } 315 316 public TimeUnit getTimeUnit() { 317 return timeUnit; 318 } 319 320 /** 321 * Sets the time unit duration. 322 */ 323 public void setTimeUnit(TimeUnit timeUnit) { 324 this.timeUnit = timeUnit; 325 } 326 327 /** 328 * Sets the exit code for the application if duration was hit 329 */ 330 public void setDurationHitExitCode(int durationHitExitCode) { 331 this.durationHitExitCode = durationHitExitCode; 332 } 333 334 public int getDurationHitExitCode() { 335 return durationHitExitCode; 336 } 337 338 public int getExitCode() { 339 return exitCode.get(); 340 } 341 342 343 public void setRouteBuilderClasses(String builders) { 344 this.routeBuilderClasses = builders; 345 } 346 347 public String getRouteBuilderClasses() { 348 return routeBuilderClasses; 349 } 350 351 public boolean isTrace() { 352 return trace; 353 } 354 355 public void enableTrace() { 356 this.trace = true; 357 } 358 359 protected void doStop() throws Exception { 360 // call completed to properly stop as we count down the waiting latch 361 completed(); 362 } 363 364 protected void doStart() throws Exception { 365 } 366 367 protected void waitUntilCompleted() { 368 while (!completed.get()) { 369 try { 370 if (duration > 0) { 371 TimeUnit unit = getTimeUnit(); 372 LOG.info("Waiting for: " + duration + " " + unit); 373 latch.await(duration, unit); 374 exitCode.compareAndSet(UNINITIALIZED_EXIT_CODE, durationHitExitCode); 375 completed.set(true); 376 } else { 377 latch.await(); 378 } 379 } catch (InterruptedException e) { 380 Thread.currentThread().interrupt(); 381 } 382 } 383 } 384 385 /** 386 * Parses the command line arguments then runs the program. 387 */ 388 public void run(String[] args) throws Exception { 389 parseArguments(args); 390 run(); 391 } 392 393 /** 394 * Displays the header message for the command line options. 395 */ 396 public void showOptionsHeader() { 397 System.out.println("Apache Camel Runner takes the following options"); 398 System.out.println(); 399 } 400 401 public List<CamelContext> getCamelContexts() { 402 return camelContexts; 403 } 404 405 public List<RouteBuilder> getRouteBuilders() { 406 return routeBuilders; 407 } 408 409 public void setRouteBuilders(List<RouteBuilder> routeBuilders) { 410 this.routeBuilders = routeBuilders; 411 } 412 413 public List<RouteDefinition> getRouteDefinitions() { 414 List<RouteDefinition> answer = new ArrayList<RouteDefinition>(); 415 for (CamelContext camelContext : camelContexts) { 416 answer.addAll(((ModelCamelContext)camelContext).getRouteDefinitions()); 417 } 418 return answer; 419 } 420 421 public ProducerTemplate getCamelTemplate() throws Exception { 422 if (camelTemplate == null) { 423 camelTemplate = findOrCreateCamelTemplate(); 424 } 425 return camelTemplate; 426 } 427 428 protected abstract ProducerTemplate findOrCreateCamelTemplate(); 429 430 protected abstract Map<String, CamelContext> getCamelContextMap(); 431 432 protected void postProcessContext() throws Exception { 433 Map<String, CamelContext> map = getCamelContextMap(); 434 Set<Map.Entry<String, CamelContext>> entries = map.entrySet(); 435 for (Map.Entry<String, CamelContext> entry : entries) { 436 CamelContext camelContext = entry.getValue(); 437 camelContexts.add(camelContext); 438 postProcessCamelContext(camelContext); 439 } 440 } 441 442 public ModelJAXBContextFactory getModelJAXBContextFactory() { 443 return new DefaultModelJAXBContextFactory(); 444 } 445 446 protected void loadRouteBuilders(CamelContext camelContext) throws Exception { 447 if (routeBuilderClasses != null) { 448 // get the list of route builder classes 449 String[] routeClasses = routeBuilderClasses.split(","); 450 for (String routeClass : routeClasses) { 451 Class<?> routeClazz = camelContext.getClassResolver().resolveClass(routeClass); 452 RouteBuilder builder = (RouteBuilder) routeClazz.newInstance(); 453 getRouteBuilders().add(builder); 454 } 455 } 456 } 457 458 protected void postProcessCamelContext(CamelContext camelContext) throws Exception { 459 if (trace) { 460 camelContext.setTracing(true); 461 } 462 // try to load the route builders from the routeBuilderClasses 463 loadRouteBuilders(camelContext); 464 for (RouteBuilder routeBuilder : routeBuilders) { 465 camelContext.addRoutes(routeBuilder); 466 } 467 // register lifecycle so we are notified in Camel is stopped from JMX or somewhere else 468 camelContext.addLifecycleStrategy(new MainLifecycleStrategy(completed, latch)); 469 // allow to do configuration before its started 470 for (MainListener listener : listeners) { 471 listener.configure(camelContext); 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}