/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.spring;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.ServiceSupport;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.view.RouteDotGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* A command line tool for booting up a CamelContext using an optional Spring
* ApplicationContext
*
* @version $Revision: 643788 $
*/
public class Main extends ServiceSupport {
private static final Log LOG = LogFactory.getLog(Main.class);
private String applicationContextUri = "META-INF/spring/*.xml";
private AbstractApplicationContext applicationContext;
private List<Option> options = new ArrayList<Option>();
private CountDownLatch latch = new CountDownLatch(1);
private AtomicBoolean completed = new AtomicBoolean(false);
private long duration = -1;
private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
private String dotOutputDir;
private boolean aggregateDot;
private List<RouteBuilder> routeBuilders = new ArrayList<RouteBuilder>();
private List<SpringCamelContext> camelContexts = new ArrayList<SpringCamelContext>();
public Main() {
addOption(new Option("h", "help", "Displays the help screen") {
protected void doProcess(String arg, LinkedList<String> remainingArgs) {
showOptions();
completed();
}
});
addOption(new ParameterOption("a", "applicationContext",
"Sets the classpath based pring ApplicationContext", "applicationContext") {
protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
setApplicationContextUri(parameter);
}
});
addOption(new ParameterOption("o", "outdir",
"Sets the DOT output directory where the visual representations of the routes are generated",
"dot") {
protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
setDotOutputDir(parameter);
}
});
addOption(new ParameterOption("ad", "aggregate-dot",
"Aggregates all routes (in addition to individual route generation) into one context to create one monolithic DOT file for visual representations the entire system.",
"aggregate-dot") {
protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
setAggregateDot("true".equals(parameter));
}
});
addOption(new ParameterOption(
"d",
"duration",
"Sets the time duration that the applicaiton will run for, by default in milliseconds. You can use '10s' for 10 seconds etc",
"duration") {
protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
String value = parameter.toUpperCase();
if (value.endsWith("S")) {
value = value.substring(0, value.length() - 1);
setTimeUnit(TimeUnit.SECONDS);
}
setDuration(Integer.parseInt(value));
}
});
}
public static void main(String... args) {
new Main().run(args);
}
/**
* Parses the command line arguments then runs the program
*/
public void run(String[] args) {
parseArguments(args);
run();
}
/**
* Runs this process with the given arguments
*/
public void run() {
if (!completed.get()) {
try {
start();
waitUntilCompleted();
stop();
} catch (Exception e) {
LOG.error("Failed: " + e, e);
}
}
}
/**
* Marks this process as being completed
*/
public void completed() {
completed.set(true);
latch.countDown();
}
public void addRouteBuilder(RouteBuilder routeBuilder) {
getRouteBuilders().add(routeBuilder);
}
/**
* Displays the command line options
*/
public void showOptions() {
System.out.println("Apache Camel Runner takes the following options");
System.out.println();
for (Option option : options) {
System.out.println(" " + option.getAbbreviation() + " or " + option.getFullName() + " = "
+ option.getDescription());
}
}
/**
* Parses the commandl ine arguments
*/
public void parseArguments(String[] arguments) {
LinkedList<String> args = new LinkedList<String>(Arrays.asList(arguments));
boolean valid = true;
while (!args.isEmpty()) {
String arg = args.removeFirst();
boolean handled = false;
for (Option option : options) {
if (option.processOption(arg, args)) {
handled = true;
break;
}
}
if (!handled) {
System.out.println("Unknown option: " + arg);
System.out.println();
valid = false;
break;
}
}
if (!valid) {
showOptions();
completed();
}
}
public void addOption(Option option) {
options.add(option);
}
public abstract class Option {
private String abbreviation;
private String fullName;
private String description;
protected Option(String abbreviation, String fullName, String description) {
this.abbreviation = "-" + abbreviation;
this.fullName = "-" + fullName;
this.description = description;
}
public boolean processOption(String arg, LinkedList<String> remainingArgs) {
if (arg.equalsIgnoreCase(abbreviation) || fullName.startsWith(arg)) {
doProcess(arg, remainingArgs);
return true;
}
return false;
}
public String getAbbreviation() {
return abbreviation;
}
public String getDescription() {
return description;
}
public String getFullName() {
return fullName;
}
protected abstract void doProcess(String arg, LinkedList<String> remainingArgs);
}
public abstract class ParameterOption extends Option {
private String parameterName;
protected ParameterOption(String abbreviation, String fullName, String description,
String parameterName) {
super(abbreviation, fullName, description);
this.parameterName = parameterName;
}
protected void doProcess(String arg, LinkedList<String> remainingArgs) {
if (remainingArgs.isEmpty()) {
System.err.println("Expected fileName for ");
showOptions();
completed();
} else {
String parameter = remainingArgs.removeFirst();
doProcess(arg, parameter, remainingArgs);
}
}
protected abstract void doProcess(String arg, String parameter, LinkedList<String> remainingArgs);
}
// Properties
// -------------------------------------------------------------------------
public AbstractApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext(AbstractApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public String getApplicationContextUri() {
return applicationContextUri;
}
public void setApplicationContextUri(String applicationContextUri) {
this.applicationContextUri = applicationContextUri;
}
public List<SpringCamelContext> getCamelContexts() {
return camelContexts;
}
public long getDuration() {
return duration;
}
/**
* Sets the duration to run the application for in milliseconds until it
* should be terminated. Defaults to -1. Any value <= 0 will run forever.
*
* @param duration
*/
public void setDuration(long duration) {
this.duration = duration;
}
public TimeUnit getTimeUnit() {
return timeUnit;
}
/**
* Sets the time unit duration
*/
public void setTimeUnit(TimeUnit timeUnit) {
this.timeUnit = timeUnit;
}
public String getDotOutputDir() {
return dotOutputDir;
}
/**
* Sets the output directory of the generated DOT Files to show the visual
* representation of the routes. A null value disables the dot file
* generation
*/
public void setDotOutputDir(String dotOutputDir) {
this.dotOutputDir = dotOutputDir;
}
public List<RouteBuilder> getRouteBuilders() {
return routeBuilders;
}
public void setRouteBuilders(List<RouteBuilder> routeBuilders) {
this.routeBuilders = routeBuilders;
}
public void setAggregateDot(boolean aggregateDot) {
this.aggregateDot = aggregateDot;
}
public boolean isAggregateDot() {
return aggregateDot;
}
// Implementation methods
// -------------------------------------------------------------------------
protected void doStart() throws Exception {
LOG.info("Apache Camel " + getVersion() + " starting");
if (applicationContext == null) {
applicationContext = createDefaultApplicationContext();
}
applicationContext.start();
postProcessContext();
}
protected AbstractApplicationContext createDefaultApplicationContext() {
String[] args = getApplicationContextUri().split(";");
return new ClassPathXmlApplicationContext(args);
}
protected void doStop() throws Exception {
LOG.info("Apache Camel terminating");
if (applicationContext != null) {
applicationContext.close();
}
}
protected void waitUntilCompleted() {
while (!completed.get()) {
try {
if (duration > 0) {
TimeUnit unit = getTimeUnit();
LOG.info("Waiting for: " + duration + " " + unit);
latch.await(duration, unit);
completed.set(true);
} else {
latch.await();
}
} catch (InterruptedException e) {
LOG.debug("Caught: " + e);
}
}
}
protected void postProcessContext() throws Exception {
Map<String, SpringCamelContext> map = applicationContext.getBeansOfType(SpringCamelContext.class);
Set<Map.Entry<String, SpringCamelContext>> entries = map.entrySet();
int size = entries.size();
for (Map.Entry<String, SpringCamelContext> entry : entries) {
String name = entry.getKey();
SpringCamelContext camelContext = entry.getValue();
camelContexts.add(camelContext);
generateDot(name, camelContext, size);
postProcesCamelContext(camelContext);
}
if (isAggregateDot()) {
generateDot("aggregate", aggregateSpringCamelContext(applicationContext), 1);
}
}
protected void generateDot(String name, SpringCamelContext camelContext, int size) throws IOException {
String outputDir = dotOutputDir;
if (ObjectHelper.isNotNullAndNonEmpty(outputDir)) {
if (size > 1) {
outputDir += "/" + name;
}
RouteDotGenerator generator = new RouteDotGenerator(outputDir);
LOG.info("Generating DOT file for routes: " + outputDir + " for: " + camelContext + " with name: " + name);
generator.drawRoutes(camelContext);
}
}
/**
* Used for aggregate dot generation
*
* @param applicationContext
* @return
* @throws Exception
*/
private static SpringCamelContext aggregateSpringCamelContext(ApplicationContext applicationContext) throws Exception {
SpringCamelContext aggregateCamelContext = new SpringCamelContext() {
/**
* Don't actually start this, it is merely fabricated for dot generation.
* @see org.apache.camel.impl.DefaultCamelContext#shouldStartRoutes()
*/
protected boolean shouldStartRoutes() {
return false;
}
};
// look up all configured camel contexts
String[] names = applicationContext.getBeanNamesForType(SpringCamelContext.class);
for (String name : names) {
SpringCamelContext next = (SpringCamelContext) applicationContext.getBean(name, SpringCamelContext.class);
// aggregateCamelContext.addRoutes( next.getRoutes() );
aggregateCamelContext.addRouteDefinitions(next.getRouteDefinitions());
}
// Don't actually start this, it is merely fabricated for dot generation.
// answer.setApplicationContext( applicationContext );
// answer.afterPropertiesSet();
return aggregateCamelContext;
}
protected void postProcesCamelContext(CamelContext camelContext) throws Exception {
for (RouteBuilder routeBuilder : routeBuilders) {
camelContext.addRoutes(routeBuilder);
}
}
protected String getVersion() {
Package aPackage = Package.getPackage("org.apache.camel");
if (aPackage != null) {
String version = aPackage.getImplementationVersion();
if (version == null) {
version = aPackage.getSpecificationVersion();
if (version == null) {
version = "";
}
}
return version;
}
return "";
}
}