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.RouteDefinition;
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: 750806 $
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 public void setDuration(long duration) {
192 this.duration = duration;
193 }
194
195 public TimeUnit getTimeUnit() {
196 return timeUnit;
197 }
198
199 /**
200 * Sets the time unit duration
201 */
202 public void setTimeUnit(TimeUnit timeUnit) {
203 this.timeUnit = timeUnit;
204 }
205
206 public String getDotOutputDir() {
207 return dotOutputDir;
208 }
209
210 /**
211 * Sets the output directory of the generated DOT Files to show the visual
212 * representation of the routes. A null value disables the dot file
213 * generation
214 */
215 public void setDotOutputDir(String dotOutputDir) {
216 this.dotOutputDir = dotOutputDir;
217 }
218
219 public void setAggregateDot(boolean aggregateDot) {
220 this.aggregateDot = aggregateDot;
221 }
222
223 public boolean isAggregateDot() {
224 return aggregateDot;
225 }
226
227 public boolean isDebug() {
228 return debug;
229 }
230
231 public void enableDebug() {
232 this.debug = true;
233 }
234
235 public boolean isTrace() {
236 return trace;
237 }
238
239 public void enableTrace() {
240 this.trace = true;
241 }
242
243 public void setRoutesOutputFile(String routesOutputFile) {
244 this.routesOutputFile = routesOutputFile;
245 }
246
247 public String getRoutesOutputFile() {
248 return routesOutputFile;
249 }
250
251 /**
252 * Returns the currently active debugger if one is enabled
253 *
254 * @return the current debugger or null if none is active
255 * @see #enableDebug()
256 */
257 public Debugger getDebugger() {
258 for (CamelContext camelContext : camelContexts) {
259 Debugger debugger = Debugger.getDebugger(camelContext);
260 if (debugger != null) {
261 return debugger;
262 }
263 }
264 return null;
265 }
266
267 protected void doStop() throws Exception {
268 LOG.info("Apache Camel " + getVersion() + " stopping");
269 // call completed to properly stop as we count down the waiting latch
270 completed();
271 }
272
273 protected void doStart() throws Exception {
274 LOG.info("Apache Camel " + getVersion() + " starting");
275 }
276
277 protected void waitUntilCompleted() {
278 while (!completed.get()) {
279 try {
280 if (duration > 0) {
281 TimeUnit unit = getTimeUnit();
282 LOG.info("Waiting for: " + duration + " " + unit);
283 latch.await(duration, unit);
284 completed.set(true);
285 } else {
286 latch.await();
287 }
288 } catch (InterruptedException e) {
289 Thread.currentThread().interrupt();
290 }
291 }
292 }
293
294 /**
295 * Parses the command line arguments then runs the program
296 */
297 public void run(String[] args) {
298 parseArguments(args);
299 run();
300 }
301
302 /**
303 * Displays the header message for the command line options
304 */
305 public void showOptionsHeader() {
306 System.out.println("Apache Camel Runner takes the following options");
307 System.out.println();
308 }
309
310 public List<CamelContext> getCamelContexts() {
311 return camelContexts;
312 }
313
314 public List<RouteBuilder> getRouteBuilders() {
315 return routeBuilders;
316 }
317
318 public void setRouteBuilders(List<RouteBuilder> routeBuilders) {
319 this.routeBuilders = routeBuilders;
320 }
321
322 public List<RouteDefinition> getRouteDefinitions() {
323 List<RouteDefinition> answer = new ArrayList<RouteDefinition>();
324 for (CamelContext camelContext : camelContexts) {
325 answer.addAll(camelContext.getRouteDefinitions());
326 }
327 return answer;
328 }
329
330 /**
331 * Returns a {@link org.apache.camel.ProducerTemplate} from the Spring {@link org.springframework.context.ApplicationContext} instances
332 * or lazily creates a new one dynamically
333 */
334 public ProducerTemplate getCamelTemplate() {
335 if (camelTemplate == null) {
336 camelTemplate = findOrCreateCamelTemplate();
337 }
338 return camelTemplate;
339 }
340
341 protected abstract ProducerTemplate findOrCreateCamelTemplate();
342
343 protected abstract Map<String, CamelContext> getCamelContextMap();
344
345 protected void postProcessContext() throws Exception {
346 Map<String, CamelContext> map = getCamelContextMap();
347 Set<Map.Entry<String, CamelContext>> entries = map.entrySet();
348 int size = entries.size();
349 for (Map.Entry<String, CamelContext> entry : entries) {
350 String name = entry.getKey();
351 CamelContext camelContext = entry.getValue();
352 camelContexts.add(camelContext);
353 generateDot(name, camelContext, size);
354 postProcesCamelContext(camelContext);
355 }
356
357 if (isAggregateDot()) {
358 generateDot("aggregate", aggregateCamelContext(), 1);
359 }
360
361 if (!"".equals(getRoutesOutputFile())) {
362 outputRoutesToFile();
363 }
364 }
365
366 protected void outputRoutesToFile() throws IOException, JAXBException {
367 if (ObjectHelper.isNotEmpty(getRoutesOutputFile())) {
368 LOG.info("Generating routes as XML in the file named: " + getRoutesOutputFile());
369 ModelFileGenerator generator = createModelFileGenerator();
370 generator.marshalRoutesUsingJaxb(getRoutesOutputFile(), getRouteDefinitions());
371 }
372 }
373
374 protected abstract ModelFileGenerator createModelFileGenerator() throws JAXBException;
375
376 protected void generateDot(String name, CamelContext camelContext, int size) throws IOException {
377 String outputDir = dotOutputDir;
378 if (ObjectHelper.isNotEmpty(outputDir)) {
379 if (size > 1) {
380 outputDir += "/" + name;
381 }
382 RouteDotGenerator generator = new RouteDotGenerator(outputDir);
383 LOG.info("Generating DOT file for routes: " + outputDir + " for: " + camelContext + " with name: " + name);
384 generator.drawRoutes(camelContext);
385 }
386 }
387
388 /**
389 * Used for aggregate dot generation, generate a single camel context containing all of the available contexts
390 */
391 private CamelContext aggregateCamelContext() throws Exception {
392 if (camelContexts.size() == 1) {
393 return camelContexts.get(0);
394 } else {
395 DefaultCamelContext answer = new DefaultCamelContext();
396 for (CamelContext camelContext : camelContexts) {
397 answer.addRouteDefinitions(camelContext.getRouteDefinitions());
398 }
399 return answer;
400 }
401 }
402
403 protected void postProcesCamelContext(CamelContext camelContext) throws Exception {
404 for (RouteBuilder routeBuilder : routeBuilders) {
405 camelContext.addRoutes(routeBuilder);
406 }
407 }
408
409 public void addRouteBuilder(RouteBuilder routeBuilder) {
410 getRouteBuilders().add(routeBuilder);
411 }
412
413 public abstract class Option {
414 private String abbreviation;
415 private String fullName;
416 private String description;
417
418 protected Option(String abbreviation, String fullName, String description) {
419 this.abbreviation = "-" + abbreviation;
420 this.fullName = "-" + fullName;
421 this.description = description;
422 }
423
424 public boolean processOption(String arg, LinkedList<String> remainingArgs) {
425 if (arg.equalsIgnoreCase(abbreviation) || fullName.startsWith(arg)) {
426 doProcess(arg, remainingArgs);
427 return true;
428 }
429 return false;
430 }
431
432 public String getAbbreviation() {
433 return abbreviation;
434 }
435
436 public String getDescription() {
437 return description;
438 }
439
440 public String getFullName() {
441 return fullName;
442 }
443
444 public String getInformation() {
445 return " " + getAbbreviation() + " or " + getFullName() + " = " + getDescription();
446 }
447
448 protected abstract void doProcess(String arg, LinkedList<String> remainingArgs);
449 }
450
451 public abstract class ParameterOption extends Option {
452 private String parameterName;
453
454 protected ParameterOption(String abbreviation, String fullName, String description,
455 String parameterName) {
456 super(abbreviation, fullName, description);
457 this.parameterName = parameterName;
458 }
459
460 protected void doProcess(String arg, LinkedList<String> remainingArgs) {
461 if (remainingArgs.isEmpty()) {
462 System.err.println("Expected fileName for ");
463 showOptions();
464 completed();
465 } else {
466 String parameter = remainingArgs.removeFirst();
467 doProcess(arg, parameter, remainingArgs);
468 }
469 }
470
471 public String getInformation() {
472 return " " + getAbbreviation() + " or " + getFullName()
473 + " <" + parameterName + "> = " + getDescription();
474 }
475
476 protected abstract void doProcess(String arg, String parameter, LinkedList<String> remainingArgs);
477 }
478 }