All Downloads are FREE. Search and download functionalities are using the official Maven repository.

metridoc.plugins.camel.CamelPlugin.groovy Maven / Gradle / Ivy

There is a newer version: 0.12
Show newest version
/*
 * 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