blob: c50021a9cd05f9ed2d4a1d3bac519ac8a8388861 [file] [log] [blame]
/**
* 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.impl;
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 javax.xml.bind.JAXBException;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelException;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.view.ModelFileGenerator;
import org.apache.camel.view.RouteDotGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @version
*/
public abstract class MainSupport extends ServiceSupport {
protected static final Logger LOG = LoggerFactory.getLogger(MainSupport.class);
protected String dotOutputDir;
protected final List<Option> options = new ArrayList<Option>();
protected final CountDownLatch latch = new CountDownLatch(1);
protected final AtomicBoolean completed = new AtomicBoolean(false);
protected long duration = -1;
protected TimeUnit timeUnit = TimeUnit.MILLISECONDS;
protected String routesOutputFile;
protected boolean aggregateDot;
protected boolean trace;
protected List<RouteBuilder> routeBuilders = new ArrayList<RouteBuilder>();
protected final List<CamelContext> camelContexts = new ArrayList<CamelContext>();
protected ProducerTemplate camelTemplate;
/**
* A class for intercepting the hang up signal and do a graceful shutdown of the Camel.
*/
private final class HangupInterceptor extends Thread {
Logger log = LoggerFactory.getLogger(this.getClass());
MainSupport mainInstance;
public HangupInterceptor(MainSupport main) {
mainInstance = main;
}
@Override
public void run() {
log.info("Received hang up - stopping the main instance.");
try {
mainInstance.stop();
} catch (Exception ex) {
log.warn("Error during stopping the main instance.", ex);
}
}
}
protected MainSupport() {
addOption(new Option("h", "help", "Displays the help screen") {
protected void doProcess(String arg, LinkedList<String> remainingArgs) {
showOptions();
completed();
}
});
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 application 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));
}
});
addOption(new Option("t", "trace", "Enables tracing") {
protected void doProcess(String arg, LinkedList<String> remainingArgs) {
enableTrace();
}
});
addOption(new ParameterOption("out", "output", "Output all routes to the specified XML file", "filename") {
protected void doProcess(String arg, String parameter,
LinkedList<String> remainingArgs) {
setRoutesOutputFile(parameter);
}
});
}
/**
* Runs this process with the given arguments, and will wait until completed, or the JVM terminates.
*/
public void run() throws Exception {
if (!completed.get()) {
// if we have an issue starting the propagate exception to caller
start();
try {
afterStart();
waitUntilCompleted();
beforeStop();
stop();
} catch (Exception e) {
// while running then just log errors
LOG.error("Failed: " + e, e);
}
}
}
/**
* Enables the hangup support. Gracefully stops by calling stop() on a
* Hangup signal.
*/
public void enableHangupSupport() {
HangupInterceptor interceptor = new HangupInterceptor(this);
Runtime.getRuntime().addShutdownHook(interceptor);
}
/**
* Callback to run custom logic after CamelContext has been started
*/
protected void afterStart() {
// noop
}
/**
* Callback to run custom logic before CamelContext is being stopped
*/
protected void beforeStop() {
// noop
}
/**
* Marks this process as being completed
*/
public void completed() {
completed.set(true);
latch.countDown();
}
/**
* Displays the command line options
*/
public void showOptions() {
showOptionsHeader();
for (Option option : options) {
System.out.println(option.getInformation());
}
}
/**
* Parses the command line 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 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.
*/
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 void setAggregateDot(boolean aggregateDot) {
this.aggregateDot = aggregateDot;
}
public boolean isAggregateDot() {
return aggregateDot;
}
public boolean isTrace() {
return trace;
}
public void enableTrace() {
this.trace = true;
for (CamelContext context : camelContexts) {
context.setTracing(true);
}
}
public void setRoutesOutputFile(String routesOutputFile) {
this.routesOutputFile = routesOutputFile;
}
public String getRoutesOutputFile() {
return routesOutputFile;
}
protected void doStop() throws Exception {
LOG.info("Apache Camel " + getVersion() + " stopping");
// call completed to properly stop as we count down the waiting latch
completed();
}
protected void doStart() throws Exception {
LOG.info("Apache Camel " + getVersion() + " starting");
}
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) {
Thread.currentThread().interrupt();
}
}
}
/**
* Parses the command line arguments then runs the program
*/
public void run(String[] args) throws Exception {
parseArguments(args);
run();
}
/**
* Displays the header message for the command line options
*/
public void showOptionsHeader() {
System.out.println("Apache Camel Runner takes the following options");
System.out.println();
}
public List<CamelContext> getCamelContexts() {
return camelContexts;
}
public List<RouteBuilder> getRouteBuilders() {
return routeBuilders;
}
public void setRouteBuilders(List<RouteBuilder> routeBuilders) {
this.routeBuilders = routeBuilders;
}
public List<RouteDefinition> getRouteDefinitions() {
List<RouteDefinition> answer = new ArrayList<RouteDefinition>();
for (CamelContext camelContext : camelContexts) {
answer.addAll(camelContext.getRouteDefinitions());
}
return answer;
}
/**
* Returns a {@link org.apache.camel.ProducerTemplate} from the Spring {@link org.springframework.context.ApplicationContext} instances
* or lazily creates a new one dynamically
*/
public ProducerTemplate getCamelTemplate() throws Exception {
if (camelTemplate == null) {
camelTemplate = findOrCreateCamelTemplate();
}
return camelTemplate;
}
protected abstract ProducerTemplate findOrCreateCamelTemplate();
protected abstract Map<String, CamelContext> getCamelContextMap();
protected void postProcessContext() throws Exception {
Map<String, CamelContext> map = getCamelContextMap();
if (map.size() == 0) {
throw new CamelException("Cannot find any Camel Context from the Application Context. Please check your Application Context setting");
}
Set<Map.Entry<String, CamelContext>> entries = map.entrySet();
int size = entries.size();
for (Map.Entry<String, CamelContext> entry : entries) {
String name = entry.getKey();
CamelContext camelContext = entry.getValue();
camelContexts.add(camelContext);
generateDot(name, camelContext, size);
postProcessCamelContext(camelContext);
}
if (isAggregateDot()) {
generateDot("aggregate", aggregateCamelContext(), 1);
}
if (!"".equals(getRoutesOutputFile())) {
outputRoutesToFile();
}
}
protected void outputRoutesToFile() throws IOException, JAXBException {
if (ObjectHelper.isNotEmpty(getRoutesOutputFile())) {
LOG.info("Generating routes as XML in the file named: " + getRoutesOutputFile());
ModelFileGenerator generator = createModelFileGenerator();
generator.marshalRoutesUsingJaxb(getRoutesOutputFile(), getRouteDefinitions());
}
}
protected abstract ModelFileGenerator createModelFileGenerator() throws JAXBException;
protected void generateDot(String name, CamelContext camelContext, int size) throws IOException {
String outputDir = dotOutputDir;
if (ObjectHelper.isNotEmpty(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, generate a single camel context containing all of the available contexts
*/
private CamelContext aggregateCamelContext() throws Exception {
if (camelContexts.size() == 1) {
return camelContexts.get(0);
} else {
CamelContext answer = new DefaultCamelContext();
for (CamelContext camelContext : camelContexts) {
answer.addRouteDefinitions(camelContext.getRouteDefinitions());
}
return answer;
}
}
protected void postProcessCamelContext(CamelContext camelContext) throws Exception {
for (RouteBuilder routeBuilder : routeBuilders) {
camelContext.addRoutes(routeBuilder);
}
}
public void addRouteBuilder(RouteBuilder routeBuilder) {
getRouteBuilders().add(routeBuilder);
}
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;
}
public String getInformation() {
return " " + getAbbreviation() + " or " + getFullName() + " = " + getDescription();
}
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);
}
}
public String getInformation() {
return " " + getAbbreviation() + " or " + getFullName()
+ " <" + parameterName + "> = " + getDescription();
}
protected abstract void doProcess(String arg, String parameter, LinkedList<String> remainingArgs);
}
}