Spring AMQP Synchronous Transaction Rollback - spring-amqp

Spring AMQP Synchronous Transaction rollback does not work. Here DB transactions within the source are not handled by Spring. I need to have the Spring AMQP messages to be received and send within one transaction. Following are the snapshot of relevant code. Please let me know if you require anything else.
/////Connection Factory initialization
#Bean
public ConnectionFactory getConnectionFactory() {
System.out.println("hello");
configManager();
String ip = ConfigManager.getQueueServerHost();
System.out.println("IP Address : "+ip);
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(ip);
connectionFactory.setUsername(ConfigManager.getQueueUserName());
connectionFactory.setPassword(ConfigManager.getQueuePassword());
connectionFactory.setPort(ConfigManager.getQueueServerPort());
//connectionFactory.setPublisherReturns(true);
//connectionFactory.setPublisherConfirms(true);
return connectionFactory;
}
/////Rabbit Template initialization
#Bean
public RabbitTemplate producerRabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(getConnectionFactory());
rabbitTemplate.setRoutingKey(ConfigManager.getProducerQueueName());
rabbitTemplate.setQueue(ConfigManager.getProducerQueueName());
rabbitTemplate.setMandatory(true);
rabbitTemplate.setChannelTransacted(true);
return rabbitTemplate;
}
/////Transactional Code
#Transactional(readOnly=false, rollbackFor=Exception.class)
public void processFile(RabbitTemplate rabbitTemplate)throws Exception{
rabbitTemplate.setRoutingKey(ConfigManager.getConsumerQueueName());
rabbitTemplate.setQueue(ConfigManager.getConsumerQueueName());
Object messageObj = rabbitTemplate.receiveAndConvert();
Message message = null;
try{
if(messageObj != null){
if (messageObj instanceof Message){
message = (Message)messageObj;
System.out.println("Message received is '" + message.getFileName() + "' for Hospital "+message.getHospitalId());
String newFileName = this.process(message.getFileName(), message.getHospitalId());
this.sendMessage(newFileName, message.getHospitalId());
}else{
System.out.println("Unknown message received '" + messageObj + "'");
}
}
}catch(Exception e){
e.printStackTrace();
throw e;
}
}

It works fine for me; I just uploaded a Gist with my test case that shows it working.
I suggest you turn on TRACE level logging to see all the transaction activity (and compare it with the log that I put in the Gist).

Related

Azure Cosmos: Register Stored Procedure If not exist already

I want to register and execute stored proc. I am using spring+Java with cosmos DB. Everytime I stop my application and restart it , it tried to create new sproc and since it already exists in cosmos DB it fails with below error . Is their any option available like "only create if not exist". I am fetching js file from src/main/resources folder.
I am following below doc to register the stored proc
https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/how-to-use-stored-procedures-triggers-udfs?tabs=java-sdk
#Configuration
public class StoredProcConfig
{
#Autowired
#Qualifier(BeansConstants.PAYMENT_CONTAINER)
CosmosContainer container;
#Bean
public CosmosStoredProcedureResponse registerSp() throws IOException
{
InputStream is = getFileFromResourceAsStream("storedProcedures/createStudent.js");
CosmosStoredProcedureProperties definition = new CosmosStoredProcedureProperties("spCreateToDoItems",
IOUtils.toString(is, StandardCharsets.UTF_8));
return container.getScripts().createStoredProcedure(definition);
}
private InputStream getFileFromResourceAsStream(String fileName)
{
// The class loader that loaded the class
ClassLoader classLoader = getClass().getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream(fileName);
// the stream holding the file content
if (inputStream == null)
{
throw new IllegalArgumentException("file not found! " + fileName);
} else
{
return inputStream;
}
}
}
Error
Caused by: com.azure.cosmos.CosmosException: {"innerErrorMessage":"Message: {\"Errors\":[\"Resource with specified id, name, or unique index already exists.\"]}
Modify your registerSp() bean as below:
private static final Logger logger = LoggerFactory.getLogger(CosmosConfiguration.class);
#Bean
public CosmosStoredProcedureResponse registerSp() throws IOException
{
InputStream is = getFileFromResourceAsStream("storedProcedures/createStudent.js");
CosmosStoredProcedureProperties definition = new CosmosStoredProcedureProperties("spCreateToDoItems",
IOUtils.toString(is, StandardCharsets.UTF_8));
return createStoredProcedureIfNotExists(definition);
}
public CosmosStoredProcedureResponse createStoredProcedureIfNotExists(CosmosStoredProcedureProperties definition){
try {
CosmosStoredProcedureResponse storedProc = container.getScripts().getStoredProcedure(definition.getId()).read();
logger.info("found stored proc");
return storedProc;
}
catch (CosmosException e){
logger.info("stored proc not found, creating....");
return container.getScripts().createStoredProcedure(definition);
}
}

Events stuck durin processing in Java app +Srping Amqp

After addded concurrrency to the #RabbitListened faced with problem that sometimes events are stuck in app.
When restarting app it's continue works normally. But then could aslo stuck suddenly after sometime.
#RabbitListener(
queues = "${app.queue}",
ackMode = "MANUAL",
concurrency = 2-5,
messageConverter = "jsonMessageConverter")
public void consumeEvent(AppEvent event, Channel channel, #Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
try {
.....
doAck(channel, deliveryTag);
} catch (Throwable e) {
.....
doNack(channel, deliveryTag);
}
}
#Bean
public PooledChannelConnectionFactory connectionFactory(){
ConnectionFactory rabbitConnectionFactory = new ConnectionFactory();
rabbitConnectionFactory.setHost(host);
rabbitConnectionFactory.setPort(port);
rabbitConnectionFactory.setUsername(userName);
rabbitConnectionFactory.setPassword(password);
return new PooledChannelConnectionFactory(rabbitConnectionFactory);
}
#Bean
public RabbitTemplate rabbitTemplate(){
final var rabbitTemplate = new RabbitTemplate(connectionFactory());
rabbitTemplate.setMessageConverter(getJsonMessageConverter());
return rabbitTemplate;
}
#Bean("jsonMessageConverter")
public Jackson2JsonMessageConverter getJsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
In thread dump there are 3 threads like on picture

Manually set authentication with ReactiveSecurityContextHolder

I'm trying to setup Spring Security with Spring Web Flux. I don't understand how to manually set the SecurityContext with ReactiveSecurityContextHolder. Do you have any resource or hint?
Take for example this filter I've written that reads a JWT token and needs to set the authentication manually:
#Slf4j
public class JwtTokenAuthenticationFilter implements WebFilter {
private final JwtAuthenticationConfig config;
private final JwtParser jwtParser = Jwts.parser();
public JwtTokenAuthenticationFilter(JwtAuthenticationConfig config) {
this.config = config;
jwtParser.setSigningKey(config.getSecret().getBytes());
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst(config.getHeader());
if (token != null && token.startsWith(config.getPrefix() + " ")) {
token = token.replace(config.getPrefix() + " ", "");
try {
Claims claims = jwtParser.parseClaimsJws(token).getBody();
String username = claims.getSubject();
#SuppressWarnings("unchecked")
List<String> authorities = claims.get("authorities", List.class);
if (username != null) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null,
authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
// TODO set authentication into ReactiveSecurityContextHolder
}
} catch (Exception ex) {
log.warn(ex.toString(), ex);
ReactiveSecurityContextHolder.clearContext();
}
}
return chain.filter(exchange);
}
}
I managed to update the SecurityContext by calling:
return chain.filter(exchange).subscriberContext(ReactiveSecurityContextHolder.withAuthentication(auth));
Correct me if I'm wrong or if there is a better way to manage it.
I searched a lot about this issue and get this thing worked.
You can try setting the context while passing the filter chain like below.
return chain.filter(exchange).contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));

SimpleRabbitListenerContainerFactory and defaultRequeueRejected

As per doc, defaultRequeueRejected's default value is true, but looking at code it seems its false. I am not sure if I am missing anything or we have to change that in SimpleRabbitListenerContainerFactory.java
EDIT
Sample code, after putting message in test queue, I expect it to stay in queue since its failing but it is throwing it out. I want message to be retried so I configured that in container factory if it fails after retry I want it to be back in queue. I am sure I am missing understanding here.
#SpringBootApplication
public class MsgRequeExampleApplication {
public static void main(String[] args) {
SpringApplication.run(MsgRequeExampleApplication.class, args);
}
#Bean(name = "myContainerFactory")
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
factory.setMissingQueuesFatal(false);
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(500);
factory.setAdviceChain(new Advice[] { org.springframework.amqp.rabbit.config.RetryInterceptorBuilder.stateless()
.maxAttempts(2).backOffPolicy(backOffPolicy).build() });
return factory;
}
#RabbitListener(queues = "test", containerFactory = "myContainerFactory")
public void processAdvisory(Message message) throws MyBusinessException {
try{
//Simulating exception while processing message
String nullString=null;
nullString.length();
}catch(Exception ex){
throw new MyBusinessException(ex.getMessage());
}
}
public class MyBusinessException extends Exception {
public MyBusinessException(String msg) {
super(msg);
}
}
}
There is a good description in the SimpleMessageListenerContainer JavaDocs:
/**
* Set the default behavior when a message is rejected, for example because the listener
* threw an exception. When true, messages will be requeued, when false, they will not. For
* versions of Rabbit that support dead-lettering, the message must not be requeued in order
* to be sent to the dead letter exchange. Setting to false causes all rejections to not
* be requeued. When true, the default can be overridden by the listener throwing an
* {#link AmqpRejectAndDontRequeueException}. Default true.
* #param defaultRequeueRejected true to reject by default.
*/
public void setDefaultRequeueRejected(boolean defaultRequeueRejected) {
this.defaultRequeueRejected = defaultRequeueRejected;
}
Does it make sense to you?
UPDATE
To requeue after retry exhausting you need to configure some custom MessageRecoverer on the RetryInterceptorBuilder with the code like:
.recoverer((message, cause) -> {
ReflectionUtils.rethrowRuntimeException(cause);
})
This way the exception will be thrown to the listener container and according its defaultRequeueRejected the message will be requeued or not.

Tried to read incoming SMS content but getting Error in Blackberry

Hi friends i am trying to read incoming sms but getting warning like this . Invocation of questionable method: java.lang.String.(String) found in: mypackage.MyApp$ListeningThread.run()
Here is my code is
public class MyApp extends UiApplication {
//private ListeningThread listener;
public static void main(String[] args) {
MyApp theApp = new MyApp();
theApp.enterEventDispatcher();
}
public MyApp() {
invokeAndWait(new Runnable() {
public void run() {
ListeningThread listener = new ListeningThread();
listener.start();
}
});
pushScreen(new MyScreen());
}
private static class ListeningThread extends Thread {
private boolean _stop = false;
private DatagramConnection _dc;
public synchronized void stop() {
_stop = true;
try {
_dc.close(); // Close the connection so the thread returns.
} catch (IOException e) {
System.err.println(e.toString());
}
}
public void run() {
try {
_dc = (DatagramConnection) Connector.open("sms://");
for (;;) {
if (_stop) {
return;
}
Datagram d = _dc.newDatagram(_dc.getMaximumLength());
_dc.receive(d);
String address = new String(d.getAddress());
String msg = new String(d.getData());
if(msg.startsWith("START")){
Dialog.alert("hello");
}
System.out.println("Message received: " + msg);
System.out.println("From: " + address);
System.exit(0);
}
} catch (IOException e) {
System.err.println(e.toString());
}
}
}
}
Please correct me where i am wrong.Is possible give me some code to read incoming sms content in blackberry.
A few points about your code:
That invokeAndWait call to launch a thread makes no sense. It doesn't harm, but is kind of waste. Use that method only to perform UI related operations.
You should try using "sms://:0" as param for Connector.open. According to the docs, a parameter with the form {protocol}://[{host}]:[{port}] will open the connection in client mode (which makes sense, since you are on the receiving part), whereas not including the host part will open it in server mode.
Finally, if you can't get it working, you could use instead the third method specified in this tutorial, which you probably have already read.
The error you quoted is complaining about the use of the String constructor that takes a string argument. Since strings are immutable in Java-ME, this is just a waste. You can use the argument string directly:
Invocation of questionable method: java.lang.String.(String) found in: mypackage.MyApp$ListeningThread.run()
//String address = new String(d.getAddress());
String address = d.getAddress();
// getData() returns a byte[], so this is a different constructor
// However, this leaves the character encoding unspecified, so it
// will default to cp1252, which may not be what you want
String msg = new String(d.getData());

Resources