Caching Java 8 Optional with Spring Cache - spring

I have a method:
#Cacheable(key = "#jobId")
public Optional<JobInfo> getJobById(String jobId) {
log.info("Querying for job " + jobId);
counterService.increment("queryJobById");
Job job = jobsRepository.findOne(jobId);
if (job != null) {
return Optional.of(createDTOFromJob(job));
}
return Optional.empty();
}
When I am trying to retrieve the cached item I am getting the following exception:
2016-01-18 00:01:10 ERROR [trace=,span=] http-nio-8021-exec-2 [dispatcherServlet]:182 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [java.util.Optional]] with root cause
java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [java.util.Optional]

Just implement the Serializable interface in your DTO
#Document(collection = "document_name")
public class Document implements Serializable {
private static final long serialVersionUID = 7156526077883281623L;

Spring supports caching Optional. The issue is your Redis serializer (JdkSerializationRedisSerializer probably). It uses Java based serialization which requires the classes to be Serializable. You can solve this by configuring the RedisCacheManager to use another serializer that doesn't have this limitation. For example you can use Kryo (com.esotericsoftware:kryo:3.0.3):
#Bean
RedisCacheManager redisCacheManager (RedisTemplate<Object, Object> redisOperations) {
// redisOperations will be injected if it is configured as a bean or create it: new RedisTemplate()...
redisOperations.setDefaultSerializer(new RedisSerializer<Object>() {
//use a pool because kryo instances are not thread safe
KryoPool kryoPool = new KryoPool.Builder(Kryo::new).build();
#Override
public byte[] serialize(Object o) throws SerializationException {
ByteBufferOutput output = new ByteBufferOutput();
Kryo kryo = kryoPool.borrow();
try {
kryo.writeClassAndObject(output, o);
} finally {
kryoPool.release(kryo);
output.close();
}
return output.toBytes();
}
#Override
public Object deserialize(byte[] bytes) throws SerializationException {
if(bytes.length == 0) return null;
Kryo kryo = kryoPool.borrow();
Object o;
try {
o = kryo.readClassAndObject(new ByteBufferInput(bytes));
} finally {
kryoPool.release(kryo);
}
return o;
}
});
RedisCacheManager redisCacheManager = new RedisCacheManager(redisOperations);
redisCacheManager.setCachePrefix(new DefaultRedisCachePrefix("app"));
redisCacheManager.setTransactionAware(true);
return redisCacheManager;
}
Note that this is just an example, I didn't test this imeplementation. But I use the Kryo serializer in production in the same manner for redis caching with Spring.

Because your serialized object is not implement RedisSerializer, or you can extend class JdkSerializationRedisSerializer, which have implement RedisSerializer.
example code:
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
public class YourDTOObject extends JdkSerializationRedisSerializer implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
....
}
More details and principle, please visit my blog

Related

Is there a way to make a transactional action in a #Scheduled method?

So, my application is multi-tenants based, and I need to apply a transactional request each Sunday at 1:00 PM.
It basically needs to get All resources and create Usage (which is an entity per week) based on the actual Date.
My transactional Method :
/**
* Scheduled to run at 1:00 every sunday
* It should create capacities for all resources that doesn't have any for next years
*/
#Transactional
public void createNewCapacities() throws Exception {
LocalDate now = LocalDate.now();
System.out.println("Date is :" + now);
System.out.println("Start of capacities creation...");
//List<Resource> resources = resourceService.findAll();
//for(Resource resource : resources){
//Calendar calendar = calendarService.findCalendarById(resource.getSelectedCalendarId());
...
//}
}
My scheduler :
#Service
public class UsageServiceScheduler {
#Autowired
private UsageService usageService;
#Scheduled(cron= "0 0 1 * * SUN")
public void callScheduledTask() throws Exception {
usageService.createNewCapacities();
}
}
This throw an Exception:
org.springframework.transaction.CannotCreateTransactionException:
Could not open JPA EntityManager for transaction; nested exception is
java.lang.IllegalStateException: Cannot determine target DataSource
for lookup key [null].
Is there a way to establish a connection with the database during the #Scheduled method?
EDIT :
I have both #Transactional and #Scheduled enabled.
TENANT DATA SOURCE PROPERTIES :
#Component
#ConfigurationProperties(prefix = "tenants")
public class TenantDataSourceProperties {
private Map<Object, Object> dataSources = new LinkedHashMap<>();
public Map<Object, Object> getDataSources() {
return dataSources;
}
public void setDataSources(Map<String, Map<String, String>> dataSources) {
dataSources.forEach((key, value) -> this.dataSources.put(key, convert(value)));
}
public DataSource convert(Map<String, String> source) {
return DataSourceBuilder.create()
.url(source.get("jdbcUrl"))
.driverClassName(source.get("driverClassName"))
.username(source.get("username"))
.password(source.get("password"))
.build();
}
}
I want my cron job to run for all existing tenants.
Or in other words, get all the dataSources and apply cron job for each db.

How to use Resilience4j Circuit Breaker with WebFlux in Spring Boot

I have service A that calls downstream service B.
Service A code
#RestController
#RequestMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
public class GreetingController {
private final GreetingService greetingService;
public GreetingController(GreetingService greetingService){
this.greetingService = greetingService;
}
#GetMapping(value = "/greetings")
public Mono<String> getGreetings() {
return greetingService.callServiceB();
}
}
#Component
#RequiredArgsConstructor
public class GreetingService {
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("greetingService");
Callable<Mono<String>> callable = CircuitBreaker.decorateCallable(circuitBreaker, this::clientCall);
Future<Mono<String>> future = Executors.newSingleThreadExecutor().submit(callable);
public Mono<String> callServiceB() {
try {
return future.get();
} catch (CircuitBreakerOpenException | InterruptedException | ExecutionException ex){
return Mono.just("Service is down!");
}
}
private final String url = "/v1/holidaysgreetings";
private Mono<String> clientCall(){
WebClient client = WebClient.builder().baseUrl("http://localhost:8080").build();
return client
.get()
.uri(url)
.retrieve()
.bodyToMono(String.class);
}
when i shut down downstream service B(running on localhost:8080) and hit /greetings endpoint in GreetingsController class to see if my circuit breaker is working properly or not, i get very this nasty error
2021-06-28 21:27:31.431 ERROR 10285 --- [nio-8081-exec-7] o.a.c.c.C.[.[.[.[dispatcherServlet]: Servlet.service() for servlet [dispatcherServlet] in context with path [/v1/holidaysgreetings]
threw exception [Request processing failed; nested exception is org.springframework.web.reactive.function.client.WebClientRequestException: Connection refused: localhost/127.0.0.1:8080;
nested exception is io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8080] with root cause
java.net.ConnectException: Connection refused
Anyone knows why i am getting this? What i am missing here? Am i implementing circuit breaker correctly?
You are mixing reactive libraries with regular non-reactive libraries. If you aim to use spring-webflux it is better to use the reactor-resilience4j together with the regular reactor-adapter library.
Use these imports:
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j'
implementation "io.projectreactor.addons:reactor-adapter:${reactorVersion}"
You are also not creating the circuit-breaker service that you can rely on. After creating it you can call the " Mono run(Mono toRun, Function<Throwable, Mono> fallback)" (to the one that return a Flux if you want) to execute your service and provide a fallback.
Here is one example from a demo code.
#RestController
public class CompletableFutureDemoController {
Logger LOG = LoggerFactory.getLogger(CompletableFutureDemoController.class);
private CompletableFutureHttpBinService httpBin;
private ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory;
public CompletableFutureDemoController(CompletableFutureHttpBinService httpBin, ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory) {
this.httpBin = httpBin;
this.reactiveCircuitBreakerFactory = reactiveCircuitBreakerFactory;
}
#GetMapping("/completablefuture/delay/{seconds}")
public Mono<Map> delay(#PathVariable int seconds) {
return reactiveCircuitBreakerFactory.create("completablefuturedelay")
.run(Mono.fromFuture(httpBin.delay(seconds)), t -> {
LOG.warn("delay call failed error", t);
Map<String, String> fallback = new HashMap();
fallback.put("hello", "world");
return Mono.just(fallback);
}
);
}
}

Spring profiles not getting resolved when using it with the spring web based project

in application.properties given : spring.profiles.active=DEV
and in dev config file : mentioned all the mongo connection properties
and added the configuration java file like
#Configuration
#PropertySource("classpath:userIdentity_Dev.properties")
#Profile("DEV")
public class UserIdentityConfigDev
{
}
when running the application the spring profiler is not getting resolved the
below stack trace is received
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userIdentityService': Unsatisfied dependency expressed through field 'userIdentityBusiness'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userIdentityBusiness': Unsatisfied dependency expressed through field 'userIdentityRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userIdentityRepositoryImpl': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'mongodb.userIdentity.host' in string value "${mongodb.userIdentity.host}"
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
Saying that the ${mongodb.userIdentity.host} property is not resolved
when creating war and jar file for the project the spring profile is not resolved
This is main class:
` #SpringBootApplication(exclude= {DataSourceAutoConfiguration.class ,MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
#PropertySource("classpath:application.properties")
public class ApplicationStart extends SpringBootServletInitializer
{
public static void main(String[] args)
{
SpringApplication.run(ApplicationStart.class,args);
}
}`
below is the property file:
## MongoDB Connection Properties-----------------
MongoDB database
mongodb.userIdentity.database = UserIdentity_CS
isConnectionStringUsed is true then application creates connection as per connectionString else it will use MongoDB single server properties.
mongodb.userIdentity.isConnectionStringUsed = false
connectionString with authentication
mongodb.connectionString = mongodb://sa:Test%40123#SPT-CPU-0259:27017,SPT-CPU-0173:27017/admin?replicaSet=surveillens
connectionString without authentication
mongodb.userIdentity.connectionString = mongodb://localhost:27017/?replicaSet=surveillens
MongoDB single server properties.
mongodb.userIdentity.host = localhost
mongodb.userIdentity.port = 27017
Authentication properties
mongodb.userIdentity.isAuthenticationEnable = false
mongodb.userIdentity.userName = sa
mongodb.userIdentity.password = Test#123
mongodb.userIdentity.authDB = admin
Collection Name for user Identity
mongodb.userIdentity.collectionName = CreditScore
Other properties -----------------------
userIdentity.ValidKeySet = email;phonenumber;_id
userIdentity.logsFolder = ./IdentityLogs/
userIdentity.insertBatchSize = 100
and below is the file .java file where all this properties are used
`
#Configuration
public abstract class MongoDbRepository {
private Class<T> clazz;
private static MongoClient mongoClient = null;
private static MongoDatabase mongoDatabase = null;
private static ObjectMapper mapper = null;
#Value("${mongodb.userIdentity.host}")
private String mongoHost;
#Value("${mongodb.userIdentity.port}")
private int mongoPortNumber;
#Value("${mongodb.userIdentity.database}")
private String mongoDatabaseName;
#Value("${mongodb.userIdentity.userName}")
private String mongoUserName;
#Value("${mongodb.userIdentity.authDB}")
private String mongoAuthDB;
#Value("${mongodb.userIdentity.password}")
private String mongoPassword;
#Value("${mongodb.userIdentity.isAuthenticationEnable}")
private boolean mongoIsAuthEnable;
#Value("${mongodb.userIdentity.isConnectionStringUsed}")
private boolean mongoIsConnectionStringUsed;
#Value("${mongodb.userIdentity.connectionString}")
private String mongoConnectionString;
public final void setClazz(Class<T> clazzToSet)
{
this.clazz = clazzToSet;
}
/**
* Instantiates a new mongo base repository.
* #throws Exception
*/
public MongoDbRepository()
{
//Trigger MongoDB Connection initialization
if(mongoClient == null)
{
prepareMongoConnection();
}
else
{
// Trigger any method to check MongoDB client is connected
mongoClient.getAddress();
}
// Trigger ObjectMapper initialization
if(mapper == null)
prepareObjectMapper();
}
/**
* Instantiates a new mongoDB connection.
* #throws Exception
*/
private void prepareMongoConnection()
{
if (mongoConnectionString != null && !mongoConnectionString.isEmpty())
{
boolean isConnectionStringUsed = mongoIsConnectionStringUsed;
if(isConnectionStringUsed)
{
MongoClientURI clientUri = new MongoClientURI(mongoConnectionString);
mongoClient = new MongoClient(clientUri);
}
else
{
if(mongoIsAuthEnable)
{
MongoCredential credential = MongoCredential.createCredential(mongoUserName, mongoAuthDB, mongoPassword.toCharArray());
mongoClient = new MongoClient( new ServerAddress(mongoHost, mongoPortNumber), Arrays.asList(credential));
}
else
mongoClient = new MongoClient(mongoHost, mongoPortNumber);
}
// Trigger any method to check MongoDB client is connected
mongoClient.getAddress();
// Get Database from mongoClient.
mongoDatabase = mongoClient.getDatabase(mongoDatabaseName);
}
}
/**
* Get an objectMapper.
*/
private void prepareObjectMapper()
{
mapper = CommonFunctions.getObjectMapper();
}
/**
* Get the MongoDB collection object from MongoDB.
*
* #param collectionName is Name of a MongoDB collection
* #return Collection object
* #throws Exception
*/
private MongoCollection<Document> getCollection(String collectionName) throws Exception
{
if(mongoClient == null)
prepareMongoConnection();
return mongoDatabase.getCollection(collectionName);
}
/* ------- Find functions ------- */
/**
* Find one documents from mongoDB collection.
*
* #param collectionName the collection name
* #param query the query document - set to empty document means no query filtering.
*
* #return entityObj the entity Object
* #throws Exception the exception
*/
public T findOne(String collectionName, Object query) throws Exception
{
if(clazz == null)
throw new NullPointerException("ST224 - Generic class is null - set the generic class before perform MongoDB operation");
MongoCollection<Document> collection = getCollection(collectionName);
Document mongoDoc = collection.find(convertToBsonDocument(query)).first();
String jsonStr = mapper.writeValueAsString(mongoDoc);
T entityObj = mapper.readValue(jsonStr, clazz);
return entityObj;
}
}`

spring mvc processing xml with relative path to dtd

My webservice receives an xml from a third-party source, which contains a !DOCTYPE declaration. Therefore I must use the second method in my controller to parse the xml document, the first one gives me this exception:
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not unmarshal to [class com.example.MeterBusXml]: null; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 48; DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.]
I have no control over the application which posts the xml, so I must adapt my webservice to parse it with the dtd.
My question is, what is the spring framework's way of injecting the EntityResolver into every XMLReader instance?
#RestController
public class MeterBusDataController {
#RequestMapping (
consumes = APPLICATION_XML_VALUE,
method = POST,
path = "/meterbus1"
)
public void method1(#RequestBody MeterBusXml xml) {
System.out.println(xml);
}
#RequestMapping(
method = POST,
path = "/meterbus2"
)
public void method2(HttpServletRequest rq) throws IOException, ParserConfigurationException, SAXException, JAXBException {
JAXBContext jc = newInstance(MeterBusXml.class);
Unmarshaller um = jc.createUnmarshaller();
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
spf.setValidating(true);
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
xr.setEntityResolver(new EntityResolver() {
#Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
return new InputSource(new StringReader(""));
}
});
BufferedReader reader = rq.getReader();
InputSource inputSource = new InputSource(reader);
SAXSource saxSource = new SAXSource(xr, inputSource);
MeterBusXml xml = (MeterBusXml)um.unmarshal(saxSource);
System.out.println(xml);
}
}
See the following document for an example of the mbus.xml I'm trying to unmarshal.
http://prevodniky.sk/products/product_EthMBus_common/download/Ethernet_converters_exports_v1_02_EN.pdf
I've found the root of the problem. First I tried to create and configure a Jaxb2Marshaller bean, but that did not work out. Then I realized, I need a HttpMessageConverter, so I had to override the extendMessageConverters method in the WebMvcConfigurerAdapter class, and set the required properties on Jaxb2RootElementHttpMessageConverter. This message converter does not use a Jaxb2Marshaller, but it's internal workings are very similar.
setSupportDtd(true) is required, to force the parser to accept the !DOCTYPE declaration.
setProcessExternalEntities(false) is required, because if this property is false, then the converter uses a blank EntityResolver, just as I did in method2.
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<Jaxb2RootElementHttpMessageConverter?>> converters) {
for (final Iterator<HttpMessageConverter<?>> iterator = converters.iterator(); iterator.hasNext();) {
HttpMessageConverter<?> next = iterator.next();
if (next instanceof Jaxb2RootElementHttpMessageConverter) {
Jaxb2RootElementHttpMessageConverter jaxbConverter = (Jaxb2RootElementHttpMessageConverter) next;
jaxbConverter.setProcessExternalEntities(false);
jaxbConverter.setSupportDtd(true);
}
}
}
}

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