0

I'm trying to get simple example of springboot and camel working but come undone. Not sure what i'm doing wrong. in the gradle build i've included so far

dependencies {
    compile 'org.apache.camel:camel-spring-boot-starter:2.18.4'
    compile 'org.apache.camel:camel-groovy:2.18.4'
    compile 'org.apache.camel:camel-stream:2.18.4'
    compile 'org.codehaus.groovy:groovy-all:2.4.11'
    testCompile group: 'junit', name: 'junit', version: '4.11'
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

i've create a DirectRoute component like this

@Component
class DirectRoute extends RouteBuilder{
    @Override
    void configure () throws Exception {
        from ("direct:in")  //tried stream:in also 
                .to ("stream:out")
    }

}

I then have a driver bean that try's to invoke the route

@Component
public class HelloImpl implements Hello {

    @Produce(uri = "direct:in")
    private ProducerTemplate template;

    @Override
    public String say(String value) throws ExecutionException, InterruptedException {

        assert template
        println "def endpoint is : " + template.getDefaultEndpoint()
        return template.sendBody (template.getDefaultEndpoint(), value)
     }


}

lastly in the springboot application class i added a command line runner like this, that gets my bean from the spring context, and invokes the say method. I'm using groovy so i just passed a closure to the command line runner.

@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
//return closure to run on startup - just list the beans enabled
    {args ->
        println("Let's inspect the beans provided by Spring Boot:")

        String[] beanNames = ctx.getBeanDefinitionNames()
        Arrays.sort(beanNames)
        for (String beanName : beanNames) {
            println(beanName)
        }

        println("call the direct:start route via the service")
        Hello service = ctx.getBean("helloService")

        def result = service.say("William")
        println "service returned : $result "
    }
}

when i run my application i get all the bean names printed out (that's ok), however when i invoke the direct:in via producer template i get this error (org.apache.camel.component.direct.DirectConsumerNotAvailableException) see below.

I was expecting the route to be triggered the name sent to see that arrive in the output stream - but this is what i get.

Caused by: org.apache.camel.CamelExecutionException: Exception occurred during execution on the exchange: Exchange[ID-MONSTER-PC2-58911-1496920205300-0-2]
    at org.apache.camel.util.ObjectHelper.wrapCamelExecutionException(ObjectHelper.java:1795) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.util.ExchangeHelper.extractResultBody(ExchangeHelper.java:677) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.impl.DefaultProducerTemplate.extractResultBody(DefaultProducerTemplate.java:515) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.impl.DefaultProducerTemplate.extractResultBody(DefaultProducerTemplate.java:511) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.impl.DefaultProducerTemplate.sendBody(DefaultProducerTemplate.java:163) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.ProducerTemplate$sendBody$0.call(Unknown Source) ~[na:na]
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) [groovy-all-2.4.11.jar:2.4.11]
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113) [groovy-all-2.4.11.jar:2.4.11]
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133) [groovy-all-2.4.11.jar:2.4.11]
    at services.HelloImpl.say(HelloImpl.groovy:29) ~[main/:na]
    at services.Hello$say.call(Unknown Source) ~[na:na]
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) [groovy-all-2.4.11.jar:2.4.11]
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113) [groovy-all-2.4.11.jar:2.4.11]
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125) [groovy-all-2.4.11.jar:2.4.11]
    at application.Application$_commandLineRunner_closure1.doCall(Application.groovy:47) ~[main/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_121]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_121]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_121]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_121]
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) ~[groovy-all-2.4.11.jar:2.4.11]
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) ~[groovy-all-2.4.11.jar:2.4.11]
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294) ~[groovy-all-2.4.11.jar:2.4.11]
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) ~[groovy-all-2.4.11.jar:2.4.11]
    at groovy.lang.Closure.call(Closure.java:414) ~[groovy-all-2.4.11.jar:2.4.11]
    at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:54) ~[groovy-all-2.4.11.jar:2.4.11]
    at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:124) ~[groovy-all-2.4.11.jar:2.4.11]
    at com.sun.proxy.$Proxy44.run(Unknown Source) ~[na:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:776) [spring-boot-1.5.2.RELEASE.jar:1.5.2.RELEASE]
    ... 10 common frames omitted
Caused by: org.apache.camel.component.direct.DirectConsumerNotAvailableException: No consumers available on endpoint: direct://in. Exchange[ID-MONSTER-PC2-58911-1496920205300-0-2]
    at org.apache.camel.component.direct.DirectProducer.process(DirectProducer.java:55) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:197) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.processor.DelegateAsyncProcessor.process(DelegateAsyncProcessor.java:97) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.impl.ProducerCache$1.doInProducer(ProducerCache.java:529) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.impl.ProducerCache$1.doInProducer(ProducerCache.java:497) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.impl.ProducerCache.doInProducer(ProducerCache.java:365) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.impl.ProducerCache.sendExchange(ProducerCache.java:497) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.impl.ProducerCache.send(ProducerCache.java:225) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.impl.DefaultProducerTemplate.send(DefaultProducerTemplate.java:144) ~[camel-core-2.18.4.jar:2.18.4]
    at org.apache.camel.impl.DefaultProducerTemplate.sendBody(DefaultProducerTemplate.java:161) ~[camel-core-2.18.4.jar:2.18.4]

What have i done wrong - and why does the producer template invocation on 'direct:in' (also tried stream in with same problem) not work? I thought that .to("stream:out") would be a consumer.

any pointers or advice gratefully received at this point

WILLIAM WOODMAN
  • 1,185
  • 5
  • 19
  • 36
  • Where is your route that shows a message being passed to the direct:in? – ndrone Jun 08 '17 at 20:16
  • all bit weird - whilst i have SpringBootApplication annotation on it didnt pick up the Component annotated routes. I had to add the ScanBasepackages to pick them up. Further the camelContext is not started automatically so i had to add a start() and a stop() with a sleep inbetween. When i do that and call my direct:in from the template things seem to work. Will finish invest in morning and update – WILLIAM WOODMAN Jun 08 '17 at 22:21
  • Cool spring boot applications component scans the sub packages only. So if the camel configuration is in a higher package it will not scan it. – ndrone Jun 08 '17 at 22:29

1 Answers1

0

I have an update on my problems:

  1. I had a subpackage with the application class annotated with @SpringBootApplication. So yes, unadorned it only scans subpackages.
  2. you can add scanBasePackages= or scanBaseClasses= parameter, however when I tried doing a scan for single class, it seemed to scan the whole directory any way and grabbed the others as well.
  3. I refactored the app to have a single root package with subpackages and elected to set the 'scanBasePakages to the new root package. but left the Application class in its own subpackage (personal preference only - documentation suggests leaving the Application in the root package)
  4. you can now add other classes annotated with @Configuration to generate beans or use the basic @Component.
  5. if you create Camel routes annotated with @Component they will be auto configured in the camelContext for you.
  6. it appears by default that Spring isnt not starting the camelContext for you. When I checked the status of the context it shows as starting and not started. so in my commandLineRunner I had to start get the spring injected camelContext and had to start it myself, and exited it when I finished. I was slightly suprised as I thought SpringBootStarter would auto start the camelContext, but it appears not.
  7. once you have Spring component scanning etc working and you start the camelContext, then problems with the org.apache.camel.component.direct.DirectConsumerNotAvailableException exception went away and things started to work - at least the baby examples I'm trying.

So revised structure now looks like this:

enter image description here

The revised ApplicationClass now looks like this with some simple println output to see the state of the context, and beans in the spring ctx. The helleoService bean is still the proxy I use to setup the producer template to call the DirectRoute.

package com.softwood.application

import groovy.util.logging.Slf4j
import org.apache.camel.CamelContext
import org.springframework.beans.factory.annotation.Autowired
import com.softwood.services.Hello

/**
 * Created by willw on 07/06/2017.
 */
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean

@Slf4j //inject logger
@SpringBootApplication  (scanBasePackages = ["com.softwood"])  //forces scan at parent
// same as @Configuration @EnableAutoConfiguration @ComponentScan with 'defaults' e.g. sub packages
public class Application {
    @Autowired
    ApplicationContext ctx

    @Autowired
    CamelContext  camelContext

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args)
    }

    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
    //return closure to run on startup - just list the beans enabled
        {args ->

            println("Let's inspect the beans provided by Spring Boot:")

            String[] beanNames = ctx.getBeanDefinitionNames()
            Arrays.sort(beanNames)
            for (String beanName : beanNames) {
                println(beanName)
            }

            /*  when component scan is working - bean routes are added
            automatically to camel context via springBoot, however you do have to start
            the camel context, yourself
             */
             println "camelCtx has following components : " + camelContext.componentNames
            println "camelCtx state is : " + camelContext.status
            println "starting camel context"
            camelContext.start()
            println "camelCtx state now is : " + camelContext.status


            //log.debug "wills logging call "
            println("call the direct:start route via the service")
            Hello service = ctx.getBean("helloService")

            def result = service.say("William")
            println "service returned : $result "
            println "sleep 5 seconds "
            sleep (5000)
            println "stop camel context"
            camelContext.stop()
            println "camelCtx state now is : " + camelContext.status

        }
    }

}

That proxy is just registered as a simple bean like this in the spring context

package com.softwood.services
/**
 * Created by willw on 07/06/2017.
 */
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate
import org.springframework.stereotype.Component;

import java.util.concurrent.ExecutionException

@Component
public class HelloImpl implements Hello {

    @Produce(uri = "direct:in")  /* ?block=true */
    private ProducerTemplate template

    @Override
    public String say(String value) throws ExecutionException, InterruptedException {

        assert template
        println "def endpoint is : " + template.getDefaultEndpoint()
        //Future future = template.asyncSendBody(template.getDefaultEndpoint(), value)
        //return future.get()
        return template.sendBody (template.getDefaultEndpoint(), value)
     }


}

The TimedRoute just sorts itself out with no template required to invoke in

package com.softwood.camelRoutes

/**
 * Created by willw on 07/06/2017.
 */
import org.apache.camel.builder.RouteBuilder
import org.springframework.stereotype.Component


@Component
class TimedRoute extends RouteBuilder {

    @Override
    void configure () throws Exception {
        from ("timer:foo")
        .to ("log:com.softwood.application.Application?level=WARN")
    }
}

My simple no-op file route isn't working (yet) and not sure why. I suspect I've not got the file config right somehow; some playing is required.

package com.softwood.camelRoutes

import org.apache.camel.builder.RouteBuilder
import org.springframework.stereotype.Component


/**
 * Created by willw on 08/06/2017.
 */
@Component
class FileNoOpRoute extends RouteBuilder{
    @Override
    void configure () throws Exception {
        from ("file:../com.softwood.file-inbox?recursive=true&noop=true&idempotent=true")
        .to ("file:../com.softwood.file-outbox")

    }

}

However the basics are not working and least camel is doing something whereas before I just had the exception and nothing.

I have found another question on Spring config highlighting some of the above also.

halfer
  • 19,824
  • 17
  • 99
  • 186
WILLIAM WOODMAN
  • 1,185
  • 5
  • 19
  • 36
  • PS done some more experimentation - the default relative route for file:// is the top level root of you project so `file://data/inbox` actually refers to `/data/inbox`. Its not where your class path starts. so in the example above this is `D:\Intellij-projects\camel-test\data\fileInbox` on my dev pc. once you figure out where the relative path starts - it works fine – WILLIAM WOODMAN Jun 09 '17 at 21:19