Custom CharacterEscapeHandler not firing in spring boot application (Jaxb2Marshaller) - web-services

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);

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 cloud aws messaging sqs serialize localdatetime as string

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()));
}

How to debug a WCF call when it's a singleton and being run in a test

I am self-hosting a duplex-contract, WCF service.
In composing a test that exercises if my client is receiving messages from the service, I have found that I can't debug the service itself.
Thus, I made a simple example that seems to help me repeat the issue.
This is an example of the test I'm attempting:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
ServiceRunner.Run(null);
var client = new ServiceReference1.Service1Client();
var result = client.GetData(11);
Assert.IsNotNull(result);
ServiceRunner.Host.Close();
}
}
ServiceRunner will host the WCF contract in a singleton. The client is from a service reference that points to the self-hosted service. When I call GetData(11) I get a response, it's just that my breakpoint in the service is never hit.
Why is that?
Here's the implementation of the service for completeness:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
namespace CanYouDebugThis
{
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
[ServiceBehaviorAttribute(InstanceContextMode = InstanceContextMode.Single)]
public class Service1 : IService1
{
public string GetData(int value)
{
Console.WriteLine($"Get data with {value}");
return string.Format("You entered: {0}", value);
}
}
public class ServiceRunner
{
public static ServiceHost Host;
public static void Run(String[] args)
{
var serviceInstance = new Service1();
Uri baseAddress = new Uri("http://localhost:8080/hello");
Host = new ServiceHost(serviceInstance, baseAddress);
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
Host.Description.Behaviors.Add(smb);
Host.Open();
}
}
}
There is something wrong with hosting the service. We should add service endpoint and MEX endpoint for exchanging metadata. Please refer to the below code segments.
public static ServiceHost Host;
public static void Main(String[] args)
{
var serviceInstance = new Service1();
Uri baseAddress = new Uri("http://localhost:8080/hello");
BasicHttpBinding binding = new BasicHttpBinding();
//Host = new ServiceHost(serviceInstance, baseAddress);
Host = new ServiceHost(typeof(Service1), baseAddress);
Host.AddServiceEndpoint(typeof(IService1), binding, "");
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
Host.Description.Behaviors.Add(smb);
Binding mexbinding = MetadataExchangeBindings.CreateMexHttpBinding();
Host.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");
Host.Open();
Console.WriteLine("Service is ready...");
//pause, accepting a word would teminate the service.
Console.ReadLine();
Host.Close();
Console.WriteLine("Service is closed....");
}
Please host the service in an individual Console project first. Then on the client-side, we generate the client proxy by adding the service reference. Please pay attention to the auto-generated service endpoint, which should be corresponding to the actual server endpoint.
Result.
Feel free to let me know if there is anything I can help with.
Updated.
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
ServiceRunner.Run(null);
ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
var result = client.GetData(34);
Assert.IsNotNull(result);
}
}
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
[ServiceBehaviorAttribute(InstanceContextMode = InstanceContextMode.Single)]
public class Service1 : IService1
{
public string GetData(int value)
{
Console.WriteLine($"Get data with {value}");
return string.Format("You entered: {0}", value);
}
}
public class ServiceRunner
{
public static ServiceHost Host;
public static void Run(String[] args)
{
var serviceInstance = new Service1();
Uri baseAddress = new Uri("http://localhost:8080/hello");
Host = new ServiceHost(serviceInstance, baseAddress);
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
Host.Description.Behaviors.Add(smb);
Host.Open();
}
}
Result.
At last, please pay attention to the automatically generated client endpoint address.

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());
}
}`

Mockito - Unable to initialize Spy on HttpEntity

The code I'm testing works correctly, logs are right.
Tests in error:
ConnectorTest: Unable to initialize #Spy annotated field 'entity'.
Why can't I use verify on the http entity?
Is there a better way to test it? Should I spy on the logs otherwise?
CLASS TO BE TESTED:
public class Connector {
private static final String hostname = "localhost";
private int varnishPort = 8000;
private int connectionTimeout = 5000; //millis
private int requestTimeout = 5000;
private int socketTimeout = 5000;
private final HttpHost host;
private HttpClient httpClient;
private HttpEntity entity;
private HttpResponse response;
public Connector(){
host = new HttpHost(this.hostname, this.varnishPort);
RequestConfig.Builder requestBuilder = RequestConfig.custom();
requestBuilder = requestBuilder.setConnectTimeout(connectionTimeout);
requestBuilder = requestBuilder.setConnectionRequestTimeout(requestTimeout);
requestBuilder = requestBuilder.setSocketTimeout(socketTimeout);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setDefaultRequestConfig(requestBuilder.build());
httpClient = builder.build();
}
public void invalidateVarnishCache( String level, String idDb ) {
try{
String xPurgeRegex = level+"/"+idDb+"$";
Header header = new BasicHeader( "X-Purge-Regex", xPurgeRegex );
BasicHttpRequest purgeRequest = new BasicHttpRequest("PURGE", "/" );
purgeRequest.setHeader(header);
response = httpClient.execute(host, purgeRequest);
entity = response.getEntity();
int statusCode = response.getStatusLine().getStatusCode();
if( statusCode >= 300 ){
int respLength = entity.getContent().available();
byte[] errorResp = new byte[ respLength ];
entity.getContent().read(errorResp, 0, respLength);
// log error
}else{
// log success
}
EntityUtils.consume(entity);
}catch(Exception e){
// log exception
}
}
}
MY TEST:
#RunWith(MockitoJUnitRunner.class)
public class ConnectorTest {
#Mock
private HttpClient httpClient;
// #Spy
// private HttpEntity entity;
#InjectMocks
private Connector varnishPurger = new Connector();
#Test
public void purgeFail() throws Exception{
HttpResponse response500 = new BasicHttpResponse( new ProtocolVersion( "HTTP/1.1", 0, 0), 500, "NO!_TEST" );
HttpEntity entity500 = new StringEntity("BAD entity. [test]");
response500.setEntity(entity500);
doReturn( response500 )
.when( httpClient ).execute( isA(HttpHost.class), isA(BasicHttpRequest.class) );
varnishPurger.invalidateVarnishCache("level_test_500", "id_test_500");
// verify( entity, times( 1 ) ).getContent().available(); // HOW DO I MAKE THIS WORK?
}
...
}
HttpEntity is an interface, it cannot be spied, only mocked. So just change the annotation with #Mock, or another option is to declare an initialised instance :
#Spy HttpEntity entity = new BasicHttpEntity();
Anyway the exception message is rather clear on why this happens :
org.mockito.exceptions.base.MockitoException: Unable to initialize #Spy annotated field 'entity'.
Type 'HttpEntity' is an interface and it cannot be spied on.
at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl$1.withBefores(JUnit45AndHigherRunnerImpl.java:27)
at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:276)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: org.mockito.exceptions.base.MockitoException: Type 'HttpEntity' is an interface and it cannot be spied on.
... 21 more
This behaviour is aligned with Mockito.spy(Object), this method cannot be invoked if there's no instance.
However the recent Mockito.spy(Class) does not complain about that. This may be a functionality that wasn't ported to the annotation subsystem.
Nonetheless it is semantically wrong to spy on an interface as it doesn't have behaviour.
I solved in this way:
class MockHttpEntity implements HttpEntity{
String msg = "Test_";
InputStream inps;
int count = 0;
public MockHttpEntity(String msg){
this.msg += msg;
inps = IOUtils.toInputStream(this.msg);
}
#Override
public InputStream getContent(){
System.out.println("\n"+(++count)+") Mocked getContent() called.\n");
return inps;
}
public int times(){
return count;
}
#Override public void consumeContent(){ }
#Override public Header getContentEncoding(){ return null; }
#Override public long getContentLength(){ return 0; }
#Override public Header getContentType(){ return null;}
#Override public boolean isChunked(){ return false; }
#Override public boolean isRepeatable(){ return false; }
#Override public boolean isStreaming(){ return false; }
#Override public void writeTo(OutputStream outstream){ }
}
#RunWith(MockitoJUnitRunner.class)
public class ConnectorTest {
#Mock
private HttpClient httpClient;
#InjectMocks
private Connector varnishPurger = new Connector();
#Test
public void purgeFailureResponse() throws Exception{
MockHttpEntity mockedHttpEntity = new MockHttpEntity("bad entity");
HttpResponse response500 = new BasicHttpResponse( new ProtocolVersion( "HTTP/1.1", 0, 0), 500, "NO!_TEST" );
response500.setEntity( mockedHttpEntity );
doReturn( response500 )
.when( httpClient ).execute( isA(HttpHost.class), isA(BasicHttpRequest.class) );
varnishPurger.invalidateVarnishCache("level_test_no", "id_test_no");
Assert.assertTrue( mockedHttpEntity.times() == 2 );
}
...
}