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