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