spring cloud aws messaging sqs serialize localdatetime as string - amazon-web-services

I am using spring cloud starter aws messaging
implementation 'io.awspring.cloud:spring-cloud-starter-aws'
implementation 'io.awspring.cloud:spring-cloud-starter-aws-messaging'
version
set('springCloudAwsVersion', '2.3.1')
dependencyManagement {
imports {
mavenBom "io.awspring.cloud:spring-cloud-aws-dependencies:${springCloudAwsVersion}"
}
}
my config beans
#Bean
public QueueMessagingTemplate queueMessagingTemplate(final AmazonSQSAsync amazonSqs) {
return new QueueMessagingTemplate(amazonSqs);
}
#Bean
public AmazonSQSAsync amazonSqs() {
return AmazonSQSAsyncClientBuilder.standard()
.build();
}
#Bean
public ObjectMapper messageConverter() {
return new ObjectMapper().findAndRegisterModules();
}
#Bean
public MappingJackson2MessageConverter mappingJackson2MessageConverter(final ObjectMapper objectMapper) {
final var jacksonMessageConverter = new MappingJackson2MessageConverter();
jacksonMessageConverter.setObjectMapper(objectMapper);
jacksonMessageConverter.setSerializedPayloadClass(String.class);
jacksonMessageConverter.setStrictContentTypeMatch(false);
return jacksonMessageConverter;
}
#Bean
public QueueMessageHandlerFactory queueMessageHandlerFactory(final MappingJackson2MessageConverter messageConverter) {
final var factory = new QueueMessageHandlerFactory();
factory.setArgumentResolvers(List.of(new PayloadMethodArgumentResolver(messageConverter)));
return factory;
}
#Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory(final AmazonSQSAsync amazonSqs) {
final var factory = new SimpleMessageListenerContainerFactory();
factory.setAmazonSqs(amazonSqs);
factory.setMaxNumberOfMessages(1);
return factory;
}
problem when I #inject QueueMessagingTemplate and use method sendAndConvert all java8 time field of my POJO converted as with separate fields minute hours etc ... object not string.
So consequence when I try to deserialize my object mapper throw exception
I try to register QueueMessagingTemplate with GroupIdResolver(as null) and same mapping2jacksonConverter but result that asyncClient Stop connecting to queue

In my case the problem solved with creating FIFO queue with groupId and deduplicationId
also added map2jackson into queuTemplate
#Bean
public QueueMessagingTemplate queueMessagingTemplate(final AmazonSQSAsync amazonSqs) {
return new QueueMessagingTemplate(amazonSqs, (ResourceIdResolver) null, mappingJackson2MessageConverter(messageConverter()));
}

Related

SpringCloud AWS - SQSListener annotated method not receiving messages

I am writing an SQS publisher/consumer application using Spring Cloud AWS 2.3.2
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-messaging</artifactId>
<version>2.3.2</version>
</dependency>
I have gotten to the point where I can successfully publish msgs to my SQS, but my #SqsListener annotated method does not consume the msgs. I looked at other Q&A here but none seemed to provide any proper insight to solve this issue.
I am following the API docs here: https://docs.awspring.io/spring-cloud-aws/docs/current/reference/html/index.html#annotation-driven-listener-endpoints
I have my configuration defined as below:
#Configuration
public class SqsMessagingConfig {
#Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
#Value("${cloud.aws.credentials.access-key}")
private String accessKey;
private AWSCredentialsProvider awsCredentialsProvider() {
return new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey,
secretKey));
}
#Bean
#Primary
public AmazonSQSAsync amazonSQSAsync() {
return AmazonSQSAsyncClientBuilder
.standard()
.withRegion("us-east-2")
.withCredentials(awsCredentialsProvider())
.build();
}
#Bean
public QueueMessagingTemplate queueMessagingTemplate(AmazonSQSAsync amazonSQSAsync) {
return new QueueMessagingTemplate(amazonSQSAsync);
}
#Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory(AmazonSQSAsync amazonSQSAsync) {
SimpleMessageListenerContainerFactory factory = new SimpleMessageListenerContainerFactory();
factory.setAmazonSqs(amazonSQSAsync);
factory.setAutoStartup(true);
factory.setMaxNumberOfMessages(10);
return factory;
}
#Bean()
public QueueMessageHandlerFactory queueMessageHandlerFactory(final ObjectMapper mapper, final AmazonSQSAsync amazonSQSAsync) {
final QueueMessageHandlerFactory queueHandlerFactory = new QueueMessageHandlerFactory();
queueHandlerFactory.setAmazonSqs(amazonSQSAsync);
queueHandlerFactory.setArgumentResolvers(Collections.singletonList(new PayloadMethodArgumentResolver(jackson2MessageConverter(mapper))));
return queueHandlerFactory;
}
private MessageConverter jackson2MessageConverter(final ObjectMapper mapper) {
final MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setObjectMapper(mapper);
return converter;
}
}
And then my SqsService looks like the following:
#Service
public class SqsQueueService {
private static final Logger logger = LoggerFactory.getLogger(SqsQueueService.class);
private final QueueMessagingTemplate queueMessagingTemplate;
private final ObjectWriter objectWriter;
private final String QUEUE_NAME = "SCHEDULES";
public SqsQueueService(QueueMessagingTemplate queueMessagingTemplate, ObjectMapper mapper) {
this.queueMessagingTemplate = queueMessagingTemplate;
this.objectWriter = mapper.writer();
}
public void send(List<Schedule> schedules) {
List<String> originatingIds = schedules.stream().map(Schedule::getOriginatingId).collect(Collectors.toList());
try {
Message<String> message = MessageBuilder.withPayload(objectWriter.writeValueAsString(schedules))
.build();
this.queueMessagingTemplate.convertAndSend(QUEUE_NAME, message);
logger.info("Successfully sent {} schedule(s) to SQS, with originatingId={}", schedules.size(),
originatingIds);
} catch (Exception e) {
logger.error("Failed to send the following schedule(s) to SQS=" + originatingIds, e);
}
}
// NO_REDRIVE ensures we do not re-queue messages forever. They will be sent to a DLQ if they exceed maxReceiveCount
#SqsListener(value = "SCHEDULES", deletionPolicy = SqsMessageDeletionPolicy.NO_REDRIVE)
private void receiveMessage(List<Schedule> schedules) String partnerId) {
List<String> originatingIds = schedules.stream().map(Schedule::getOriginatingId).collect(Collectors.toList());
logger.info("Received request from SQS for originatingId={}", originatingIds);
try {
someService.createSchedules(schedules);
} catch (Exception e) {
throw new RuntimeException("An issue occurred during ingest for originatingId=" + originatingIds, e);
}
}
}
I also tried the aws-autoconfigured dependency, but that added ton of extra noise and I still was not able to get it to consume from SQS. Hoping someone can spot where I am messing up/missing something. The docs that I saw as reference directly from spring devs point to me doing the right thing, but obviously, that is not the case.
After I send the message to the queue, I can see it waiting to be consumed but nothing happens. Any help is greatly appreciated.
Add the spring cloud aws autoconfigure dependency:
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-autoconfigure</artifactId>
</dependency>
https://docs.awspring.io/spring-cloud-aws/docs/current/reference/html/index.html#maven-dependencies

Spring-Webflux: Handler function unit test is throwing UnsupportedMediaTypeStatusException

I am trying to write Unit test to the handler function, I followed the example from the Spring project. Can someone help me why the following test is throwing UnsupportedMediaTypeStatusException?
Thanks
Handler function
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
log.info("{} Processing create request", serverRequest.exchange().getLogPrefix());
return ok().body(serverRequest.bodyToMono(Person.class).map(p -> p.toBuilder().id(UUID.randomUUID().toString()).build()), Person.class);
}
Test Class
#SpringBootTest
#RunWith(SpringRunner.class)
public class MyHandlerTest {
#Autowired
private MyHandler myHandler;
private ServerResponse.Context context;
#Before
public void createContext() {
HandlerStrategies strategies = HandlerStrategies.withDefaults();
context = new ServerResponse.Context() {
#Override
public List<HttpMessageWriter<?>> messageWriters() {
return strategies.messageWriters();
}
#Override
public List<ViewResolver> viewResolvers() {
return strategies.viewResolvers();
}
};
}
#Test
public void handle() {
Gson gson = new Gson();
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.post("/api/create")
.body(gson.toJson(Person.builder().firstName("Jon").lastName("Doe").build())));
MockServerHttpResponse mockResponse = exchange.getResponse();
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
Mono<ServerResponse> serverResponseMono = myHandler.handle(serverRequest);
Mono<Void> voidMono = serverResponseMono.flatMap(response -> {
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
boolean condition = response instanceof EntityResponse;
assertThat(condition).isTrue();
return response.writeTo(exchange, context);
});
StepVerifier.create(voidMono)
.expectComplete().verify();
StepVerifier.create(mockResponse.getBody())
.consumeNextWith(a -> System.out.println(a))
.expectComplete().verify();
assertThat(mockResponse.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
}
}
Error Message:
java.lang.AssertionError: expectation "expectComplete" failed (expected: onComplete(); actual: onError(org.springframework.web.server.UnsupportedMediaTypeStatusException: 415 UNSUPPORTED_MEDIA_TYPE "Content type 'application/octet-stream' not supported for bodyType=com.example.demo.Person"))
I found that I missed .contentType(MediaType.APPLICATION_JSON) to my mock request.
MockServerWebExchange.from(
MockServerHttpRequest.post("/api/create").contentType(MediaType.APPLICATION_JSON)
.body(gson.toJson(Person.builder().firstName("Jon").lastName("Doe").build())));
fixed my issue.

How to write Unit test for ViewModel that contains RxJava/RxAndroid

I'm trying to refactor one pretty old project, so I started implementing new architecture (MVVM) with Dagger2, RxJava, RxAndroid... Now everything is connected and working fine, now the problem is, I have no idea how to write a Unit test for my ViewModel..
I want to start with Login screen first, so I created a LoginViewModel, but first let me show you what I did..
I have a DataModule that provides 2 classes, RestApiRepository and ViewModelFactory. RestApiRepository looks like this:
public class RestApiRepository {
private RestClient restClient;
public RestApiRepository(RestClient restClient) {
this.restClient = restClient;
}
public Observable<AuthResponseEntity> authenticate(String header, AuthRequestEntity requestEntity) {
return restClient.postAuthObservable(header, requestEntity);
}
}
Rest client with api call for login:
public interface RestClient {
#POST(AUTH_URL)
Observable<AuthResponseEntity> postAuthObservable(#Header("Authorization") String authKey, #Body AuthRequestEntity requestEntity);
}
Second class from DataModule is ViewModelFactory:
#Singleton
public class ViewModelFactory extends ViewModelProvider.NewInstanceFactory implements ViewModelProvider.Factory {
private RestApiRepository repository;
#Inject
public ViewModelFactory(RestApiRepository repository) {
this.repository = repository;
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(LoginViewModel.class)) {
return (T) new LoginViewModel(repository);
}
throw new IllegalArgumentException("Unknown class name");
}
}
And finally, LoginViewModel:
public class LoginViewModel extends ViewModel {
private final CompositeDisposable disposable = new CompositeDisposable();
private final MutableLiveData<AuthResponseEntity> responseLiveData = new MutableLiveData<>();
private RestApiRepository restApiRepository;
private SchedulerProvider provider;
public LoginViewModel(RestApiRepository restApiRepository, SchedulerProvider provider) {
this.restApiRepository = restApiRepository;
this.provider = provider;
}
public MutableLiveData<AuthResponseEntity> getResponseLiveData() {
return responseLiveData;
}
#Override
protected void onCleared() {
disposable.clear();
}
public void auth(String token, AuthRequestEntity requestEntity) {
if (token != null && requestEntity != null) {
disposable.add(restApiRepository.authenticate(token, requestEntity)
.subscribeOn(provider.io())
.observeOn(provider.ui())
.subscribeWith(new DisposableObserver<AuthResponseEntity>() {
#Override
public void onNext(AuthResponseEntity authResponseEntity) {
responseLiveData.setValue(authResponseEntity);
}
#Override
public void onError(Throwable e) {
AuthResponseEntity authResponseEntity = new AuthResponseEntity();
authResponseEntity.setErrorMessage(e.getMessage());
responseLiveData.setValue(authResponseEntity);
}
#Override
public void onComplete() {
}
}
));
}
}
}
So, I'm sure everything is connected well, I can successfuly login...
For the RxAndroid test issues, I found somewhere that I have to use this Scheduler provider like this:
public class AppSchedulerProvider implements SchedulerProvider {
public AppSchedulerProvider() {
}
#Override
public Scheduler computation() {
return Schedulers.trampoline();
}
#Override
public Scheduler io() {
return Schedulers.trampoline();
}
#Override
public Scheduler ui() {
return Schedulers.trampoline();
}
}
Below is my LoginViewModelTest class, but I don't know how to handle RxJava/RxAndroid inside the tests..
#RunWith(MockitoJUnitRunner.class)
public class LoginViewModelTest {
#Mock
private RestApiRepository restApiRepository;
#Mock
private MutableLiveData<AuthResponseEntity> mutableLiveData;
private LoginViewModel loginViewModel;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
AppSchedulerProvider schedulerProvider = new AppSchedulerProvider();
loginViewModel = Mockito.spy(new LoginViewModel(restApiRepository, schedulerProvider));
}
#Test
public void authenticate_error() {
String token = "token";
AuthRequestEntity requestEntity = Mockito.mock(AuthRequestEntity.class);
Mockito.doReturn(Observable.error(new Throwable())).when(restApiRepository).authenticate(token, requestEntity);
loginViewModel.auth(token, requestEntity);
AuthResponseEntity responseEntity = Mockito.mock(AuthResponseEntity.class);
responseEntity.setErrorMessage("Error");
Mockito.verify(mutableLiveData).setValue(responseEntity);
}
}
So, I wanted to write a test for failed case when onError is called, but when I run it, I get this error:
exclude patterns:io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
You can mock the behaviour of restApiRepository:
Mockito.when(restApiRepository.authenticate(token, requestEntity)).thenReturn(Observable.error(error));
and verify that responseLiveData.setValue is being called with the appropriate parameters

Custom CharacterEscapeHandler not firing in spring boot application (Jaxb2Marshaller)

I am trying to configure the marshaller so that it does not convert '<' to '& lt;' et.
I have implemented a custom CharacterEscapeHandler but it does not seem to fire when i make a request.
I have found some similar threads but unfortunately there is no valid solution. I even read that this is a spring bug, i am hopping it is not.
My code is
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
Map<String, Object> props = new HashMap<>();
props.put("com.sun.xml.bind.marshaller.CharacterEscapeHandler",
new CharacterEscapeHandler() {
#Override
public void escape(char[] ac, int i, int j, boolean flag,
Writer writer) throws IOException {
System.out.println("I AM HERE");
writer.write(ac, i, j);
}
});
marshaller.setMarshallerProperties(props);
marshaller.setContextPaths(path);
return marshaller;
}
#Bean
public SOAPClientConnector soapConnector(Jaxb2Marshaller marshaller) {
SOAPClientConnector client = new SOAPClientConnector();
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
And i use it like this.
getWebServiceTemplate().marshalSendAndReceive(url, request);

Spring boot How to access Appconfig properties in test case

I am new to web services and spring boot. I have written a service for which I am now writing a test case.
My application gets Soap request, parses the body and saves contents into database.
My test case tests this service.
When I run the application and send a request from Postman, it runs alright. But when I call my service method from test case, I get nullpointer for JaxBcontext.
I have declared Jaxbcontext in my AppConfig.java (which is annotated with #Configuration and my jaxb is a bean with #Bean annotation) in my service, I have #autowire to use jaxbcontext.
I have pasted code snippets for clarity. Please advise me what I am doing wrongly here.
My test case
public class ReferralExchangeEndpointTest {
ReferralExchangeEndpoint referralExchangeEndpoint = new ReferralExchangeEndpoint();
JAXBContext jbcTest;
Marshaller marshaller;
Unmarshaller unmarshaller;
public ReferralExchangeEndpointTest() throws JAXBException {
}
#Before
public void setUp() throws Exception {
jbcTest = JAXBContext.newInstance(
"our app schema"); // this is working fine, I have replaced schema with this text for posting it in stack.
ObjectFactory factory = new ObjectFactory();
marshaller = jbcTest.createMarshaller();
unmarshaller = jbcTest.createUnmarshaller();
}
#Test
public void send() throws Exception {
File payload = new File("payload.xml");
Object x = unmarshaller.unmarshal(payload);
JAXBElement jbe = (JAXBElement) x;
System.out.println(jbe.getName());
Object test = jbe.getValue();
SendRequestMessage sendRequestMessage = (SendRequestMessage) jbe.getValue();
// Method in test.
referralExchangeEndpoint.send(sendRequestMessage);
}
}
My service class
#Endpoint
public class ReferralExchangeEndpoint {
public static final Logger logger = LoggerFactory.getLogger(ReferralExchangeEndpoint.class);
#Autowired
private JAXBContext jaxbContext;
#Autowired
.
.
.
private Form parseBody(String payLoadBody) {
try {
Unmarshaller um = jaxbContext.createUnmarshaller();
return (Form) um.unmarshal(new StringReader(payLoadBody));
} catch (Exception e) {
throw new RuntimeException("Failed to extract the form from the payload body", e);
}
}
My appconfig file
#Configuration
public class AppConfig {
#Bean
public JAXBContext jaxbContext() throws JAXBException {
return
JAXBContext.newInstance("packagename");
}
#Bean public MessagingService messagingService() {
return new MessagingService();
}
}
Thanks.
Kavitha.
** Solved **
My test case now looks like this.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {AppConfig.class})`
public class ReferralExchangeEndpointTest {
#Autowired
ReferralExchangeEndpoint referralExchangeEndpoint;
#Autowired
private JAXBContext jaxbContext;
private Marshaller marshaller;
private Unmarshaller unmarshaller;
#Before
public void setUp() throws Exception {
marshaller = jaxbContext.createMarshaller();
unmarshaller = jaxbContext.createUnmarshaller();
}
#Test
public void send() throws Exception {
File payload = new File("src/test/resources/payload.xml");
JAXBElement jbe = (JAXBElement) unmarshaller.unmarshal(payload);
SendRequestMessage sendRequestMessage = (SendRequestMessage) jbe.getValue();
JAXBElement<SendResponseMessage> response = referralExchangeEndpoint.send(sendRequestMessage);
//TODO add remaining assertions on response after confirming what should the service return for these attributes.
assertEquals("SiteId wrong in response: ", "siteId", response.getValue().getSiteId());
}
}`