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: 56884 $
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 protected String getVersion() {
291 Package aPackage = Package.getPackage("org.apache.camel");
292 if (aPackage != null) {
293 String version = aPackage.getImplementationVersion();
294 if (version == null) {
295 version = aPackage.getSpecificationVersion();
296 if (version == null) {
297 version = "";
298 }
299 }
300 return version;
301 }
302 return "";
303 }
304
305 /**
306 * Parses the command line arguments then runs the program
307 */
308 public void run(String[] args) {
309 parseArguments(args);
310 run();
311 }
312
313 /**
314 * Displays the header message for the command line options
315 */
316 public void showOptionsHeader() {
317 System.out.println("Apache Camel Runner takes the following options");
318 System.out.println();
319 }
320
321 public List<CamelContext> getCamelContexts() {
322 return camelContexts;
323 }
324
325 public List<RouteBuilder> getRouteBuilders() {
326 return routeBuilders;
327 }
328
329 public void setRouteBuilders(List<RouteBuilder> routeBuilders) {
330 this.routeBuilders = routeBuilders;
331 }
332
333 public List<RouteType> getRouteDefinitions() {
334 List<RouteType> answer = new ArrayList<RouteType>();
335 for (CamelContext camelContext : camelContexts) {
336 answer.addAll(camelContext.getRouteDefinitions());
337 }
338 return answer;
339 }
340
341 /**
342 * Returns a {@link org.apache.camel.ProducerTemplate} from the Spring {@link org.springframework.context.ApplicationContext} instances
343 * or lazily creates a new one dynamically
344 */
345 public ProducerTemplate getCamelTemplate() {
346 if (camelTemplate == null) {
347 camelTemplate = findOrCreateCamelTemplate();
348 }
349 return camelTemplate;
350 }
351
352 protected abstract ProducerTemplate findOrCreateCamelTemplate();
353
354 protected abstract Map<String, CamelContext> getCamelContextMap();
355
356 protected void postProcessContext() throws Exception {
357 Map<String, CamelContext> map = getCamelContextMap();
358 Set<Map.Entry<String, CamelContext>> entries = map.entrySet();
359 int size = entries.size();
360 for (Map.Entry<String, CamelContext> entry : entries) {
361 String name = entry.getKey();
362 CamelContext camelContext = entry.getValue();
363 camelContexts.add(camelContext);
364 generateDot(name, camelContext, size);
365 postProcesCamelContext(camelContext);
366 }
367
368 if (isAggregateDot()) {
369 generateDot("aggregate", aggregateCamelContext(), 1);
370 }
371
372 if (!"".equals(getRoutesOutputFile())) {
373 outputRoutesToFile();
374 }
375 }
376
377 protected void outputRoutesToFile() throws IOException, JAXBException {
378 if (ObjectHelper.isNotNullAndNonEmpty(getRoutesOutputFile())) {
379 LOG.info("Generating routes as XML in the file named: " + getRoutesOutputFile());
380 ModelFileGenerator generator = createModelFileGenerator();
381 generator.marshalRoutesUsingJaxb(getRoutesOutputFile(), getRouteDefinitions());
382 }
383 }
384
385 protected abstract ModelFileGenerator createModelFileGenerator() throws JAXBException;
386
387 protected void generateDot(String name, CamelContext camelContext, int size) throws IOException {
388 String outputDir = dotOutputDir;
389 if (ObjectHelper.isNotNullAndNonEmpty(outputDir)) {
390 if (size > 1) {
391 outputDir += "/" + name;
392 }
393 RouteDotGenerator generator = new RouteDotGenerator(outputDir);
394 LOG.info("Generating DOT file for routes: " + outputDir + " for: " + camelContext + " with name: " + name);
395 generator.drawRoutes(camelContext);
396 }
397 }
398
399 /**
400 * Used for aggregate dot generation, generate a single camel context containing all of the available contexts
401 */
402 private CamelContext aggregateCamelContext() throws Exception {
403 if (camelContexts.size() == 1) {
404 return camelContexts.get(0);
405 } else {
406 DefaultCamelContext answer = new DefaultCamelContext();
407 for (CamelContext camelContext : camelContexts) {
408 answer.addRouteDefinitions(camelContext.getRouteDefinitions());
409 }
410 return answer;
411 }
412 }
413
414 protected void postProcesCamelContext(CamelContext camelContext) throws Exception {
415 for (RouteBuilder routeBuilder : routeBuilders) {
416 camelContext.addRoutes(routeBuilder);
417 }
418 }
419
420 public void addRouteBuilder(RouteBuilder routeBuilder) {
421 getRouteBuilders().add(routeBuilder);
422 }
423
424 public abstract class Option {
425 private String abbreviation;
426 private String fullName;
427 private String description;
428
429 protected Option(String abbreviation, String fullName, String description) {
430 this.abbreviation = "-" + abbreviation;
431 this.fullName = "-" + fullName;
432 this.description = description;
433 }
434
435 public boolean processOption(String arg, LinkedList<String> remainingArgs) {
436 if (arg.equalsIgnoreCase(abbreviation) || fullName.startsWith(arg)) {
437 doProcess(arg, remainingArgs);
438 return true;
439 }
440 return false;
441 }
442
443 public String getAbbreviation() {
444 return abbreviation;
445 }
446
447 public String getDescription() {
448 return description;
449 }
450
451 public String getFullName() {
452 return fullName;
453 }
454
455 public String getInformation() {
456 return " " + getAbbreviation() + " or " + getFullName() + " = " + getDescription();
457 }
458
459 protected abstract void doProcess(String arg, LinkedList<String> remainingArgs);
460 }
461
462 public abstract class ParameterOption extends Option {
463 private String parameterName;
464
465 protected ParameterOption(String abbreviation, String fullName, String description,
466 String parameterName) {
467 super(abbreviation, fullName, description);
468 this.parameterName = parameterName;
469 }
470
471 protected void doProcess(String arg, LinkedList<String> remainingArgs) {
472 if (remainingArgs.isEmpty()) {
473 System.err.println("Expected fileName for ");
474 showOptions();
475 completed();
476 } else {
477 String parameter = remainingArgs.removeFirst();
478 doProcess(arg, parameter, remainingArgs);
479 }
480 }
481
482 public String getInformation() {
483 return " " + getAbbreviation() + " or " + getFullName()
484 + " <" + parameterName + "> = " + getDescription();
485 }
486
487 protected abstract void doProcess(String arg, String parameter, LinkedList<String> remainingArgs);
488 }
489 }