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