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