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