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.spring; 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.Map; 025 import java.util.Set; 026 import java.util.concurrent.CountDownLatch; 027 import java.util.concurrent.TimeUnit; 028 import java.util.concurrent.atomic.AtomicBoolean; 029 030 import org.apache.camel.CamelContext; 031 import org.apache.camel.builder.RouteBuilder; 032 import org.apache.camel.impl.ServiceSupport; 033 import org.apache.camel.util.ObjectHelper; 034 import org.apache.camel.view.RouteDotGenerator; 035 import org.apache.commons.logging.Log; 036 import org.apache.commons.logging.LogFactory; 037 import org.springframework.context.ApplicationContext; 038 import org.springframework.context.support.AbstractApplicationContext; 039 import org.springframework.context.support.ClassPathXmlApplicationContext; 040 041 /** 042 * A command line tool for booting up a CamelContext using an optional Spring 043 * ApplicationContext 044 * 045 * @version $Revision: 643788 $ 046 */ 047 public class Main extends ServiceSupport { 048 private static final Log LOG = LogFactory.getLog(Main.class); 049 private String applicationContextUri = "META-INF/spring/*.xml"; 050 private AbstractApplicationContext applicationContext; 051 private List<Option> options = new ArrayList<Option>(); 052 private CountDownLatch latch = new CountDownLatch(1); 053 private AtomicBoolean completed = new AtomicBoolean(false); 054 private long duration = -1; 055 private TimeUnit timeUnit = TimeUnit.MILLISECONDS; 056 private String dotOutputDir; 057 private boolean aggregateDot; 058 private List<RouteBuilder> routeBuilders = new ArrayList<RouteBuilder>(); 059 private List<SpringCamelContext> camelContexts = new ArrayList<SpringCamelContext>(); 060 061 public Main() { 062 addOption(new Option("h", "help", "Displays the help screen") { 063 protected void doProcess(String arg, LinkedList<String> remainingArgs) { 064 showOptions(); 065 completed(); 066 } 067 }); 068 069 addOption(new ParameterOption("a", "applicationContext", 070 "Sets the classpath based pring ApplicationContext", "applicationContext") { 071 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 072 setApplicationContextUri(parameter); 073 } 074 }); 075 addOption(new ParameterOption("o", "outdir", 076 "Sets the DOT output directory where the visual representations of the routes are generated", 077 "dot") { 078 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 079 setDotOutputDir(parameter); 080 } 081 }); 082 addOption(new ParameterOption("ad", "aggregate-dot", 083 "Aggregates all routes (in addition to individual route generation) into one context to create one monolithic DOT file for visual representations the entire system.", 084 "aggregate-dot") { 085 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 086 setAggregateDot("true".equals(parameter)); 087 } 088 }); 089 addOption(new ParameterOption( 090 "d", 091 "duration", 092 "Sets the time duration that the applicaiton will run for, by default in milliseconds. You can use '10s' for 10 seconds etc", 093 "duration") { 094 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) { 095 String value = parameter.toUpperCase(); 096 if (value.endsWith("S")) { 097 value = value.substring(0, value.length() - 1); 098 setTimeUnit(TimeUnit.SECONDS); 099 } 100 setDuration(Integer.parseInt(value)); 101 } 102 }); 103 } 104 105 public static void main(String... args) { 106 new Main().run(args); 107 } 108 109 /** 110 * Parses the command line arguments then runs the program 111 */ 112 public void run(String[] args) { 113 parseArguments(args); 114 run(); 115 } 116 117 /** 118 * Runs this process with the given arguments 119 */ 120 public void run() { 121 if (!completed.get()) { 122 try { 123 start(); 124 waitUntilCompleted(); 125 stop(); 126 } catch (Exception e) { 127 LOG.error("Failed: " + e, e); 128 } 129 } 130 } 131 132 /** 133 * Marks this process as being completed 134 */ 135 public void completed() { 136 completed.set(true); 137 latch.countDown(); 138 } 139 140 public void addRouteBuilder(RouteBuilder routeBuilder) { 141 getRouteBuilders().add(routeBuilder); 142 } 143 144 /** 145 * Displays the command line options 146 */ 147 public void showOptions() { 148 System.out.println("Apache Camel Runner takes the following options"); 149 System.out.println(); 150 151 for (Option option : options) { 152 System.out.println(" " + option.getAbbreviation() + " or " + option.getFullName() + " = " 153 + option.getDescription()); 154 } 155 } 156 157 /** 158 * Parses the commandl ine arguments 159 */ 160 public void parseArguments(String[] arguments) { 161 LinkedList<String> args = new LinkedList<String>(Arrays.asList(arguments)); 162 163 boolean valid = true; 164 while (!args.isEmpty()) { 165 String arg = args.removeFirst(); 166 167 boolean handled = false; 168 for (Option option : options) { 169 if (option.processOption(arg, args)) { 170 handled = true; 171 break; 172 } 173 } 174 if (!handled) { 175 System.out.println("Unknown option: " + arg); 176 System.out.println(); 177 valid = false; 178 break; 179 } 180 } 181 if (!valid) { 182 showOptions(); 183 completed(); 184 } 185 } 186 187 public void addOption(Option option) { 188 options.add(option); 189 } 190 191 public abstract class Option { 192 private String abbreviation; 193 private String fullName; 194 private String description; 195 196 protected Option(String abbreviation, String fullName, String description) { 197 this.abbreviation = "-" + abbreviation; 198 this.fullName = "-" + fullName; 199 this.description = description; 200 } 201 202 public boolean processOption(String arg, LinkedList<String> remainingArgs) { 203 if (arg.equalsIgnoreCase(abbreviation) || fullName.startsWith(arg)) { 204 doProcess(arg, remainingArgs); 205 return true; 206 } 207 return false; 208 } 209 210 public String getAbbreviation() { 211 return abbreviation; 212 } 213 214 public String getDescription() { 215 return description; 216 } 217 218 public String getFullName() { 219 return fullName; 220 } 221 222 protected abstract void doProcess(String arg, LinkedList<String> remainingArgs); 223 } 224 225 public abstract class ParameterOption extends Option { 226 private String parameterName; 227 228 protected ParameterOption(String abbreviation, String fullName, String description, 229 String parameterName) { 230 super(abbreviation, fullName, description); 231 this.parameterName = parameterName; 232 } 233 234 protected void doProcess(String arg, LinkedList<String> remainingArgs) { 235 if (remainingArgs.isEmpty()) { 236 System.err.println("Expected fileName for "); 237 showOptions(); 238 completed(); 239 } else { 240 String parameter = remainingArgs.removeFirst(); 241 doProcess(arg, parameter, remainingArgs); 242 } 243 } 244 245 protected abstract void doProcess(String arg, String parameter, LinkedList<String> remainingArgs); 246 } 247 248 // Properties 249 // ------------------------------------------------------------------------- 250 public AbstractApplicationContext getApplicationContext() { 251 return applicationContext; 252 } 253 254 public void setApplicationContext(AbstractApplicationContext applicationContext) { 255 this.applicationContext = applicationContext; 256 } 257 258 public String getApplicationContextUri() { 259 return applicationContextUri; 260 } 261 262 public void setApplicationContextUri(String applicationContextUri) { 263 this.applicationContextUri = applicationContextUri; 264 } 265 266 public List<SpringCamelContext> getCamelContexts() { 267 return camelContexts; 268 } 269 270 public long getDuration() { 271 return duration; 272 } 273 274 /** 275 * Sets the duration to run the application for in milliseconds until it 276 * should be terminated. Defaults to -1. Any value <= 0 will run forever. 277 * 278 * @param duration 279 */ 280 public void setDuration(long duration) { 281 this.duration = duration; 282 } 283 284 public TimeUnit getTimeUnit() { 285 return timeUnit; 286 } 287 288 /** 289 * Sets the time unit duration 290 */ 291 public void setTimeUnit(TimeUnit timeUnit) { 292 this.timeUnit = timeUnit; 293 } 294 295 public String getDotOutputDir() { 296 return dotOutputDir; 297 } 298 299 /** 300 * Sets the output directory of the generated DOT Files to show the visual 301 * representation of the routes. A null value disables the dot file 302 * generation 303 */ 304 public void setDotOutputDir(String dotOutputDir) { 305 this.dotOutputDir = dotOutputDir; 306 } 307 308 public List<RouteBuilder> getRouteBuilders() { 309 return routeBuilders; 310 } 311 312 public void setRouteBuilders(List<RouteBuilder> routeBuilders) { 313 this.routeBuilders = routeBuilders; 314 } 315 316 public void setAggregateDot(boolean aggregateDot) { 317 this.aggregateDot = aggregateDot; 318 } 319 320 public boolean isAggregateDot() { 321 return aggregateDot; 322 } 323 324 // Implementation methods 325 // ------------------------------------------------------------------------- 326 protected void doStart() throws Exception { 327 LOG.info("Apache Camel " + getVersion() + " starting"); 328 if (applicationContext == null) { 329 applicationContext = createDefaultApplicationContext(); 330 } 331 applicationContext.start(); 332 333 postProcessContext(); 334 } 335 336 protected AbstractApplicationContext createDefaultApplicationContext() { 337 String[] args = getApplicationContextUri().split(";"); 338 return new ClassPathXmlApplicationContext(args); 339 } 340 341 protected void doStop() throws Exception { 342 LOG.info("Apache Camel terminating"); 343 344 if (applicationContext != null) { 345 applicationContext.close(); 346 } 347 } 348 349 protected void waitUntilCompleted() { 350 while (!completed.get()) { 351 try { 352 if (duration > 0) { 353 TimeUnit unit = getTimeUnit(); 354 LOG.info("Waiting for: " + duration + " " + unit); 355 latch.await(duration, unit); 356 completed.set(true); 357 } else { 358 latch.await(); 359 } 360 } catch (InterruptedException e) { 361 LOG.debug("Caught: " + e); 362 } 363 } 364 } 365 366 protected void postProcessContext() throws Exception { 367 Map<String, SpringCamelContext> map = applicationContext.getBeansOfType(SpringCamelContext.class); 368 Set<Map.Entry<String, SpringCamelContext>> entries = map.entrySet(); 369 int size = entries.size(); 370 for (Map.Entry<String, SpringCamelContext> entry : entries) { 371 String name = entry.getKey(); 372 SpringCamelContext camelContext = entry.getValue(); 373 camelContexts.add(camelContext); 374 generateDot(name, camelContext, size); 375 postProcesCamelContext(camelContext); 376 } 377 378 if (isAggregateDot()) { 379 380 generateDot("aggregate", aggregateSpringCamelContext(applicationContext), 1); 381 } 382 } 383 384 protected void generateDot(String name, SpringCamelContext camelContext, int size) throws IOException { 385 String outputDir = dotOutputDir; 386 if (ObjectHelper.isNotNullAndNonEmpty(outputDir)) { 387 if (size > 1) { 388 outputDir += "/" + name; 389 } 390 RouteDotGenerator generator = new RouteDotGenerator(outputDir); 391 LOG.info("Generating DOT file for routes: " + outputDir + " for: " + camelContext + " with name: " + name); 392 generator.drawRoutes(camelContext); 393 } 394 } 395 396 /** 397 * Used for aggregate dot generation 398 * 399 * @param applicationContext 400 * @return 401 * @throws Exception 402 */ 403 private static SpringCamelContext aggregateSpringCamelContext(ApplicationContext applicationContext) throws Exception { 404 SpringCamelContext aggregateCamelContext = new SpringCamelContext() { 405 /** 406 * Don't actually start this, it is merely fabricated for dot generation. 407 * @see org.apache.camel.impl.DefaultCamelContext#shouldStartRoutes() 408 */ 409 protected boolean shouldStartRoutes() { 410 411 return false; 412 } 413 }; 414 415 // look up all configured camel contexts 416 String[] names = applicationContext.getBeanNamesForType(SpringCamelContext.class); 417 for (String name : names) { 418 419 SpringCamelContext next = (SpringCamelContext) applicationContext.getBean(name, SpringCamelContext.class); 420 // aggregateCamelContext.addRoutes( next.getRoutes() ); 421 aggregateCamelContext.addRouteDefinitions(next.getRouteDefinitions()); 422 } 423 // Don't actually start this, it is merely fabricated for dot generation. 424 // answer.setApplicationContext( applicationContext ); 425 // answer.afterPropertiesSet(); 426 return aggregateCamelContext; 427 } 428 429 protected void postProcesCamelContext(CamelContext camelContext) throws Exception { 430 for (RouteBuilder routeBuilder : routeBuilders) { 431 camelContext.addRoutes(routeBuilder); 432 } 433 } 434 435 protected String getVersion() { 436 Package aPackage = Package.getPackage("org.apache.camel"); 437 if (aPackage != null) { 438 String version = aPackage.getImplementationVersion(); 439 if (version == null) { 440 version = aPackage.getSpecificationVersion(); 441 if (version == null) { 442 version = ""; 443 } 444 } 445 return version; 446 } 447 return ""; 448 } 449 }