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.ProducerTemplate;
032 import org.apache.camel.builder.RouteBuilder;
033 import org.apache.camel.impl.ServiceSupport;
034 import org.apache.camel.model.RouteType;
035 import org.apache.camel.processor.interceptor.Debugger;
036 import org.apache.camel.util.ObjectHelper;
037 import org.apache.camel.view.RouteDotGenerator;
038 import org.apache.commons.logging.Log;
039 import org.apache.commons.logging.LogFactory;
040 import org.springframework.context.ApplicationContext;
041 import org.springframework.context.support.AbstractApplicationContext;
042 import org.springframework.context.support.ClassPathXmlApplicationContext;
043 import org.springframework.context.support.FileSystemXmlApplicationContext;
044
045 /**
046 * A command line tool for booting up a CamelContext using an optional Spring
047 * ApplicationContext
048 *
049 * @version $Revision: 674203 $
050 */
051 public class Main extends ServiceSupport {
052 private static final Log LOG = LogFactory.getLog(Main.class);
053 private String applicationContextUri = "META-INF/spring/*.xml";
054 private String fileApplicationContextUri;
055 private AbstractApplicationContext applicationContext;
056 private List<Option> options = new ArrayList<Option>();
057 private CountDownLatch latch = new CountDownLatch(1);
058 private AtomicBoolean completed = new AtomicBoolean(false);
059 private long duration = -1;
060 private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
061 private String dotOutputDir;
062 private boolean aggregateDot;
063 private boolean debug;
064 private boolean trace;
065 private List<RouteBuilder> routeBuilders = new ArrayList<RouteBuilder>();
066 private List<SpringCamelContext> camelContexts = new ArrayList<SpringCamelContext>();
067 private AbstractApplicationContext parentApplicationContext;
068 private String parentApplicationContextUri;
069 private ProducerTemplate camelTemplate;
070
071 public Main() {
072 addOption(new Option("h", "help", "Displays the help screen") {
073 protected void doProcess(String arg, LinkedList<String> remainingArgs) {
074 showOptions();
075 completed();
076 }
077 });
078
079 addOption(new ParameterOption("a", "applicationContext",
080 "Sets the classpath based spring ApplicationContext", "applicationContext") {
081 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
082 setApplicationContextUri(parameter);
083 }
084 });
085
086 addOption(new ParameterOption("fa", "fileApplicationContext",
087 "Sets the filesystem based spring ApplicationContext", "fileApplicationContext") {
088 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
089 setFileApplicationContextUri(parameter);
090 }
091 });
092
093 addOption(new ParameterOption("o", "outdir",
094 "Sets the DOT output directory where the visual representations of the routes are generated",
095 "dot") {
096 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
097 setDotOutputDir(parameter);
098 }
099 });
100 addOption(new ParameterOption("ad", "aggregate-dot",
101 "Aggregates all routes (in addition to individual route generation) into one context to create one monolithic DOT file for visual representations the entire system.",
102 "aggregate-dot") {
103 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
104 setAggregateDot("true".equals(parameter));
105 }
106 });
107 addOption(new ParameterOption("d", "duration",
108 "Sets the time duration that the applicaiton will run for, by default in milliseconds. You can use '10s' for 10 seconds etc",
109 "duration") {
110 protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
111 String value = parameter.toUpperCase();
112 if (value.endsWith("S")) {
113 value = value.substring(0, value.length() - 1);
114 setTimeUnit(TimeUnit.SECONDS);
115 }
116 setDuration(Integer.parseInt(value));
117 }
118 });
119
120 addOption(new Option("x", "debug", "Enables the debugger") {
121 protected void doProcess(String arg, LinkedList<String> remainingArgs) {
122 enableDebug();
123 }
124 });
125 addOption(new Option("t", "trace", "Enables tracing") {
126 protected void doProcess(String arg, LinkedList<String> remainingArgs) {
127 enableTrace();
128 }
129 });
130 }
131
132 public static void main(String... args) {
133 new Main().run(args);
134 }
135
136 /**
137 * Parses the command line arguments then runs the program
138 */
139 public void run(String[] args) {
140 parseArguments(args);
141 run();
142 }
143
144 /**
145 * Runs this process with the given arguments
146 */
147 public void run() {
148 if (!completed.get()) {
149 try {
150 start();
151 waitUntilCompleted();
152 stop();
153 } catch (Exception e) {
154 LOG.error("Failed: " + e, e);
155 }
156 }
157 }
158
159 /**
160 * Marks this process as being completed
161 */
162 public void completed() {
163 completed.set(true);
164 latch.countDown();
165 }
166
167 public void addRouteBuilder(RouteBuilder routeBuilder) {
168 getRouteBuilders().add(routeBuilder);
169 }
170
171 /**
172 * Displays the command line options
173 */
174 public void showOptions() {
175 System.out.println("Apache Camel Runner takes the following options");
176 System.out.println();
177
178 for (Option option : options) {
179 System.out.println(" " + option.getAbbreviation() + " or " + option.getFullName() + " = "
180 + option.getDescription());
181 }
182 }
183
184 /**
185 * Parses the commandl ine arguments
186 */
187 public void parseArguments(String[] arguments) {
188 LinkedList<String> args = new LinkedList<String>(Arrays.asList(arguments));
189
190 boolean valid = true;
191 while (!args.isEmpty()) {
192 String arg = args.removeFirst();
193
194 boolean handled = false;
195 for (Option option : options) {
196 if (option.processOption(arg, args)) {
197 handled = true;
198 break;
199 }
200 }
201 if (!handled) {
202 System.out.println("Unknown option: " + arg);
203 System.out.println();
204 valid = false;
205 break;
206 }
207 }
208 if (!valid) {
209 showOptions();
210 completed();
211 }
212 }
213
214 public void addOption(Option option) {
215 options.add(option);
216 }
217
218 public abstract class Option {
219 private String abbreviation;
220 private String fullName;
221 private String description;
222
223 protected Option(String abbreviation, String fullName, String description) {
224 this.abbreviation = "-" + abbreviation;
225 this.fullName = "-" + fullName;
226 this.description = description;
227 }
228
229 public boolean processOption(String arg, LinkedList<String> remainingArgs) {
230 if (arg.equalsIgnoreCase(abbreviation) || fullName.startsWith(arg)) {
231 doProcess(arg, remainingArgs);
232 return true;
233 }
234 return false;
235 }
236
237 public String getAbbreviation() {
238 return abbreviation;
239 }
240
241 public String getDescription() {
242 return description;
243 }
244
245 public String getFullName() {
246 return fullName;
247 }
248
249 protected abstract void doProcess(String arg, LinkedList<String> remainingArgs);
250 }
251
252 public abstract class ParameterOption extends Option {
253 private String parameterName;
254
255 protected ParameterOption(String abbreviation, String fullName, String description,
256 String parameterName) {
257 super(abbreviation, fullName, description);
258 this.parameterName = parameterName;
259 }
260
261 protected void doProcess(String arg, LinkedList<String> remainingArgs) {
262 if (remainingArgs.isEmpty()) {
263 System.err.println("Expected fileName for ");
264 showOptions();
265 completed();
266 } else {
267 String parameter = remainingArgs.removeFirst();
268 doProcess(arg, parameter, remainingArgs);
269 }
270 }
271
272 protected abstract void doProcess(String arg, String parameter, LinkedList<String> remainingArgs);
273 }
274
275 // Properties
276 // -------------------------------------------------------------------------
277 public AbstractApplicationContext getApplicationContext() {
278 return applicationContext;
279 }
280
281 public void setApplicationContext(AbstractApplicationContext applicationContext) {
282 this.applicationContext = applicationContext;
283 }
284
285 public String getApplicationContextUri() {
286 return applicationContextUri;
287 }
288
289 public void setApplicationContextUri(String applicationContextUri) {
290 this.applicationContextUri = applicationContextUri;
291 }
292
293 public String getFileApplicationContextUri() {
294 return fileApplicationContextUri;
295 }
296
297 public void setFileApplicationContextUri(String fileApplicationContextUri) {
298 this.fileApplicationContextUri = fileApplicationContextUri;
299 }
300
301 public AbstractApplicationContext getParentApplicationContext() {
302 if (parentApplicationContext == null) {
303 if (parentApplicationContextUri != null) {
304 parentApplicationContext = new ClassPathXmlApplicationContext(parentApplicationContextUri);
305 parentApplicationContext.start();
306 }
307 }
308 return parentApplicationContext;
309 }
310
311 public void setParentApplicationContext(AbstractApplicationContext parentApplicationContext) {
312 this.parentApplicationContext = parentApplicationContext;
313 }
314
315 public String getParentApplicationContextUri() {
316 return parentApplicationContextUri;
317 }
318
319 public void setParentApplicationContextUri(String parentApplicationContextUri) {
320 this.parentApplicationContextUri = parentApplicationContextUri;
321 }
322
323 public List<SpringCamelContext> getCamelContexts() {
324 return camelContexts;
325 }
326
327 public long getDuration() {
328 return duration;
329 }
330
331 /**
332 * Sets the duration to run the application for in milliseconds until it
333 * should be terminated. Defaults to -1. Any value <= 0 will run forever.
334 *
335 * @param duration
336 */
337 public void setDuration(long duration) {
338 this.duration = duration;
339 }
340
341 public TimeUnit getTimeUnit() {
342 return timeUnit;
343 }
344
345 /**
346 * Sets the time unit duration
347 */
348 public void setTimeUnit(TimeUnit timeUnit) {
349 this.timeUnit = timeUnit;
350 }
351
352 public String getDotOutputDir() {
353 return dotOutputDir;
354 }
355
356 /**
357 * Sets the output directory of the generated DOT Files to show the visual
358 * representation of the routes. A null value disables the dot file
359 * generation
360 */
361 public void setDotOutputDir(String dotOutputDir) {
362 this.dotOutputDir = dotOutputDir;
363 }
364
365 public List<RouteBuilder> getRouteBuilders() {
366 return routeBuilders;
367 }
368
369 public void setRouteBuilders(List<RouteBuilder> routeBuilders) {
370 this.routeBuilders = routeBuilders;
371 }
372
373 public void setAggregateDot(boolean aggregateDot) {
374 this.aggregateDot = aggregateDot;
375 }
376
377 public boolean isAggregateDot() {
378 return aggregateDot;
379 }
380
381 public boolean isDebug() {
382 return debug;
383 }
384
385 public void enableDebug() {
386 this.debug = true;
387 setParentApplicationContextUri("/META-INF/services/org/apache/camel/spring/debug.xml");
388 }
389
390 public boolean isTrace() {
391 return trace;
392 }
393
394 public void enableTrace() {
395 this.trace = true;
396 setParentApplicationContextUri("/META-INF/services/org/apache/camel/spring/trace.xml");
397 }
398
399 /**
400 * Returns the currently active debugger if one is enabled
401 *
402 * @return the current debugger or null if none is active
403 * @see #enableDebug()
404 */
405 public Debugger getDebugger() {
406 for (SpringCamelContext camelContext : camelContexts) {
407 Debugger debugger = Debugger.getDebugger(camelContext);
408 if (debugger != null) {
409 return debugger;
410 }
411 }
412 return null;
413 }
414
415 public List<RouteType> getRouteDefinitions() {
416 List<RouteType> answer = new ArrayList<RouteType>();
417 for (SpringCamelContext camelContext : camelContexts) {
418 answer.addAll(camelContext.getRouteDefinitions());
419 }
420 return answer;
421 }
422
423 /**
424 * Returns a {@link ProducerTemplate} from the Spring {@link ApplicationContext} instances
425 * or lazily creates a new one dynamically
426 *
427 * @return
428 */
429 public ProducerTemplate getCamelTemplate() {
430 if (camelTemplate == null) {
431 camelTemplate = findOrCreateCamelTemplate();
432 }
433 return camelTemplate;
434 }
435
436 // Implementation methods
437 // -------------------------------------------------------------------------
438 protected ProducerTemplate findOrCreateCamelTemplate() {
439 String[] names = getApplicationContext().getBeanNamesForType(ProducerTemplate.class);
440 if (names != null && names.length > 0) {
441 return (ProducerTemplate) getApplicationContext().getBean(names[0], ProducerTemplate.class);
442 }
443 for (SpringCamelContext camelContext : camelContexts) {
444 return camelContext.createProducerTemplate();
445 }
446 throw new IllegalArgumentException("No CamelContexts are available so cannot create a ProducerTemplate!");
447 }
448
449 protected void doStart() throws Exception {
450 LOG.info("Apache Camel " + getVersion() + " starting");
451 if (applicationContext == null) {
452 applicationContext = createDefaultApplicationContext();
453 }
454 applicationContext.start();
455
456 postProcessContext();
457 }
458
459 protected AbstractApplicationContext createDefaultApplicationContext() {
460 // file based
461 if (getFileApplicationContextUri() != null) {
462 String[] args = getFileApplicationContextUri().split(";");
463
464 ApplicationContext parentContext = getParentApplicationContext();
465 if (parentContext != null) {
466 return new FileSystemXmlApplicationContext(args, parentContext);
467 } else {
468 return new FileSystemXmlApplicationContext(args);
469 }
470 }
471
472 // default to classpath based
473 String[] args = getApplicationContextUri().split(";");
474 ApplicationContext parentContext = getParentApplicationContext();
475 if (parentContext != null) {
476 return new ClassPathXmlApplicationContext(args, parentContext);
477 } else {
478 return new ClassPathXmlApplicationContext(args);
479 }
480 }
481
482 protected void doStop() throws Exception {
483 LOG.info("Apache Camel terminating");
484
485 if (applicationContext != null) {
486 applicationContext.close();
487 }
488 }
489
490 protected void waitUntilCompleted() {
491 while (!completed.get()) {
492 try {
493 if (duration > 0) {
494 TimeUnit unit = getTimeUnit();
495 LOG.info("Waiting for: " + duration + " " + unit);
496 latch.await(duration, unit);
497 completed.set(true);
498 } else {
499 latch.await();
500 }
501 } catch (InterruptedException e) {
502 LOG.debug("Caught: " + e);
503 }
504 }
505 }
506
507 protected void postProcessContext() throws Exception {
508 Map<String, SpringCamelContext> map = applicationContext.getBeansOfType(SpringCamelContext.class);
509 Set<Map.Entry<String, SpringCamelContext>> entries = map.entrySet();
510 int size = entries.size();
511 for (Map.Entry<String, SpringCamelContext> entry : entries) {
512 String name = entry.getKey();
513 SpringCamelContext camelContext = entry.getValue();
514 camelContexts.add(camelContext);
515 generateDot(name, camelContext, size);
516 postProcesCamelContext(camelContext);
517 }
518
519 if (isAggregateDot()) {
520 generateDot("aggregate", aggregateSpringCamelContext(applicationContext), 1);
521 }
522 }
523
524 protected void generateDot(String name, SpringCamelContext camelContext, int size) throws IOException {
525 String outputDir = dotOutputDir;
526 if (ObjectHelper.isNotNullAndNonEmpty(outputDir)) {
527 if (size > 1) {
528 outputDir += "/" + name;
529 }
530 RouteDotGenerator generator = new RouteDotGenerator(outputDir);
531 LOG.info("Generating DOT file for routes: " + outputDir + " for: " + camelContext + " with name: " + name);
532 generator.drawRoutes(camelContext);
533 }
534 }
535
536 /**
537 * Used for aggregate dot generation
538 *
539 * @param applicationContext
540 * @return
541 * @throws Exception
542 */
543 private static SpringCamelContext aggregateSpringCamelContext(ApplicationContext applicationContext) throws Exception {
544 SpringCamelContext aggregateCamelContext = new SpringCamelContext() {
545 /**
546 * Don't actually start this, it is merely fabricated for dot generation.
547 * @see org.apache.camel.impl.DefaultCamelContext#shouldStartRoutes()
548 */
549 protected boolean shouldStartRoutes() {
550
551 return false;
552 }
553 };
554
555 // look up all configured camel contexts
556 String[] names = applicationContext.getBeanNamesForType(SpringCamelContext.class);
557 for (String name : names) {
558
559 SpringCamelContext next = (SpringCamelContext) applicationContext.getBean(name, SpringCamelContext.class);
560 // aggregateCamelContext.addRoutes( next.getRoutes() );
561 aggregateCamelContext.addRouteDefinitions(next.getRouteDefinitions());
562 }
563 // Don't actually start this, it is merely fabricated for dot generation.
564 // answer.setApplicationContext( applicationContext );
565 // answer.afterPropertiesSet();
566 return aggregateCamelContext;
567 }
568
569 protected void postProcesCamelContext(CamelContext camelContext) throws Exception {
570 for (RouteBuilder routeBuilder : routeBuilders) {
571 camelContext.addRoutes(routeBuilder);
572 }
573 }
574
575 protected String getVersion() {
576 Package aPackage = Package.getPackage("org.apache.camel");
577 if (aPackage != null) {
578 String version = aPackage.getImplementationVersion();
579 if (version == null) {
580 version = aPackage.getSpecificationVersion();
581 if (version == null) {
582 version = "";
583 }
584 }
585 return version;
586 }
587 return "";
588 }
589 }