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