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: 36865 $
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 }