Exception accessing Spring managed bean from Groovy closure - spring

I have a simple Spring Boot application with 2 bean classes, a main class and a configuration class. Whenever I try to access the Spring managed Bank bean from a Groovy closure, I get an exception:
Exception in thread "main" java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:304)
at org.springframework.boot.SpringApplication.runCommandLineRunners(SpringApplication.java:675)
at com.example.closures.ClosuresApplication$_run_closure1.doCall(ClosuresApplication.groovy:22)
at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:690)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:957)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:946)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.boot.SpringApplication$run.call(Unknown Source)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:324)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:110)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:130)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:292)
at com.example.closures.ClosuresApplication.main(ClosuresApplication.groovy:27)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016)
Caused by: groovy.lang.MissingPropertyException: No such property: bank for class: com.example.closures.ClosuresApplication$$EnhancerBySpringCGLIB$$44735576
at groovy.lang.Closure.call(Closure.java:423)
at groovy.lang.Closure.call(Closure.java:439)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2027)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:51)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2012)
at org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:49)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2053)
at org.codehaus.groovy.runtime.dgm$162.invoke(Unknown Source)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:304)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
at com.example.closures.ClosuresApplication$_run_closure1.doCall(ClosuresApplication.groovy:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:110)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:122)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.example.closures.ClosuresApplication.run(ClosuresApplication.groovy:21)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at org.springframework.boot.SpringApplication.runCommandLineRunners(SpringApplication.java:672)
... 9 common frames omitted
Bank.groovy
#Component
final class Bank {
final String name = 'MyBank'
final AutomatedTellerMachine insideAtm
final AutomatedTellerMachine outsideAtm
#Autowired
Bank(#Qualifier('insideAtm') final AutomatedTellerMachine insideAtm, #Qualifier('outsideAtm') final AutomatedTellerMachine outsideAtm) {
this.insideAtm = insideAtm
this.outsideAtm = outsideAtm
}
String getName() {
return name
}
AutomatedTellerMachine getInsideAtm() {
return insideAtm
}
AutomatedTellerMachine getOutsideAtm() {
return outsideAtm
}
}
AutomatedTellerMachine.groovy
final class AutomatedTellerMachine {
final String name
AutomatedTellerMachine(final String name) {
this.name = name
}
String getName() {
return name
}
}
AppConfig.groovy
#Configuration
class AppConfig {
#Bean(name = 'insideAtm')
AutomatedTellerMachine getInsideAtm() {
new AutomatedTellerMachine('insideAtm')
}
#Bean(name = 'outsideAtm')
AutomatedTellerMachine getOutsideAtm() {
new AutomatedTellerMachine('outsideAtm')
}
}
ClosuresApplication.groovy
#SpringBootApplication
class ClosuresApplication implements CommandLineRunner {
#Autowired
private Bank bank
#Override
void run(String... args) throws Exception {
for (def i = 0; i < 10; i++) {
printf 'Bank %02d: %s%n', (i + 1), bank
}
(1..10).each {
printf 'Bank %02d: %s%n', it, bank
}
}
static void main(String[] args) {
SpringApplication.run ClosuresApplication, args
}
}
The regular for loop works just fine, but the .each {} closure from Groovy gives an exception. Any ideas?

I have run into this problem at odd times and found that a simple work-around can fix it - add a reference to your bank variable in the run method:
def _bank = bank
(1..10).each {
printf 'Bank %02d: %s%n', it, _bank
}
It seems to be an odd scoping issue that I have never been able to determine the real cause of.

Another possible solution is converting bank field to groovy property by removing 'private' modifier.
#Autowired
Bank bank
This is slightly less visually hideous (especially if you having this issue with more than one field), but it does change access level of that field.
I've encountered a number of these issues, all seemingly related to using private fields inside various closures in spring-managed beans, specifically when the beans are 'enhanced' (proxied) by spring (EnhancerBySpringCGLIB).

Related

How to resolve ClassCastException in MultiResourceItemReader Spring Batch

I'm reading multiple files from the S3 bucket using MultiResourceItemReader, I'm getting ClassCastException before executing the myReader() method, Something wrong with MultiResourceItemReader not sure what's going wrong here.
Please find my code below:
#Bean
public MultiResourceItemReader<String> multiResourceReader()
{
String bucket = "mybucket;
String key = "/myfiles";
List<InputStream> resourceList = s3Client.getFiles(bucket, key);
List<InputStreamResource> inputStreamResourceList = new ArrayList<>();
for (InputStream s: resourceList) {
inputStreamResourceList.add(new InputStreamResource(s));
}
Resource[] resources = inputStreamResourceList.toArray(new InputStreamResource[inputStreamResourceList.size()]);
//InputStreamResource[] resources = inputStreamResourceList.toArray(new InputStreamResource[inputStreamResourceList.size()]);
// I'm getting all the stream content - I verified my stream is not null
for (int i = 0; i < resources.length; i++) {
try {
InputStream s = resources[i].getInputStream();
String result = IOUtils.toString(s, StandardCharsets.UTF_8);
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
}
MultiResourceItemReader<String> resourceItemReader = new MultiResourceItemReader<>();
resourceItemReader.setResources(resources);
resourceItemReader.setDelegate(myReader());
resourceItemReader.setDelegate((ResourceAwareItemReaderItemStream<? extends String>) new CustomComparator());
return resourceItemReader;
}
Exception:
Caused by: java.lang.ClassCastException: class CustomComparator cannot be cast to class org.springframework.batch.item.file.ResourceAwareItemReaderItemStream (CustomComparator and org.springframework.batch.item.file.ResourceAwareItemReaderItemStream are in unnamed module of loader org.springframework.boot.loader.LaunchedURLClassLoader #cc285f4)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 65 common frames omitted
Can someone please help me to resolve this issue. Appreciated your help in advance. Thanks.
The reason you see the NullPointerException is due to the default comparator used by the MultiResourceItemReader to sort the resources after loading them.
The default compare behavior calls the getFilename() method of the InputStreamResource.
Refer - https://github.com/spring-projects/spring-batch/blob/115c3022147692155d45e23cdd5cef84895bf9f5/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemReader.java#L82
But the InputStreamResource just inherits the getFileName() method from its parent AbstractResource, which just returns null.
https://github.com/spring-projects/spring-framework/blob/316e84f04f3dbec3ea5ab8563cc920fb21f49749/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java#L220
The solution is to provide a custom comparator for the MultiResourceItemReader. Here is a simple example, assuming you do not want to sort the resources in a specific way before processing:
public class CustomComparator implements Comparator<InputStream>{
#Override
public int compare(InputStream is1, InputStream is2) {
//comparing based on last modified time
return Long.compare(is1.hashCode(),is2.hashCode());
}
}
MultiResourceItemReader<String> resourceItemReader = new MultiResourceItemReader<>();
resourceItemReader.setResources(resources);
resourceItemReader.setDelegate(myReader());
//UPDATED with correction - set custom Comparator
resourceItemReader.setComparator(new CustomComparator());
Refer this answer for how a Comparator is used by Spring Batch MultiResourceItemReader.
File processing order with Spring Batch

How to backport `ObjectFactory.namedDomainObjectList(...)` to gradle (5.6.4)

Working on a gradle-plugin. And i've tried to backport the functionality from gradle 6 ObjectFactory.namedDomainObjectList​(Class<T> elementType) to gradle 5.6.4.
I'v tried this with no luck:
import org.gradle.testfixtures.ProjectBuilder
import org.gradle.api.internal.CollectionCallbackActionDecorator
import org.gradle.api.internal.DefaultDomainObjectCollection
def main() {
def project = ProjectBuilder.builder().build();
CollectionCallbackActionDecorator ccad;
def myList = project.objects.newInstance(DefaultDomainObjectCollection.class,
MyClass.class,
new ListElementSource(),
ccad);
}
static class MyClass implements Named {
#Override
String getName() {
return "foobar"
}
}
Exception Stack
org.gradle.api.reflect.ObjectInstantiationException: Could not create an instance of type org.gradle.api.internal.DefaultDomainObjectCollection.
at org.gradle.internal.instantiation.DependencyInjectingInstantiator.newInstance(DependencyInjectingInstantiator.java:54)
at org.gradle.api.internal.model.DefaultObjectFactory.newInstance(DefaultObjectFactory.java:82)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.groovy.runtime.callsite.PlainObjectMetaMethodSite.doInvoke(PlainObjectMetaMethodSite.java:43)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSite.invoke(PojoMetaMethodSite.java:190)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:55)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:151)
github: Source of gradle instantiation
Solved it this way:
def myList = new DefaultNamedDomainObjectList(
MyClass.class,
new Instantiator() {
#Override
def <T> T newInstance(Class<? extends T> aClass, Object... objects) throws ObjectInstantiationException {
project.objects.newInstance(aClass, objects)
}
},
new DynamicPropertyNamer())

kafka embedded : java.io.FileNotFoundException: /tmp/kafka-7785736914220873149/replication-offset-checkpoint.tmp

I use kafkaEmbedded in integration test and I get FileNotFoundException :
java.io.FileNotFoundException: /tmp/kafka-7785736914220873149/replication-offset-checkpoint.tmp
at java.io.FileOutputStream.open0(Native Method) ~[na:1.8.0_141]
at java.io.FileOutputStream.open(FileOutputStream.java:270) ~[na:1.8.0_141]
at java.io.FileOutputStream.<init>(FileOutputStream.java:213) ~[na:1.8.0_141]
at java.io.FileOutputStream.<init>(FileOutputStream.java:162) ~[na:1.8.0_141]
at kafka.server.checkpoints.CheckpointFile.write(CheckpointFile.scala:43) ~[kafka_2.11-0.11.0.0.jar:na]
at kafka.server.checkpoints.OffsetCheckpointFile.write(OffsetCheckpointFile.scala:58) ~[kafka_2.11-0.11.0.0.jar:na]
at kafka.server.ReplicaManager$$anonfun$checkpointHighWatermarks$2.apply(ReplicaManager.scala:1118) [kafka_2.11-0.11.0.0.jar:na]
at kafka.server.ReplicaManager$$anonfun$checkpointHighWatermarks$2.apply(ReplicaManager.scala:1115) [kafka_2.11-0.11.0.0.jar:na]
at scala.collection.TraversableLike$WithFilter$$anonfun$foreach$1.apply(TraversableLike.scala:733) [scala-library-2.11.11.jar:na]
at scala.collection.immutable.Map$Map1.foreach(Map.scala:116) [scala-library-2.11.11.jar:na]
at scala.collection.TraversableLike$WithFilter.foreach(TraversableLike.scala:732) [scala-library-2.11.11.jar:na]
at kafka.server.ReplicaManager.checkpointHighWatermarks(ReplicaManager.scala:1115) [kafka_2.11-0.11.0.0.jar:na]
at kafka.server.ReplicaManager$$anonfun$1.apply$mcV$sp(ReplicaManager.scala:211) [kafka_2.11-0.11.0.0.jar:na]
at kafka.utils.KafkaScheduler$$anonfun$1.apply$mcV$sp(KafkaScheduler.scala:110) [kafka_2.11-0.11.0.0.jar:na]
at kafka.utils.CoreUtils$$anon$1.run(CoreUtils.scala:57) [kafka_2.11-0.11.0.0.jar:na]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_141]
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [na:1.8.0_141]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_141]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_141]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_141]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_141]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_141]
My tests pass with success but I get this error in the end of my build
After many hours of research I found this :
kafka TestUtils.tempDirectory method is used to create temporary directory for embedded kafka broker. It also registers shutdown hook which deletes this directory when JVM exits.
when unit test finishes execution it calls System.exit, which in turn executes all registered shutdown hooks
If kafka broker runs at the end of unit test it will attempt to write/read data in a dir which is deleted and produces different FileNotFound exceptions.
My config class :
#Configuration
public class KafkaEmbeddedConfiguration {
private final KafkaEmbedded kafkaEmbedded;
public KafkaEmbeddedListenerConfigurationIT() throws Exception {
kafkaEmbedded = new KafkaEmbedded(1, true, "topic1");
kafkaEmbedded.before();
}
#Bean
public KafkaTemplate<String, Message> sender(ProtobufSerializer protobufSerializer,
KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry) throws Exception {
KafkaTemplate<String, Message> sender = KafkaTestUtils.newTemplate(kafkaEmbedded, new StringSerializer(),
protobufSerializer);
for (MessageListenerContainer listenerContainer :
registry.getListenerContainers()) {
ContainerTestUtils.waitForAssignment(listenerContainer,
kafkaEmbedded.getPartitionsPerTopic());
}
return sender;
}
Test class :
#RunWith(SpringRunner.class)
public class DeviceEnergyKafkaListenerIT {
...
#Autowired
private KafkaTemplate<String, Message> sender;
#Test
public void test (){
...
sender.send(topic, msg);
sender.flush();
}
Any ideas how to resolve this please ?
With a #ClassRule broker, add an #AfterClass method...
#AfterClass
public static void tearDown() {
embeddedKafka.getKafkaServers().forEach(b -> b.shutdown());
embeddedKafka.getKafkaServers().forEach(b -> b.awaitShutdown());
}
For a #Rule or bean, use an #After method.
final KafkaServer server =
embeddedKafka.getKafkaServers().stream().findFirst().orElse(null);
if(server != null) {
server.replicaManager().shutdown(false);
final Field replicaManagerField = server.getClass().getDeclaredField("replicaManager");
if(replicaManagerField != null) {
replicaManagerField.setAccessible(true);
replicaManagerField.set(server, null);
}
}
embeddedKafka.after();
For a more detail discussion you can refer this thread
Embedded kafka issue with multiple tests using the same context
The following solution provided by mhyeon-lee has worked for me:
import org.apache.kafka.common.utils.Exit
class SomeTest {
static {
Exit.setHaltProcedure((statusCode, message) -> {
if (statusCode != 1) {
Runtime.getRuntime().halt(statusCode);
}
});
}
#Test
void test1() {
}
#Test
void test2() {
}
}
When JVM shutdown Hook is running, kafka log file is deleted and
Exit.halt (1) is called when other shutdown hook accesses kafka log
file at the same time.
Since halt is called here and status is 1, i only defend against 1.
https://github.com/a0x8o/kafka/blob/master/core/src/main/scala/kafka/log/LogManager.scala#L193
If you encounter a situation where the test fails with a different
status value, you can add defense code.
An error log may occur, but the test will not fail because the command
is not propagated to Runtime.halt.
References:
https://github.com/spring-projects/spring-kafka/issues/194#issuecomment-612875646
https://github.com/spring-projects/spring-kafka/issues/194#issuecomment-613548108

Swagger codegen - change api and controller class name

I am trying to generate rest code for spring using swagger. Currently generate api is V10Api.java and controller is V10ApiController.java. I want to have custom prefix as 'ReadApi.java' and 'ReadApiController.java'.
I looked at the solution here to implement this, so my code was:
public class ReadApiSpringCodeGen extends SpringCodegen
{
static {
PREFIX="Read"; //compile error at PREFIX
}
}
it gives compilation error at PREFIX so i am guessing PREFIX is not in superclass.
I modified the class to over toApiName() method:
public class ReadApiSpringCodeGen extends SpringCodegen
{
#Override
public String toApiName(String name) {
System.out.println("Name in is ["+name+"]");
if (name.length() == 0) {
return "DefaultApi";
}
name = sanitizeName(name);
return camelize(name) + "Read";
}
public static void main(String[] args)
{
System.out.println("Main called");
}
}
When ran the code generator as:
${JAVA_HOME}/bin/java -cp .:./swagger-codegen-cli-2.2.1.jar \
-jar swagger-codegen-cli-2.2.1.jar generate \
-i Read.yaml \
-l com.foo.swag.codegen.swagger.ReadApiSpringCodeGen \
....
I get the error:
Exception in thread "main" java.lang.RuntimeException: Can't load config class with name com.foo.swag.codegen.swagger.ReadApiSpringCodeGen Available: android
ndroid
aspnet5
async-scala
cwiki
csharp
cpprest
.....
at io.swagger.codegen.CodegenConfigLoader.forName(CodegenConfigLoader.java:31)
at io.swagger.codegen.config.CodegenConfigurator.toClientOptInput(CodegenConfigurator.java:353)
at io.swagger.codegen.cmd.Generate.run(Generate.java:221)
at io.swagger.codegen.SwaggerCodegen.main(SwaggerCodegen.java:36)
Caused by: java.lang.ClassNotFoundException: com.foo.swag.codegen.swagger.ReadApiSpringCodeGen
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at io.swagger.codegen.CodegenConfigLoader.forName(CodegenConfigLoader.java:29)
... 3 more
You have to override method apiFilename - something like this:
#Override
public String apiFilename(final String templateName, final String tag) {
final String pathWithFile = super.apiFilename(templateName, tag);
final String pathWithoutFileExtension = pathWithFile.substring(0, pathWithFile.lastIndexOf('.')); //without .java
final int index = pathWithoutFileExtension.lastIndexOf('.');
final String className = ".Read" + pathWithoutFileExtension.substring(index+1) + ".java";
result pathWithoutFileExtension.substring(0, pathWithoutFileExtension.lastIndexOf('.')) + className;
}
I fixed it by extending SpringCodegen class and overriding toApiName() method.
public class ReadApiSpringCodeGen extends SpringCodegen
{
#Override
public String toApiName(String name) {
return "CustomReadApi";
}
}
Works perfectly. Thanks for all the clues.

Camel's BridgePropertyPlaceholderConfigurer is not working when using Java config

I'm using Spring Java config and writing a console application with a few Camel routes. I have several properties sources in my app, so I use two PropertyPlaceholderConfigurers:
#Configuration
#Import(CamelConfig.class)
#ComponentScan(basePackageClasses = {App.class})
public class Config
{
final static String ENV = System.getProperty( "ENV" );
#Bean
public static BridgePropertyPlaceholderConfigurer properties()
{
final BridgePropertyPlaceholderConfigurer result = new BridgePropertyPlaceholderConfigurer();
result.setOrder( 0 );
result.setIgnoreUnresolvablePlaceholders( true );
result.setLocations( new ClassPathResource( "a/b/c/environments/base.properties" ),
new ClassPathResource( "a/b/c/environments/" + ENV + "/env.properties" ) );
return result;
}
#Bean
public static BridgePropertyPlaceholderConfigurer dlqAppProperties()
{
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
final BridgePropertyPlaceholderConfigurer result = new BridgePropertyPlaceholderConfigurer();
yaml.setResources( new ClassPathResource( "app.yaml" ) );
result.setOrder( 1 );
result.setIgnoreUnresolvablePlaceholders( true );
result.setProperties( yaml.getObject() );
return result;
}
}
As per this doc I'm using BridgePropertyPlaceholderConfigurer class to make Spring properties available in Camel. It's config is simple too:
#Configuration
public class CamelConfig extends SingleRouteCamelConfiguration
{
#Override
protected CamelContext createCamelContext() throws Exception
{
final SpringCamelContext result = new SpringCamelContext( getApplicationContext() );
return result;
}
#Override
protected void setupCamelContext( CamelContext camelContext ) throws Exception
{
}
#Bean
#Override
public RouteBuilder route()
{
return (new Routes()).builder();
}
}
Test route (Scala DSL) is simple too:
class Routes extends RouteBuilder {
"timer://{{foo}}?period=2s" ==> {
process((exchange) => {
exchange.getIn.setBody("test")
})
to("log:test")
}
}
But the context does not start with following exception:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'camelContext' defined in class path resource [a/b/c/config/CamelConfig.class]: Invocation of init method failed; nested exception is org.apache.camel.FailedToCreateRouteException: Failed to create route route1: Route(route1)[[From[timer://{{foo}}?period=2s]] -> [process[... because of Failed to resolve endpoint: timer://{{foo}}?period=2s due to: PropertiesComponent with name properties must be defined in CamelContext to support property placeholders.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1566)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
at a.b.c.App.main(App.java:13)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: org.apache.camel.FailedToCreateRouteException: Failed to create route route1: Route(route1)[[From[timer://{{foo}}?period=2s]] -> [process[... because of Failed to resolve endpoint: timer://{{foo}}?period=2s due to: PropertiesComponent with name properties must be defined in CamelContext to support property placeholders.
at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:182)
at org.apache.camel.impl.DefaultCamelContext.startRoute(DefaultCamelContext.java:770)
at org.apache.camel.impl.DefaultCamelContext.startRouteDefinitions(DefaultCamelContext.java:1914)
at org.apache.camel.impl.DefaultCamelContext.doStartCamel(DefaultCamelContext.java:1670)
at org.apache.camel.impl.DefaultCamelContext.doStart(DefaultCamelContext.java:1544)
at org.apache.camel.spring.SpringCamelContext.doStart(SpringCamelContext.java:179)
at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
at org.apache.camel.impl.DefaultCamelContext.start(DefaultCamelContext.java:1512)
at org.apache.camel.spring.SpringCamelContext.maybeStart(SpringCamelContext.java:228)
at org.apache.camel.spring.SpringCamelContext.afterPropertiesSet(SpringCamelContext.java:104)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1625)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1562)
... 16 more
Caused by: org.apache.camel.ResolveEndpointFailedException: Failed to resolve endpoint: timer://{{foo}}?period=2s due to: PropertiesComponent with name properties must be defined in CamelContext to support property placeholders.
at org.apache.camel.impl.DefaultCamelContext.getEndpoint(DefaultCamelContext.java:477)
at org.apache.camel.util.CamelContextHelper.getMandatoryEndpoint(CamelContextHelper.java:63)
at org.apache.camel.model.RouteDefinition.resolveEndpoint(RouteDefinition.java:192)
at org.apache.camel.impl.DefaultRouteContext.resolveEndpoint(DefaultRouteContext.java:106)
at org.apache.camel.impl.DefaultRouteContext.resolveEndpoint(DefaultRouteContext.java:112)
at org.apache.camel.model.FromDefinition.resolveEndpoint(FromDefinition.java:72)
at org.apache.camel.impl.DefaultRouteContext.getEndpoint(DefaultRouteContext.java:88)
at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:890)
at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:177)
... 27 more
Caused by: java.lang.IllegalArgumentException: PropertiesComponent with name properties must be defined in CamelContext to support property placeholders.
at org.apache.camel.impl.DefaultCamelContext.resolvePropertyPlaceholders(DefaultCamelContext.java:1121)
at org.apache.camel.impl.DefaultCamelContext.getEndpoint(DefaultCamelContext.java:475)
... 35 more
Looks like the bridge does not work (but I definitely can use placeholders in Spring). What can be the problem?
Looks like if you want to use BridgePropertyPlaceholderConfigurer, you need to instantiate Camel contexts with CamelContextFactoryBean. It has initPropertyPlaceholder method:
#Override
protected void initPropertyPlaceholder() throws Exception {
super.initPropertyPlaceholder();
Map<String, BridgePropertyPlaceholderConfigurer> beans = applicationContext.getBeansOfType(BridgePropertyPlaceholderConfigurer.class);
if (beans.size() == 1) {
// setup properties component that uses this beans
BridgePropertyPlaceholderConfigurer configurer = beans.values().iterator().next();
String id = beans.keySet().iterator().next();
LOG.info("Bridging Camel and Spring property placeholder configurer with id: " + id);
// get properties component
PropertiesComponent pc = getContext().getComponent("properties", PropertiesComponent.class);
// replace existing resolver with us
configurer.setResolver(pc.getPropertiesResolver());
configurer.setParser(pc.getPropertiesParser());
String ref = "ref:" + id;
// use the bridge to handle the resolve and parsing
pc.setPropertiesResolver(configurer);
pc.setPropertiesParser(configurer);
// and update locations to have our as ref first
String[] locations = pc.getLocations();
String[] updatedLocations;
if (locations != null && locations.length > 0) {
updatedLocations = new String[locations.length + 1];
updatedLocations[0] = ref;
System.arraycopy(locations, 0, updatedLocations, 1, locations.length);
} else {
updatedLocations = new String[]{ref};
}
pc.setLocations(updatedLocations);
} else if (beans.size() > 1) {
LOG.warn("Cannot bridge Camel and Spring property placeholders, as exact only 1 bean of type BridgePropertyPlaceholderConfigurer"
+ " must be defined, was {} beans defined.", beans.size());
}
}
Well, the problem now is to have two bridges, but that's another story..
I had the same problem. Here's what worked for me (inspired by the initPropertyPlaceholder() method):
import org.apache.camel.component.properties.PropertiesComponent;
import org.apache.camel.spring.javaconfig.CamelConfiguration;
import org.apache.camel.spring.spi.BridgePropertyPlaceholderConfigurer;
#Configuration
#ComponentScan
public class AwesomeConfig extends CamelConfiguration {
private static final String PROPERTIES_BEAN_NAME = "springProperties";
#Resource(name = PROPERTIES_BEAN_NAME)
private BridgePropertyPlaceholderConfigurer springProperties;
#Bean(PROPERTIES_BEAN_NAME)
public static BridgePropertyPlaceholderConfigurer springProperties() throws Exception {
BridgePropertyPlaceholderConfigurer configurer = new BridgePropertyPlaceholderConfigurer();
configurer.setSystemPropertiesMode(BridgePropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE);
String defaultPropertiesPath = buildProperties().getProperty("properties.path");
String propertiesPath = System.getProperty(PROPERTY_FILE_SYSTEM_PROPERTY, defaultPropertiesPath);
configurer.setLocations(new ClassPathResource("META-INF/application.properties"));
return configurer;
}
#Bean
public PropertiesComponent camelProperties() throws Exception {
PropertiesComponent camelProperties = new PropertiesComponent();
springProperties.setParser(camelProperties.getPropertiesParser());
springProperties.setResolver(camelProperties.getPropertiesResolver());
camelProperties.setSystemPropertiesMode(springProperties.getSystemPropertiesMode());
camelProperties.setPropertiesResolver(springProperties);
camelProperties.setPropertiesParser(springProperties);
camelProperties.setLocation("ref:" + PROPERTIES_BEAN_NAME);
return camelProperties;
}
#Override
protected void setupCamelContext(CamelContext camelContext) throws Exception {
camelContext.addComponent("properties", camelProperties());
}
}
And here's how I use it:
import org.apache.camel.spring.javaconfig.Main;
public class AwesomeMain extends Main {
setConfigClass(AwesomeConfig.class);
}
public static void main(String... args) throws Exception {
AwesomeMain main = new AwesomeMain();
instance = main;
main.run(args);
}
Try to rename your first BridgePropertyPlaceholderConfigurer bean (method's name in your case).
Look what I have hacked up. Haven't fully tested but wanted to share; should work with Spring 5.x. Basically copies all of the Environment to the Camel's properties, so I don't use the Camel's "bridge" at all. One thing I am not sure for today, if I have to put it into "initial" or "overiding" properties:
#Configuration
public static class CamelConfig extends CamelConfiguration {
#Autowired
private ConfigurableEnvironment environment;
#Bean
... some beans ...
//#Bean -- haven't yet found out if we need it as a bean ...
private PropertiesComponent camelProperties() throws Exception {
PropertiesComponent camelProperties = new PropertiesComponent();
// just brutally copy all the properties form environment
HashSet<String> propertyNames = new HashSet<String>(100);
for (PropertySource ps : environment.getPropertySources()) {
if (ps instanceof MapPropertySource) {
MapPropertySource mps = (MapPropertySource) ps;
propertyNames.addAll(Arrays.asList(mps.getPropertyNames()));
}
}
Properties allProps = new Properties();
for (String prop : propertyNames) {
allProps.setProperty(prop, environment.getProperty(prop));
}
camelProperties.setInitialProperties(allProps);
// TODO: check it this is better or worse
//camelProperties.setOverrideProperties(allProps);
return camelProperties;
}
#Override
protected void setupCamelContext(CamelContext camelContext) throws Exception {
... some configs. ...
camelContext.addComponent("properties", camelProperties());
}
}

Resources