How and where to invoke HeaderValueRouter and HeaderEnricher for jms or activemq with spring-integration - spring

In my project ,we use spring-integration to implement the queue. The configuration for Header-Value-Router is in the xml as below:
<header-value-router input-channel="jmsUVMasterMessageChannel"
header-name="METADATA_TYPE" default-output-channel="nullChannel"
resolution-required="false">
<mapping value="ASSEMBLY" channel="downloadImageToS3Channel" />
<mapping value="THIRDPARTY" channel="invokeUVMasterAPIChannel" />
</header-value-router>
<header-enricher input-channel="JuiceVendorMessageChannel"
output-channel="JuiceHeaderEnricherChannel">
<header name="sourceName" value="JUICE" />
</header-enricher>
And here is the gateway config:
I wanna know how to implement this config in java code? I searched google and found following code:
#Bean
public HeaderValueRouter headerRouter(String gatewayPrefix) {
HeaderValueRouter router = new HeaderValueRouter("METADATA_TYPE");
router.setChannelMapping("ASSEMBLY", "downloadImageToS3Channel");
router.setChannelMapping("THIRDPARTY", "invokeUVMasterAPIChannel");
router.setDefaultOutputChannel(new NullChannel());
router.setResolutionRequired(false);
return router;
}
#Bean
#Transformer(inputChannel="sdiVenderMessageChannel", outputChannel="sdiHeaderEnricherChannel")
public HeaderEnricher enrichHeaders() {
Map<String, HeaderValueMessageProcessor<?>> headersToAdd = new HashMap<String, HeaderValueMessageProcessor<?>>();
// TODO
//headersToAdd.put("sourceName",new StaticHeaderValueMessageProcessor<String>("SDI"));
headersToAdd.put("sourceName", null);
HeaderEnricher enricher = new HeaderEnricher(headersToAdd);
if(logger.isDebugEnabled()){
logger.debug("HeaderEnricher bean initial!");
}
return enricher;
}
But here are my questions:
1) how to config the 'input-channel' in java config?
2) Where should I invoke this function? When I start the application , the code doesn't run into this function. I know the headerValueRouter is used to dispatcher the message from input-channel to the output-channel, but what's the exact place that I can invoke this router? In the ConnectionFactory or ListenerContainer?
And here is the gateway I defined:
public void registerDynamicInboundGateway(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition("org.springframework.integration.jms.JmsMessageDrivenEndpoint");
String jndiFactoryName = this.registerJndiObjectFactoryBean(gatewayPrefix, jmsQueueValue);
this.destination = jndiFactoryName;
String containerBeanName = this.registerMessageListenerContainer();
String listenerBeanName = this.registerMessageListener();
builder.addConstructorArgReference(containerBeanName);
builder.addConstructorArgReference(listenerBeanName);
setValueIfAttributeDefined(builder, autoStartup, "auto-startup");
String beanName = null;
if(gatewayPrefix.equals(HCC_GATEWAY_PREFIX)){
beanName = gatewayPrefix + DynamicInboundGateway.HCC_GATEWAY_SUFIX;
} else if (gatewayPrefix.equals(UVMASTER_GATEWAY_PREFIX)) {
beanName = gatewayPrefix + DynamicInboundGateway.UVMASTER_GATEWAY_PREFIX;
} else {
beanName = gatewayPrefix + DynamicInboundGateway.GATEWAY_SUFIX;
}
this.beanFactory.registerBeanDefinition(
beanName, builder.getBeanDefinition());
}
I felt confused and didn't found any sample code for this.
Thanks a lot!
When I define the HeaderEnricher according to the sample code in the spring-integration website like below:
#Bean
#Transformer(inputChannel="sdiVenderMessageChannel", outputChannel="sdiHeaderEnricherChannel")
public HeaderEnricher enrichHeaders() {
Map<String, HeaderValueMessageProcessor<?>> headersToAdd = new HashMap<String, HeaderValueMessageProcessor<?>>();
// TODO
headersToAdd.put("sourceName",new StaticHeaderValueMessageProcessor<String>("SDI"));
//headersToAdd.put("sourceName", null);
HeaderEnricher enricher = new HeaderEnricher(headersToAdd);
if(logger.isDebugEnabled()){
logger.debug("HeaderEnricher bean initial!");
}
return enricher;
}
And this occured some error:
StaticHeaderValueMessageProcessor cannot be resolved to a type
But I've import all the required packages with maven. I can not figure out why.

Hopefully, this simple Spring Boot app will make things clear...
#SpringBootApplication
public class So40585409Application {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(So40585409Application.class, args);
JmsTemplate template = context.getBean(JmsTemplate.class);
template.convertAndSend("foo", "message1", m -> {
m.setStringProperty("foo", "bar");
return m;
});
template.convertAndSend("foo", "message2", m -> {
m.setStringProperty("foo", "baz");
return m;
});
Thread.sleep(5000);
context.close();
}
#Bean
public JmsMessageDrivenEndpoint inbound(ConnectionFactory jmsConnectionFactory) {
return new JmsMessageDrivenEndpoint(container(jmsConnectionFactory), listener());
}
#Bean
public DefaultMessageListenerContainer container(ConnectionFactory jmsConnectionFactory) {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(jmsConnectionFactory);
container.setDestinationName("foo");
return container;
}
#Bean
public ChannelPublishingJmsMessageListener listener() {
ChannelPublishingJmsMessageListener listener = new ChannelPublishingJmsMessageListener();
listener.setRequestChannelName("toRouter");
return listener;
}
#Bean
#Router(inputChannel="toRouter")
public HeaderValueRouter router() {
HeaderValueRouter router = new HeaderValueRouter("foo");
router.setChannelMapping("bar", "barChannel");
router.setChannelMapping("baz", "bazChannel");
return router;
}
#ServiceActivator(inputChannel = "barChannel")
public void bar(String in) {
System.out.println("Received via barChannel: " + in);
}
#ServiceActivator(inputChannel = "bazChannel")
public void baz(String in) {
System.out.println("Received via bazChannel: " + in);
}
}
Result:
2016-11-14 09:17:56.798 INFO 65082 --- [ main] com.example.So40585409Application : Started So40585409Application in 8.842 seconds (JVM running for 10.819)
Received via barChannel: message1
Received via bazChannel: message2
2016-11-14 09:18:02.038 INFO 65082 --- [ main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext#2b546384: startup date [Mon Nov 14 09:17:49 EST 2016]; root of context hierarchy
EDIT - Header Enricher
I just added your bean with no problems...
#ServiceActivator(inputChannel = "barChannel2")
public void bar(String in, #Header("sourceName") String sourceName) {
System.out.println("Received via barChannel: " + in + " " + sourceName);
}
#ServiceActivator(inputChannel = "bazChannel")
public void baz(String in) {
System.out.println("Received via bazChannel: " + in);
}
#Bean
#Transformer(inputChannel = "barChannel", outputChannel = "barChannel2")
public HeaderEnricher enrichHeaders() {
Map<String, HeaderValueMessageProcessor<?>> headersToAdd = new HashMap<String, HeaderValueMessageProcessor<?>>();
headersToAdd.put("sourceName", new StaticHeaderValueMessageProcessor<String>("SDI"));
HeaderEnricher enricher = new HeaderEnricher(headersToAdd);
return enricher;
}
Result:
2016-11-15 09:00:48.383 INFO 50137 --- [ main] com.example.So40585409Application : Started So40585409Application in 1.814 seconds (JVM running for 2.528)
Received via barChannel: message1 SDI
Received via bazChannel: message2
2016-11-15 09:00:53.424 INFO 50137 --- [ main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext#2c34f934: startup date [Tue Nov 15 09:00:46 EST 2016]; root of context hierarchy

Related

Spring Boot Kafka Configure DefaultErrorHandler?

I created a batch-consumer following the Spring Kafka docs:
#SpringBootApplication
public class ApplicationConsumer {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationConsumer.class);
private static final String TOPIC = "foo";
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ApplicationConsumer.class, args);
}
#Bean
public RecordMessageConverter converter() {
return new JsonMessageConverter();
}
#Bean
public BatchMessagingMessageConverter batchConverter() {
return new BatchMessagingMessageConverter(converter());
}
#KafkaListener(topics = TOPIC)
public void listen(List<Name> ps) {
LOGGER.info("received name beans: {}", Arrays.toString(ps.toArray()));
}
}
I was able to successfully get the consumer running by defining the following additional configuration env variables, that Spring automatically picks up:
export SPRING_KAFKA_BOOTSTRAP-SERVERS=...
export SPRING_KAFKA_CONSUMER_GROUP-ID=...
So the above code works. But now I want to customize the default error handler to use exponential backoff. From the ref docs I tried adding the following to ApplicationConsumer class:
#Bean
public ConcurrentKafkaListenerContainerFactory<?, ?> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setCommonErrorHandler(new DefaultErrorHandler(new ExponentialBackOffWithMaxRetries(10)));
factory.setConsumerFactory(consumerFactory());
return factory;
}
#Bean
public ConsumerFactory<String, Object> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
#Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>();
return props;
}
But now I get errors saying that it can't find some of the configuration. It looks like I'm stuck having to redefine all of the properties in consumerConfigs() that were already being automatically defined before. This includes everything from bootstrap server uris to the json-deserialization config.
Is there a good way to update my first version of the code to just override the default-error handler?
Just define the error handler as a #Bean and Boot will automatically wire it into its auto configured container factory.
EDIT
This works as expected for me:
#SpringBootApplication
public class So70884203Application {
public static void main(String[] args) {
SpringApplication.run(So70884203Application.class, args);
}
#Bean
DefaultErrorHandler eh() {
return new DefaultErrorHandler((rec, ex) -> {
System.out.println("Recovered: " + rec);
}, new FixedBackOff(0L, 0L));
}
#KafkaListener(id = "so70884203", topics = "so70884203")
void listen(String in) {
System.out.println(in);
throw new RuntimeException("test");
}
#Bean
NewTopic topic() {
return TopicBuilder.name("so70884203").partitions(1).replicas(1).build();
}
}
foo
Recovered: ConsumerRecord(topic = so70884203, partition = 0, leaderEpoch = 0, offset = 0, CreateTime = 1643316625291, serialized key size = -1, serialized value size = 3, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = foo)

Spring Boot - Getting the hostname in application.properties for Spring-Kafka client Id

I am working on a project with Spring-Kafka and Boot and am wanting to get the hostname in the application.properties for the property spring.kafka.consumer.client-Id so that each of my consumers can be distinguished in the server side logs should there be an issue.
Is there a way i could do that? I check on the spring boot reference guide and java.lang.System class but could not find fruitful pointers.
Here's one way - add an EnvironmentAware bean to your config...
#SpringBootApplication
public class So43191948Application implements EnvironmentAware {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(So43191948Application.class, args);
#SuppressWarnings("unchecked")
KafkaTemplate<String, String> template = context.getBean(KafkaTemplate.class);
template.send("so43191948", "foo");
Thread.sleep(10000);
context.close();
}
#KafkaListener(topics = "so43191948")
public void foo(String in) {
System.out.println(in);
}
#Override
public void setEnvironment(Environment environment) {
Properties props = new Properties();
try {
props.setProperty("spring.kafka.consumer.client-id", InetAddress.getLocalHost().getHostName() + ".client");
PropertiesPropertySource propertySource = new PropertiesPropertySource("myProps", props);
if (environment instanceof StandardEnvironment) {
((StandardEnvironment) environment).getPropertySources().addFirst(propertySource);
}
}
catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
In this case, I did it in the boot app itself but it can be done in any bean.
2017-04-03 16:12:32.646 INFO 64879 --- [ main] o.a.k.clients.consumer.ConsumerConfig
: ConsumerConfig values:
auto.commit.interval.ms = 5000
auto.offset.reset = earliest
bootstrap.servers = [localhost:9092]
check.crcs = true
client.id = myhost.client
...
application.properties:
spring.kafka.consumer.client-id=foo
spring.kafka.consumer.group-id=myGroup
spring.kafka.consumer.auto-offset-reset=earliest
As you can see, the client-id is overridden.
import java.net.InetAddress;
import java.net.UnknownHostException;
private String getHostAddress() {
String hostaddress = "";
try {
hostaddress = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error("Error while fetching hostaddress:", e);
}
return hostaddress;}

Unexpected message - no endpoint registered with connection interceptor while communicating with multiple servers

#Configuration
#Component
public class GatewayAqrConfig {
#Autowired
ConnectorService connectorService;
#Autowired
MasterService masterService;
private HashMap<ConnectorPK, GatewayAqr> connectorMap;
#Bean
#Scope(value = "prototype")
public AbstractClientConnectionFactory clientCF(Connector connector , Master master) {
TcpNetClientConnectionFactory clientConnectionFactory = new TcpNetClientConnectionFactory(connector.getAqrIpAddr(), connector.getAqrIpPortNo());
clientConnectionFactory.setSingleUse(false);
MyByteArraySerializer obj = new MyByteArraySerializer(master.getAqrMsgHeaderLength(), master.getAqrId());
clientConnectionFactory.setSerializer(obj);
clientConnectionFactory.setDeserializer(obj);
clientConnectionFactory.setSoKeepAlive(true);
TcpMessageMapper tcpMessageMapper = new TcpMessageMapper();
tcpMessageMapper.setCharset("ISO-8859-1");
clientConnectionFactory.setMapper(tcpMessageMapper);
clientConnectionFactory.setBeanName(connector.getAqrIpAddr() + ":" + connector.getAqrIpPortNo());
clientConnectionFactory.afterPropertiesSet();
clientConnectionFactory.start();
return clientConnectionFactory;
}
#Bean
#Scope(value = "prototype")
public TcpSendingMessageHandler tcpOutGateway(AbstractClientConnectionFactory connectionFactory) {
TcpSendingMessageHandler messageHandler = new TcpSendingMessageHandler();
messageHandler.setConnectionFactory(connectionFactory);
messageHandler.setClientMode(true);
messageHandler.setTaskScheduler(getTaskScheduler());
messageHandler.setStatsEnabled(true);
messageHandler.afterPropertiesSet();
messageHandler.start();
return messageHandler;
}
#Bean
#Scope(value = "prototype")
public TcpReceivingChannelAdapter tcpInGateway(AbstractClientConnectionFactory connectionFactory) {
TcpReceivingChannelAdapter messageHandler = new TcpReceivingChannelAdapter();
messageHandler.setConnectionFactory(connectionFactory);
messageHandler.setClientMode(true);
messageHandler.setOutputChannel(receive());
messageHandler.setAutoStartup(true);
messageHandler.setTaskScheduler(getTaskScheduler());
messageHandler.afterPropertiesSet();
messageHandler.start();
return messageHandler;
}
#Bean
#Scope(value = "prototype")
public TaskScheduler getTaskScheduler() {
TaskScheduler ts = new ThreadPoolTaskScheduler();
return ts;
}
#Bean
public MessageChannel receive() {
QueueChannel channel = new QueueChannel();
return channel;
}
#Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return new PollerMetadata();
}
#Bean
#Transactional
public HashMap<ConnectorPK, GatewayAqr> gatewayAqr() throws Exception {
connectorMap = new HashMap();
Connector connector = null;
ConnectorPK connectorPK = null;
Master master = null;
TcpConnectionSupport connectionSupport = null;
// 1. Get List of Connections configured in Database
List<Connector> connectors = connectorService.getConnections();
if (connectors.size() > 0) {
for (int i = 0; i < connectors.size(); i++) {
// 2. Get the connection details
connector = connectors.get(i);
connectorPK = aqrConnector.getConnectorpk();
master = masterService.findById(connectorPK.getAcuirerId());
try {
// 3. Create object of TcpNetClientConnectionFactory for each Acquirer connection
AbstractClientConnectionFactory clientConnectionFactory = clientCF(aqrConnector, aqrMaster);
// 4. Create TcpSendingMessageHandler for the Connection
TcpSendingMessageHandler outHandler = tcpOutGateway(clientConnectionFactory);
// 5. Create TcpReceivingChannelAdapter object for the Connection and assign it to receive channel
TcpReceivingChannelAdapter inHandler = tcpInGateway(clientConnectionFactory);
// 6. Generate the GatewayAqr object
GatewayAqr gatewayAqr = new GatewayAqr(clientConnectionFactory, outHandler, inHandler);
// 7. Put in the MAP acuirerPK and Send MessageHandler object
connectorMap.put(aqrConnectorPK, gatewayAquirer);
} catch (Exception e) {
}
} // for
} // if
return connectorMap;
}
}
*********************************************************************************************************************************
#EnableIntegration
#IntegrationComponentScan(basePackageClasses = {GatewayEventConfig.class,GatewayAqrConfig.class })
#Configuration
#ComponentScan(basePackages = {"com.iz.zw.gateway.impl", "com.iz.zw.configuration"})
#Import({GatewayEventConfig.class,GatewayAquirerConfig.class})
public class GatewayConfig {
#Autowired
private GatewayAsyncReply<Object, Message<?>> gatewayAsyncReply;
#Autowired
private GatewayCorrelationStrategy gatewayCorrelationStrategy;
#Autowired
private HashMap<ConnectorPK, GatewayAqr> gatewayAqrs;
#Autowired
ConnectorService connectorService;
#Autowired
GatewayResponseDeserializer gatewayResponseDeserializer;
#MessagingGateway(defaultRequestChannel = "send")
public interface Gateway {
void waitForResponse(TransactionMessage transaction);
}
#Bean
public MessageChannel send() {
DirectChannel channel = new DirectChannel();
return channel;
}
#Bean
#ServiceActivator(inputChannel = "send")
public BarrierMessageHandlerWithLateGoodResponse barrier() {
BarrierMessageHandlerWithLateGoodResponse barrier = new BarrierMessageHandlerWithLateGoodResponse(25000, this.gatewayCorrelationStrategy);
barrier.setAsync(true);
barrier.setOutputChannel(out());
barrier.setDiscardChannel(lateGoodresponseChannel());
return barrier;
}
#ServiceActivator(inputChannel = "out")
public void printMessage(Message<?> message) {
System.out.println("in out channel");
}
#Transformer(inputChannel = "receive", outputChannel = "process")
public TransactionMessage convert(byte[] response) {
logger.debug("Response Received", Arrays.toString(response));
TransactionMessage transactionMessage = gatewayResponseDeserializer.deserializeResponse(response);
System.out.println("Response : " + response);
return transactionMessage;
}
#ServiceActivator(inputChannel = "process")
#Bean
public MessageHandler releaser() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
try {
gatewayAsyncReply.put(message);
barrier().trigger(message);
} catch (GatewayLateGoodMessageException exception) {
System.out.println("Late good response..!");
gatewayAsyncReply.get(message);
lateGoodresponseChannel().send(message);
}
}
};
}
#Bean
public MessageChannel process() {
QueueChannel channel = new QueueChannel();
return channel;
}
#Bean
public MessageChannel out() {
DirectChannel channel = new DirectChannel();
return channel;
}
#Bean
public MessageChannel lateGoodresponseChannel() {
QueueChannel channel = new QueueChannel();
return channel;
}
#ServiceActivator(inputChannel="lateGoodresponseChannel")
public void handleLateGoodResponse(Message<?> message) {
String strSTAN = null;
String strResponse = null;
Message<?> respMessage = null;
if(message instanceof TransactionMessage){
strSTAN = ((TransactionMessage)message).getStan();
respMessage = gatewayAsyncReply.get(strSTAN);
if (null != respMessage) {
strResponse = (String) message.getPayload();
}
}
logger.info("Late Good Response: " + strResponse);
}
}
*********************************************************************************************************************************
#Configuration
public class GatewayEventConfig {
private static final Logger logger = LoggerFactory.getLogger(GatewayEventConfig.class);
#Bean
public ApplicationEventListeningMessageProducer tcpEventListener() {
ApplicationEventListeningMessageProducer producer = new ApplicationEventListeningMessageProducer();
producer.setEventTypes(new Class[] {TcpConnectionOpenEvent.class, TcpConnectionCloseEvent.class, TcpConnectionExceptionEvent.class});
producer.setOutputChannel(tcpEventChannel());
producer.setAutoStartup(true);
producer.setTaskScheduler(getEventTaskScheduler());
producer.start();
return producer;
}
#Bean
public TaskScheduler getEventTaskScheduler() {
TaskScheduler ts = new ThreadPoolTaskScheduler();
return ts;
}
#Bean
public MessageChannel tcpEventChannel() {
return new QueueChannel();
}
#Transactional
#ServiceActivator(inputChannel = "tcpEventChannel")
public void tcpConnectionEvent(TcpConnectionEvent event) {
System.out.println("In publishing" + event.toString());
String strConnectionFactory = event.getConnectionFactoryName();
if (strConnectionFactory.equals("connection1")) {
//send some message to connector
} else {
// send message to another connector
}
}
}
this is my configuration files, my application tries to connect to 2 servers as soon as it starts.
I have made 2 configurations for 2 servers as above class
GatewayAqrConfig1 and GatewayConfig1 classes are used for first server connection
GatewayAqrConfig2 and GatewayConfig2 classes are used for second server connection
Using event I am connecting to server and sending a connection set up message, if server is already started and If I have started my application,
it gets the event, connects and sends the message but I am not getting the response instead I am getting the WARNING as below
**WARN TcpNetConnection:186 - Unexpected message - no endpoint registered with connection interceptor:**
i.e connection does not registers the listener properly
but if I am starting my application first and then servers I am getting responses perfectly, As I am connecting to servers
I could not restart it ? My application should connect to server which is already started ? what could be the problem ?
Version used:
Spring integration Version : 4.3.1
Spring version : 4.3.2
JDK 1.8 on JBOSS EAP 7
That WARN message means that, somehow, an inbound message was received without a TcpReceivingChannelAdapter having been registered with the connection factory. Client mode should make no difference.
Having looked at your code a little more, the prototype beans should be ok, as long as you use those objects (especially TcpMessageHandler directly rather than via the framework).
It's not obvious to me how that can happen, given your configuration; the listener is registered when you call setConnectionFactory on the receiving adapter.
If you can reproduce it with a trimmed-down project and post it someplace, I will take a look.

Why spring bean can't be injected in quartz job?

I am now writing a quartz job to start at 3:00 am every day to save data in database and redis. But now I got a problem and can't get it solved even though I tried a lot of times.
The job:
public class QuartzScheduler implements ServletContextListener {
private static Logger logger = Logger.getLogger(QuartzScheduler.class);
#Autowired
private ExchangeRateConfigBean exchangeRateConfigBean;
private Scheduler scheduler = null;
#Override
public void contextInitialized(ServletContextEvent servletContext) {
try {
ExchangeRateConfig exchangeRateConfig = exchangeRateConfigBean.setUpConfigurationFromConfigFile();
if(!exchangeRateConfig.getJob_fire()) {
logger.info("ExchangeRate Job Info: Current Exchange Rate Job flag is not open. Wont start the job!");
return;
}
} catch (Exception e) {
logger.error("ExchangeRate Job Info: Something wrong when analyzing config.properties file!");
logger.error(e.getMessage());
logger.error(e.getStackTrace());
logger.error(e.getCause());
return;
}
try {
scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
logger.info("ExchangeRate Job Info: Exchange Rate job starting......");
} catch (SchedulerException ex) {
logger.error(ex);
}
}
#Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
try {
scheduler.shutdown();
} catch (SchedulerException e) {
logger.error(e);
}
}
In this job , I autowired the component ExchangeRateConfigBean, but when I debugged in, I saw that the instance is null.
the spring config below:
<bean id="exchangeRateConfigBean" class="com.letv.exchangerate.bean.impl.ExchangeRateConfigBeanImpl">
<constructor-arg ref="exchangeRateErrorBean" />
</bean>
<bean id="exchangeRateErrorBean" class="com.letv.exchangerate.bean.impl.ExchangeRateErrorBeanImpl">
</bean>
The config Bean below:
public class ExchangeRateConfigBeanImpl implements ExchangeRateConfigBean {
public ExchangeRateConfigBeanImpl(){}
public ExchangeRateConfigBeanImpl(ExchangeRateErrorBean exchangeRateErrorBean)
{
this.exchangeRateErrorBean = exchangeRateErrorBean;
}
private static Logger logger = Logger.getLogger(ExchangeRateConfigBeanImpl.class.getClass());
private ExchangeRateErrorBean exchangeRateErrorBean;
#Override
public ExchangeRateConfig setUpConfigurationFromConfigFile() throws IOException {
InputStream inputStream = null;
ExchangeRateConfig exchangeRateConfig = null;
try {
Properties prop = new Properties();
String propFileName = "config.properties";
inputStream = getClass().getClassLoader().getResourceAsStream(propFileName);
exchangeRateConfig = new ExchangeRateConfig();
if (inputStream != null) {
prop.load(inputStream);
} else {
logger.error("property file '" + propFileName + "' not found in the classpath");
throw new FileNotFoundException("property file '" + propFileName + "' not found in the classpath");
}
String job_fire_tmp = prop.getProperty("job_fire");
exchangeRateConfig.setJob_fire(job_fire_tmp.isEmpty() ? false : Boolean.parseBoolean(job_fire_tmp));
return exchangeRateConfig;
} catch (IOException e) {
logger.error(e.getStackTrace());
return exchangeRateConfig;
} finally {
inputStream.close();
}
}
In the config bean, I used the constructor injection to inject the error bean above. the error bean's code lists below:
public class ExchangeRateErrorBeanImpl implements ExchangeRateErrorBean{
public ExchangeRateErrorBeanImpl(){}
#Override
public ExchangeRateError getExchangeRateError(String content) {
if (content.isEmpty())
return null;
if (!content.contains(":"))
return null;
String[] strArray = content.split(":");
return new ExchangeRateError(strArray[0], strArray[1]);
}
}
Anyone can help? why I autowired the Config bean in the job class, it will create null instance? thx.
And before I post this, I have read this in detail, Why is my Spring #Autowired field null?
but it didn't save my time.
Your code shows QuartzScheduler as an instance of ServletContextListener. This will only create a Java bean. Your Quartz Scheduler class must be a bean in a Spring applicationContext. Only then will the other dependencies be autowired. Refer this for example - http://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html#scheduling-quartz

How to send email asynchronously using spring 4 #Async

I know this question has been asked but I am not able to send email using configuration. I don't know what I am doing wrong and why I am not getting the email. Here is my Spring configuration.
#Configuration
#PropertySource(value = {
"classpath:autoalert.properties"
})
#EnableAsync
#Import({PersistenceConfig.class, EmailConfig.class, VelocityConfig.class})
#ComponentScan(basePackageClasses = {
ServiceMarker.class,
RepositoryMarker.class }
)
public class AutoAlertAppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Here is my email config
#Configuration
#PropertySources({
#PropertySource("classpath:email/email.properties")
})
public class EmailConfig {
#Autowired
private Environment env;
#Bean
public JavaMailSender mailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setSession(getEmailSession());
return mailSender;
}
#Bean
public MailMessage mailSettings() {
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setFrom(env.getProperty("mail.from"));
...
mailMessage.setText(env.getProperty("mail.body"));
return mailMessage;
}
private Session getEmailSession() {
Properties emailProperties = SpringUtil.loadPropertiesFileFromClassPath("email" + File.separator + "general-mail-settings.properties");
final String userName = emailProperties.getProperty("user");
final String password = emailProperties.getProperty("password");
Session session = null;
try {
session = Session.getInstance(emailProperties, new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, password);
}
}); //end of anonymous class
} catch (Exception e) {
e.printStackTrace();
}
return session;
} //end of getEmailSession()
}
Here is my class
public static void main(String[] args) {
try (GenericApplicationContext springContext = new AnnotationConfigApplicationContext(AutoAlertAppConfig.class)) {
AutoAlertService autoAlertService = springContext.getBean(AutoAlertServiceImpl.class);
try {
autoAlertService.handleProcess(fromDate, toDate);
} catch (Exception e) {
logger.error("Exception occurs", e);
autoAlertService.handleException(fromDate, toDate, e);
}
} catch (Exception e) {
logger.error("Exception occurs in loading Spring context: ", e);
}
#Service
public class AutoAlertServiceImpl implements AutoAlertService {
#Inject
private AsyncEmailService asyncEmailService;
#Override
public void handleProcess(String fromDate, String toDate) {
logger.info("Start process");
try {
..
//Sending email
asyncEmailService.sendMailWithFileAttachment(fromDate, toDate, file);
} catch (Exception e) {
handleException(fromDate, toDate, e);
}
logger.info("Finish process");
}
}
here is my email service
#Component
public class AsyncEmailServiceImpl implements AsyncEmailService {
#Resource(name="mailSender")
private JavaMailSender mailSender;
#Resource(name="mailSettings")
private SimpleMailMessage simpleMailMessage;
#Async
#Override
public void sendMailWithFileAttachment(String from, String to, String attachFile) {
logger.info("Start execution of async. Sending email with file attachment");
MimeMessage message = mailSender.createMimeMessage();
try{
MimeMessageHelper helper = new MimeMessageHelper(message, true);
....
helper.setText(String.format(simpleMailMessage.getText(), from, to));
FileSystemResource file = new FileSystemResource(attachFile);
helper.addAttachment(file.getFilename(), file);
mailSender.send(message);
} catch (MessagingException e) {
logger.error("Exception occurs in sending email with file attachment: " + attachFile, e);
throw new MailParseException(e);
}
logger.info("Complete execution of async. Email with file attachment " + attachFile + " send successfully.");
}
}
When I run the code it comes to method. This is printed on the console
13:59:43.004 [main] INFO com.softech.vu360.autoalert.service.impl.AutoAlertServiceImpl - Finish process
13:59:43.005 [SimpleAsyncTaskExecutor-1] INFO com.softech.vu360.autoalert.service.impl.AsyncEmailServiceImpl - Start execution of async. Sending email with file attachment
13:59:43.007 [main] INFO com.softech.vu360.autoalert.AutoAlert - Exiting application.
But I get no email. In case of Synchronous calling I get the email. Why I am not getting email ? Am I doing something wrong ?
Thanks
I think this is better approach. Create a file AsyncConfig.java
#Configuration
#EnableAsync(proxyTargetClass = true)
#EnableScheduling
public class AsyncConfig implements SchedulingConfigurer, AsyncConfigurer {
private static final Logger log = LogManager.getLogger();
private static final Logger schedulingLogger = LogManager.getLogger(log.getName() + ".[scheduling]");
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
log.info("Setting up thread pool task scheduler with 20 threads.");
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(2);
scheduler.setThreadNamePrefix("task-");
scheduler.setAwaitTerminationSeconds(1200); // 20 minutes
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setErrorHandler(t -> schedulingLogger.error("Unknown error occurred while executing task.", t));
scheduler.setRejectedExecutionHandler((r, e) -> schedulingLogger.error("Execution of task {} was rejected for unknown reasons.", r));
return scheduler;
}
#Override
public Executor getAsyncExecutor() {
Executor executor = this.taskScheduler();
log.info("Configuring asynchronous method executor {}.", executor);
return executor;
}
#Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
TaskScheduler scheduler = this.taskScheduler();
log.info("Configuring scheduled method executor {}.", scheduler);
registrar.setTaskScheduler(scheduler);
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
Now import it into your main configuration like this
#Configuration
#PropertySource(value = {
"classpath:autoalert.properties"
})
#Import({AsyncConfig.class, PersistenceConfig.class, EmailConfig.class, VelocityConfig.class})
#ComponentScan(basePackageClasses = {
ServiceMarker.class,
RepositoryMarker.class }
)
public class AutoAlertAppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Change return type from void to Future
#Service
public class AsyncEmailServiceImpl implements AsyncEmailService {
#Resource(name="mailSender")
private JavaMailSender mailSender;
#Resource(name="mailSettings")
private SimpleMailMessage simpleMailMessage;
#Async
#Override
public Future<String> sendMailWithFileAttachment(String from, String to, String attachFile) {
....
return new AsyncResult<String>("Attachment File successfully send: " + attachFile);
}
#Async
#Override
public Future<String> sendMail(String from, String to, String emailBody) {
....
return new AsyncResult<String>("Email send successfully");
}
}
and in my service class just do this
logger.info("Start process");
try {
....
//Sending email
Future<String> result = asyncEmailService.sendMailWithFileAttachment(fromDate, toDate, file);
} catch (Exception e) {
handleException(fromDate, toDate, e);
}
logger.info("Finish process");
See no need to check result.get(). Now when new Thread starts and application starts to finish. I configured the scheduler.setAwaitTerminationSeconds(1200); // 20 minutes in AsyncConfig.java. This will ensure that all the pending threads must complete before the application shuts down. Ofcourse this can be change according to any ones need.
Now when I run the application it prints these on the console
12:55:33.879 [main] INFO com.softech.vu360.autoalert.service.impl.AutoAlertServiceImpl - Finish process
12:55:33.895 [task-1] INFO com.softech.vu360.autoalert.service.impl.AsyncEmailServiceImpl - Start execution of async. Sending email with file attachment
12:58:09.030 [task-1] INFO com.softech.vu360.autoalert.service.impl.AsyncEmailServiceImpl - Complete execution of async. Email with file attachment D:\projects\AutoAlerts\marketo\autoalert 2015-08-24 to 2015-08-30.csv send successfully.
12:58:09.033 [main] INFO com.softech.vu360.autoalert.AutoAlert - Exiting application.
See starts new thread, but before application shuts down make sure, the thread completes and then exits the application. I configured 20 minutes for email, but as soon as thread completes, application get shut down. This is happy ending :)

Resources