
metridoc.plugins.camel.CamelPlugin.groovy Maven / Gradle / Ivy
/*
* Copyright 2010 Trustees of the University of Pennsylvania Licensed under the
* Educational Community 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.osedu.org/licenses/ECL-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 metridoc.plugins.camel
import org.apache.camel.CamelContext
import org.apache.camel.spi.BrowsableEndpoint
import metridoc.utils.Assert
import org.slf4j.Logger
import org.apache.camel.spi.Registry
import org.apache.camel.impl.DefaultCamelContext
import org.apache.camel.ProducerTemplate
import metridoc.utils.ObjectUtils
import org.apache.camel.util.ServiceHelper
import org.apache.camel.model.ProcessorDefinition
import org.apache.camel.Expression
import org.apache.camel.builder.ExpressionBuilder
import org.apache.camel.impl.DefaultCamelContextNameStrategy
import org.apache.camel.Endpoint
import org.apache.camel.model.ChoiceDefinition
import org.apache.camel.language.groovy.CamelGroovyMethods
import metridoc.camel.registry.MetridocSimpleRegistry
import metridoc.camel.builder.GroovyRouteBuilder
import metridoc.camel.aggregator.InflightAggregationWrapper
import metridoc.camel.aggregator.BodyAggregator
import metridoc.camel.processor.ClosureProcessor
import metridoc.camel.impl.iterator.LineIteratorProvider
import metridoc.camel.impl.iterator.ExcelIteratorCreator
import metridoc.camel.impl.iterator.ExcelXlsxIteratorCreator
import org.apache.camel.Processor
import org.apache.camel.Exchange
import metridoc.camel.iterator.IteratorProvider
import java.util.concurrent.Future
import org.slf4j.LoggerFactory
import metridoc.dsl.JobRunner
/**
* Created by IntelliJ IDEA.
* User: tbarker
* Date: 7/3/11
* Time: 9:05 PM
*
* This class is meant to be mixed in from a {@link metridoc.dsl.JobRunner}. It is assumed a log
* property which is an instance of {@link Logger} and a property services of type {@link Binding} have been
* instantiated in the {@link metridoc.dsl.JobRunner} which mixes this in.
*
* The plugin will generate a default {@link CamelContext} and {@link Registry} unless the provided services already
* contains a {@link CamelContext} with the name {@link CamelPlugin#METRIDOC_CAMEL_CONTEXT} or a {@link Registry}
* with the name {@link CamelPlugin#METRIDOC_CAMEL_REGISTRY}
*
*/
class CamelPlugin {
static final METRIDOC_CAMEL_CONTEXT = "metridocCamelContext"
static final METRIDOC_CAMEL_REGISTRY = "metridocCamelRegistry"
static final METRIDOC_URIS_TO_CHECK = "metridocCamelRouteUrisToCheck"
static final METRIDOC_ROUTE_EXCEPTIONS = "metridocCamelRouteExceptions"
static final METRIDOC_STOP_CAMEL = "metridocTopCamel"
static {
runClassExtensions()
}
/**
* best effort to retrieve a service of a certain type from the property services. The services property is a
* mixed in property,see class level javadoc for details
*
* @param name name of the service
* @param type type of the service to retrieve
* @return the service if it exists, otherwise null
*/
def T getCamelService(String name, Class type) {
Binding services = this.metaClass.owner.services
Assert.notNull(services, "services in the CamelPlugin must be set to create or retrieve camel components")
def bindingVariables = services.variables
if (bindingVariables.containsKey(name)) {
def possibleValue = services[name]
if (type.isInstance(possibleValue)) {
return possibleValue
}
}
//didn't find it
return null
}
Registry getRegistry() {
def services = this.metaClass.owner.services
def defaultService = {
new MetridocSimpleRegistry(services)
}
getServiceAndSetProperty("registry", METRIDOC_CAMEL_REGISTRY, Registry.class, defaultService)
}
CamelContext getCamelContext() {
def defaultService = {
def camelContext = new DefaultCamelContext(getRegistry())
camelContext.nameStrategy = new DefaultCamelContextNameStrategy("metridocCamel")
return camelContext
}
getServiceAndSetProperty("camelContext", METRIDOC_CAMEL_CONTEXT, CamelContext.class, defaultService)
}
List getCamelUrisToCheck() {
def defaultService = {
[] //empty list
}
getServiceAndSetProperty("urisToCheck", METRIDOC_URIS_TO_CHECK, List.class, defaultService)
}
List getRouteExceptions() {
def defaultService = {
[] //empty list
}
getServiceAndSetProperty("routeExceptions", METRIDOC_ROUTE_EXCEPTIONS, List.class, defaultService)
}
Endpoint getEndpoint(String uri) {
getCamelContext().getEndpoint(uri)
}
/**
* retrieves a service by name and type from services. If it is not in services the
* getDefault
function is called to retrieve the default object. If getDefault returns null
* then an {@link IllegalArgumentException} is thrown. The property is set to whatever value is found
*
* @param propertyName name of the property, it must exist or else a ${@link MissingPropertyException} is thrown
* @param serviceName the name of the service in services
* @param type type of the service
* @param getDefault closure to call to retrieve the default, must not return null
* @return the value that we are looking for
*/
def T getServiceAndSetProperty(String propertyName, String serviceName, Class type, Closure getDefault) {
def service = getCamelService(serviceName, type)
if (service != null) return service
service = getDefault()
Assert.notNull(service, "When retrieving a service, the passed default function 'getDefault' must " +
"not be null")
def services = this.metaClass.owner.services
services[serviceName] = service //set the service
return service
}
def runRoute(LinkedHashMap args = null, Closure closure) {
def routeConfig = new RouteConfig()
if (args != null) {
routeConfig = new RouteConfig(args)
}
def routeExceptions = getRouteExceptions()
def routeBuilder = new GroovyRouteBuilder(route: closure,
log: log, usePolling: routeConfig.usePolling, routeExceptions: routeExceptions)
def camelContext = getCamelContext() //use get to force initialization if not set
if (!camelContext.status.started) {
camelContext.start()
addShutdownHook {
log.info("calling shutdown hook to stop camel")
stop()
}
}
camelContext.addRoutes(routeBuilder)
def routes = routeBuilder.routeCollection.routes
def inflight = camelContext.inflightRepository
def boolean activityDetected = true
//best effort to continue until everything is finished
while (activityDetected) {
routes.each {route ->
def fromDefinitions = route.inputs
fromDefinitions.each {from ->
def uri = from.uri
def urisToCheck = getCamelUrisToCheck()
urisToCheck.add(uri)
def endpoint = camelContext.getEndpoint(uri)
def size = inflight.size(endpoint)
if (size == 0) {
activityDetected = false
}
if (endpoint instanceof BrowsableEndpoint && !activityDetected) {
size = endpoint.exchanges.size()
if (size == 0) {
activityDetected = false
}
}
}
}
if (activityDetected) {
Thread.sleep(routeConfig.completionCheckPeriod)
}
}
while (routeExceptions.size() > 1) {
log.error("routing exception occurred", routeExceptions.pop()) //we will only throw the first one
}
if (routeExceptions) { //basically if there is one left, throw it
throw routeExceptions[0]
}
}
/**
* Calls a route and gets the result
*
* @param route the route to call
* @param body the body, default to null
* @param headers the headers as a map, defaults to an empty map
* @return he result
*/
def runRoute(String route, body = ObjectUtils.NULL, Map headers = [:]) {
def camelContext = getCamelContext()
ProducerTemplate template = camelContext.createProducerTemplate()
def answer = template.requestBodyAndHeaders(route, body, headers);
ServiceHelper.stopService(template)
return answer
}
def stop() {
def runner = this as JobRunner
def variables = runner.services.variables
def stopCamel = variables[CamelPlugin.METRIDOC_STOP_CAMEL]
if (stopCamel == null) {
stopCamel = true
}
Set urisToCheck = variables[CamelPlugin.METRIDOC_URIS_TO_CHECK]
if (stopCamel && urisToCheck) {
def activityDetected = !urisToCheck.empty
while (activityDetected) {
urisToCheck.each {uri ->
def endpoint = camelContext.getEndpoint(uri)
def inflight = camelContext.inflightRepository
def size = inflight.size(endpoint)
if (size == 0) {
activityDetected = false
}
if (endpoint instanceof BrowsableEndpoint && !activityDetected) {
size = endpoint.exchanges.size()
if (size == 0) {
activityDetected = false
}
}
}
}
}
if (stopCamel) {
if (camelContext != null) {
camelContext.stop()
}
}
}
/**
* creates all the camel dsl extensions. Is initiated when the class is first referenced via a static block
*/
private static void runClassExtensions() {
ProcessorDefinition.metaClass.process = {filter ->
if (filter instanceof Closure) {
filter = new ClosureProcessor(filter)
}
delegate.process(filter);
}
ProcessorDefinition.metaClass.aggregateBody = {
def expression = ExpressionBuilder.constantExpression(true)
delegate.aggregate(expression, new InflightAggregationWrapper(new BodyAggregator())).
completionSize(500).completionTimeout(500);
}
ProcessorDefinition.metaClass.aggregateBody = {int size, long timeout = 500 ->
def expression = ExpressionBuilder.constantExpression(true)
delegate.aggregate(expression, new InflightAggregationWrapper(new BodyAggregator())).
completionSize(size).completionTimeout(timeout);
}
ProcessorDefinition.metaClass.splitByLine = {
Expression bean = ExpressionBuilder.beanExpression(LineIteratorProvider.class, "create");
delegate.split(bean).streaming()
}
ProcessorDefinition.metaClass.splitByXlsRecord = {
Expression bean = ExpressionBuilder.beanExpression(ExcelIteratorCreator.class, "create");
delegate.split(bean).streaming()
}
ProcessorDefinition.metaClass.splitByXlsxRecord = {int threads = 10, int maxPoolSize = 20, int maxQueueSize = 20 ->
Expression bean = ExpressionBuilder.beanExpression(ExcelXlsxIteratorCreator.class, "create");
delegate.split(bean).streaming().threads(threads, maxPoolSize).maxQueueSize(maxQueueSize)
}
ProcessorDefinition.metaClass.splitByLine = {int threads, int maxPoolSize = 20, int maxQueueSize = 20 ->
Expression bean = ExpressionBuilder.beanExpression(LineIteratorProvider.class, "create");
delegate.split(bean).streaming().threads(threads, maxPoolSize).maxQueueSize(maxQueueSize)
}
ProcessorDefinition.metaClass.filter = { filter ->
if (filter instanceof Closure) {
filter = CamelGroovyMethods.toExpression(filter)
}
delegate.filter(filter);
}
ChoiceDefinition.metaClass.when = { filter ->
if (filter instanceof Closure) {
filter = CamelGroovyMethods.toExpression(filter)
}
delegate.when(filter);
}
ProcessorDefinition.metaClass.batch = {LinkedHashMap args ->
process(new BatchProcessor(args))
}
}
}
class BatchProcessor implements Processor {
int threads = 10
int batchSize = 50
int lineNum
Class iteratorProvider
Closure lineProcessor
Closure batchProcessor
def futures = []
def logger = LoggerFactory.getLogger(BatchProcessor.class)
void process(Exchange exchange) {
def executor = exchange.context.executorServiceStrategy.newFixedThreadPool(this, "batchProcessor", 10)
Iterator iterator
def headers = exchange.in.headers
if (iteratorProvider) {
iterator = iteratorProvider.create(exchange)
} else {
iterator = exchange.in.getBody(Iterable.class).iterator()
}
def batch = []
Exception exception
def curriedProcessor
(1..batchSize).each {
if (iterator.hasNext()) {
lineNum++
def line = iterator.next()
switch (lineProcessor.parameterTypes.length) {
case 3:
curriedProcessor = lineProcessor.curry(line, lineNum, batch)
break;
case 4:
curriedProcessor = lineProcessor.curry(line, lineNum, batch, headers)
break;
default:
throw new IllegalArgumentException("lineProcessor must have at least three arguments")
}
futures.add(executor.submit(curriedProcessor))
}
}
futures.each {Future future ->
try {
future.get()
} catch (Exception ex) {
if (exception) {
logger.error("Another exception occurred", ex)
} else {
exception = ex
}
}
}
futures.clear()
if (exception) {
throw exception
}
switch (batchProcessor.parameterTypes.length) {
case 1:
curriedProcessor = batchProcessor.curry(batch)
break;
case 2:
curriedProcessor = batchProcessor.curry(batch, headers)
break;
default:
throw new IllegalArgumentException("lineProcessor must have at least two arguments")
}
batchProcessor(batch)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy