SPRING CLOUD: RestTemplate postForObject cannot send request body - spring-boot

This is my consumer request function:
#PostMapping(value = "/spuSkuInfo")
public DataResponseDto<Map<String, Object>> addSpuAndSkuInfo(#Valid SpuSkuInfoBo spuSkuInfoBo) {
String url = REST_URL_PREFIX + "addSpuAndSkuInfo";
DataResponseDto<Map<String, Object>> dataResponseDto;
MultiValueMap<String,SpuSkuInfoBo> reqMap = new LinkedMultiValueMap<>();
reqMap.add("spuSkuInfoBo",spuSkuInfoBo);
HttpEntity<MultiValueMap<String,SpuSkuInfoBo>> httpEntity = new HttpEntity<>(reqMap);
dataResponseDto = restTemplate.postForObject(url,httpEntity,DataResponseDto.class);
}
This is my provider:
#PostMapping(value = "/addSpuAndSkuInfo")
public DataResponseDto<Map<String, Object>> addSpuAndSkuInfo(SpuSkuInfoBo spuSkuInfoBo) {
DataResponseDto<Map<String, Object>> responseDto;
try {
responseDto = spuSkuService.addSpuAndSkuInfo(spuSkuInfoBo);
} catch (Exception e) {
logger.error("addSpuAndSkuInfo", e);
responseDto = new DataResponseDto<>();
responseDto.setRspCd(PrdErrorCode.PRD_FAIL_CD);
responseDto.setRspInf("");
return responseDto;
}
return responseDto;
}
In consumer I can got the Pojo 'SpuSkuInfoBo'
And When I debugger in provider, I just can not receive the request body 'SpuSkuInfoBo'

Finally I solved the problem by adding #RequstBody in provider function
public DataResponseDto<Map<String, Object>> addSpuAndSkuInfo(#RequestBody SpuSkuInfoBo spuSkuInfoBo)

Related

Handling exception from webservices called by OpenFeign

I have several microservices and I use openfeign to call the different micro services.
the entry point for the global application is named dispatcher-ws. His role is to dispatch calls depending on the payload.
As entry I do have the following payload:
{
"operation": "signature",
"clientId": "abcdef",
...
"pdfDocument": "JVBERi0xLjMNCiXi48/TDQoNCjEg..."
}
I have microservice named signature-ws that handles pdf signature. So far, so good. I implemented my client this way:
#FeignClient(name="signature-ws", decode404 = true, url = "http://localhost:8080/signature-ws/api")
public interface SignatureClient {
#PostMapping("/signature")
Map<String, Object> signDocument(RequestDto request) throws AppServiceException;
}
In my service layer, I try to make the call depending on operation value:
#Service
public class RequestServiceImpl implements DispatchService {
private final RequestRepository requestRepository;
private final SignatureClient signatureClient;
private final Resilience4JCircuitBreakerFactory circuitBreakerFactory;
#Autowired
public DispatchServiceImpl(RequestRepository requestRepository,
SignatureClient signatureClient,
Resilience4JCircuitBreakerFactory circuitBreakerFactory) {
this.requestRepository = requestRepository;
this.signatureClient = signatureClient;
this.circuitBreakerFactory = circuitBreakerFactory;
}
#Override
public RequestDto handleRequest(RequestDto request) {
RequestDto returnValue = new RequestDto();
// if not initialized, throw null pointer exception...
returnValue.setPayloads(new ArrayList<>());
if(request.getOperation().equals("signature") {
Resilience4JCircuitBreaker circuitBreaker = circuitBreakerFactory.create("signature");
Supplier<Map<String, Object>> signatureResponseSupplier =
() -> signatureClient.signDocument(request);
Map<String, Object> signatureResponse = circuitBreaker.run(
signatureResponseSupplier,
throwable -> handleException()
);
...
returnValue.getResponses().add(signatureResponse)
}
return retunValue;
}
...
private Map<String, Object> handleException() {
Map<String, Object> returnValue = new HashMap<>();
returnValue.put("Error", "Error rmessage ... ");
returnValue.put("status", "Failure");
return returnValue;
}
If i don't pass pdfDocument in signature webservice, I do retrieve an exception.
{
"errorId": "Qe99DwntFrMPCAfuZfDQW1ucwNh5BK",
"status": "ERROR",
"operations": "signature",
"profile": "client123456",
"errorMessage": "PDF is missing",
"createdAt": 1647354022127
}
I would like to retrieve the exception response and pass the key values to the map in the handleException method. At this stage, it doesn't return anything and worse of all, i do return a 200 status.
I implemented a controllerAdvice that manage the response to return. This class is identical in all my web services(i should think about creating a micro service for handling all exceptions...)
#ControllerAdvice(basePackages = { "com.company.app" })
public class AppExceptionsHandler {
private final RequestContext requestContext;
#Autowired
public AppExceptionsHandler(RequestContext requestContext) {
this.requestContext = requestContext;
}
#ExceptionHandler(value = {AppServiceException.class})
public ResponseEntity<Object> handleAppException(AppServiceException ex,
WebRequest request) {
// récupérer le body
DispatchDto response = requestContext.getResponse();
ErrorMessage errorMessage = ErrorMessage.builder()
.errorId(response.getId())
.status(RequestOperationStatus.ERROR.name())
.operations(response.getOperations())
.profile(response.getProfile())
.errorMessage(ex.getMessage())
.createdAt(new Date())
.build();
}
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
What i expect is to return the same exception in my dispatcher microservice.
I found a trick to solve this issue.
First I surrounded with a try catch my feign request :
try {
...
Map<String, Object> facturxResponse =
facturXClient.createFacturX(dispatchDto);
...
} catch(FeignException e) {
System.out.println(e.getMessage());
throw new AppServiceException(e.getMessage());
}
I noted that e.getMessage returns a string which has this pattern:
[500 Internal Server Error] during [POST] to [http://localhost:8080/my-ws/api/ws]
[FacturXClient#createFacturX(DispatchDto)]: [{"errorId":"z3o1bE8SJrm8WGrxlpIIWe6TNf0NzR","status":"ERROR","operations":"facturx","profile":"client123456","errorMessage":"PDF is missing","createdAt":1647422337344}]
I throw this exception and intercept the response
#ExceptionHandler(value = {AppServiceException.class})
public ResponseEntity<Object> handleUserServiceException(AppServiceException ex,
WebRequest request) throws JsonProcessingException {
String input = ex.getMessage();
String[] splitResponse = input.split(":", 4);
ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
String response = splitResponse[3].trim().substring(1, splitResponse[3].trim().length() -1);
ErrorMessage errorMessage = mapper.readValue(response, ErrorMessage.class);
System.out.println(errorMessage.toString());
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
I finally get the expected response:
{
"errorId": "z3o1bE8SJrm8WGrxlpIIWe6TNf0NzR",
"status": "ERROR",
"operations": "facturx",
"profile": "client123456",
"errorMessage": "PDF is missing",
"createdAt": "2022-03-16T09:18:57.344+00:00"
}

How to hit ElasticSearch using Apache HttpClient

I have SearchRequest object with all the Elasticsearch(ES) query data set. I cannot use RestHighLevel client for my usecase because it requires endpoint need to be passed at the time of instantiation. I gets ES endpoint dynamically based on some condition. One way is to always create new RestHighLevel client which will be inefficient approach. Other way is to create static CloseableHttpClient on service start and make HttpPost request with dynamic endpoint. I wanted to take later approach but don't know how to convert SearchRequest object into json query string.
Any code reference/snippet would be very helpful
private final CloseableHttpClient client;
public GenericElasticSearchResponse search(#Nonnull final SearchRequest searchRequest,
#Nonnull final RoutingConfig route) {
final URIBuilder builder = new URIBuilder()
.setScheme(route.getScheme())
.setHost(route.getESEndpoint())
.setPort(Optional.ofNullable(route.getPort())
.orElse(80))
.setPath("/sessions*/_search");
final URI uri = builder.build();
final ContentType contentType = ContentType.create("application/json", "UTF-8");
final HttpPost httpPost = new HttpPost(uri);
httpPost.setEntity(entity);
final CloseableHttpResponse response = client.execute(httpPost);
final String responseEntity;
try (final Reader reader = new InputStreamReader(response.getEntity().getContent(), Charsets.UTF_8)) {
responseEntity = CharStreams.toString(reader);
}
final SearchResponse searchResponse = objectMapper.readValue(responseEntity, SearchResponse.class);
return new ElasticSearchResponse(searchResponse);
}
I found searchRequest.source().toString() was actually returning json form of SearchRequest. Following is complete code snippet for hitting ES via Apache client
final EndpointConfig endpoint = route.getEndpoint();
final URIBuilder builder = new URIBuilder()
.setScheme(endpoint.getScheme())
.setHost(endpoint.getHost())
.setPort(Optional.ofNullable(endpoint.getPort())
.orElse(HTTPS_PORT))
.setPath(Optional.ofNullable(endpoint.getQueryPath())
.orElse(StringUtils.EMPTY));
final URI uri = builder.build();
final ContentType contentType = ContentType.create("application/json", "UTF-8");
final String queryString = searchRequest.source().toString();
final StringEntity entity = new StringEntity(queryString, contentType);
final HttpPost httpPost = new HttpPost(uri);
httpPost.setEntity(entity);
final CloseableHttpResponse response = sendRequest(httpPost);
final String responseEntity;
try (final Reader reader = new InputStreamReader(response.getEntity().getContent(), Charsets.UTF_8)) {
responseEntity = CharStreams.toString(reader);
}
log.info("ElasticSearchClient response: Code: {}, Entity {}", response.getCode(), responseEntity);
SearchResponse searchResponse = null;
if (Objects.nonNull(responseEntity)) {
searchResponse = parseResponse(responseEntity, searchRequest, response.getCode());
log.info("ElasticSearchClient searchResponse- {} ", searchResponse);
}
return new ElasticSearchResponse(searchResponse);
} catch (final URISyntaxException e) {
throw new IllegalStateException(
String.format("Invalid URI. host: %s", route.getEndpoint()), e);
} catch (final IOException e) {
throw new IllegalStateException("ElasticSearch Request failed.", e);
}
private SearchResponse parseResponse(#Nonnull final String responseEntity,
#Nonnull final SearchRequest searchRequest,
final int responseCode) {
if (responseCode >= 400 || responseCode < 200) {
log.info("ES error response - {} ", responseEntity);
final ESErrorResponse response = GSON.fromJson(responseEntity, ESErrorResponse.class);
throw new IllegalStateException();
}
SearchResponse searchResponse = null;
final NamedXContentRegistry registry = new NamedXContentRegistry(getDefaultNamedXContents());
final XContentParser parser;
try {
parser = JsonXContent.jsonXContent.createParser(registry,
DeprecationHandler.THROW_UNSUPPORTED_OPERATION, responseEntity);
searchResponse = SearchResponse.fromXContent(parser);
} catch (IOException e) {
throw new IllegalStateException("Error while parsing response ", e);
}
return searchResponse;
}
public static List<NamedXContentRegistry.Entry> getDefaultNamedXContents() {
final Map<String, ContextParser<Object, ? extends Aggregation>> map = new HashMap<>();
map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c));
map.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c));
return map.entrySet().stream()
.map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))
.collect(Collectors.toList());
}
private CloseableHttpResponse sendRequest(final HttpPost httpPost) throws IOException {
return client.execute(httpPost);
}

Spring Webflux(Mono/Flux) with AOP triggering REST call at interception and working with Mono/Flux

I have written an #Aspect to intercept Reactive Methods that return values in Mono/Flux. Using #AfterReturning advice, i'm trying to fire an APNS notification by calling a webservice.
unfortunately the processNotification Mono services is immediately returning onComplete signal without executing the chain of calls. Below is my sample program.
#Aspect
#Component
#Slf4j
public class NotifyAspect{
private final NotificationServiceHelper notificationServiceHelper;
#Autowired
public NotifyAspect(NotificationServiceHelper notificationServiceHelper) {
this.notificationServiceHelper = notificationServiceHelper;
}
#AfterReturning(pointcut = "#annotation(com.cupid9.api.common.annotations.Notify)", returning = "returnValue")
public void generateNotification(JoinPoint joinPoint, Object returnValue) throws Throwable {
log.info("AfterReturning Advice - Intercepting Method : {}", joinPoint.getSignature().getName());
//Get Intercepted method details.
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//Get the Notification Details.
Notify myNotify = method.getAnnotation(Notify.class);
if (Mono.class.isAssignableFrom(returnValue.getClass())) {
Mono<Object> result = (Mono<Object>) returnValue;
result.doOnSubscribe(o -> {
log.debug("On Subscription...");
notificationServiceHelper.processNotification(myNotify.notificationType())
.doOnError(throwable -> {
log.error("Exception in notification processor",throwable);
});
});
}
}
}
#Slf4j
#Service
public class NotificationServiceHelper {
private ReactiveUserProfileRepository userProfileRepository;
#Value("${services.notification.url}")
private String notificationServiceUrl;
private RestWebClient restWebClient;
#Autowired
public NotificationServiceHelper(RestWebClient restWebClient,
ReactiveUserProfileRepository reactiveUserProfileRepository) {
this.restWebClient = restWebClient;
this.userProfileRepository = reactiveUserProfileRepository;
}
public Flux<Notification> processNotification(NotificationSchema.NotificationType notificationType) {
/*Get user profile details*/
return SessionHelper.getProfileId()
.switchIfEmpty( Mono.error(new BadRequest("Invalid Account 1!")))
.flatMap(profileId ->
Mono.zip(userProfileRepository.findByIdAndStatus(profileId, Status.Active), SessionHelper.getJwtToken()))
.switchIfEmpty( Mono.error(new BadRequest("Invalid Account 2!")))
.flatMapMany(tuple2 ->{
//Get user details and make sure there are some valid devices associated.
var userProfileSchema = tuple2.getT1();
log.info("Processing Notifications for User Profile : {}", userProfileSchema.getId());
if (Objects.isNull(userProfileSchema.getDevices()) || (userProfileSchema.getDevices().size() < 1)) {
return Flux.error(new InternalServerError("No Devices associate with this user. Can not send notifications."));
}
//Build Notification message from the Notification Type
var notificationsMap = new LinkedHashSet<Notification>();
userProfileSchema.getDevices().forEach(device -> {
var notificationPayload = Notification.builder()
.notificationType(notificationType)
.receiverDevice(device)
.receiverProfileRef(userProfileSchema.getId())
.build();
notificationsMap.add(notificationPayload);
});
//Get session token for authorization
var jwtToken = tuple2.getT2();
//Build the URI needed to make the rest call.
var uri = UriComponentsBuilder.fromUriString(notificationServiceUrl).build().toUri();
log.info("URI built String : {}", uri.toString());
//Build the Headers needed to make the rest call.
var headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.AUTHORIZATION, jwtToken);
var publishers = new ArrayList<Mono<ClientResponse>>();
notificationsMap.forEach(notification -> {
publishers.add(restWebClient.post(uri, headers, notification));
});
return Flux.merge(publishers).flatMap(clientResponse -> {
var httpStatus = clientResponse.statusCode();
log.info("NotificationService HTTP status code : {}", httpStatus.value());
if (httpStatus.is2xxSuccessful()) {
log.info("Successfully received response from Notification Service...");
return clientResponse.bodyToMono(Notification.class);
} else {
// return Flux.empty();
return clientResponse.bodyToMono(Error.class)
.flatMap(error -> {
log.error("Error calling Notification Service :{}", httpStatus.getReasonPhrase());
if (httpStatus.value() == 400) {
return Mono.error(new BadRequest(error.getMessage()));
}
return Mono.error(new InternalServerError(String.format("Error calling Notification Service : %s", error.getMessage())));
});
}
});
}).doOnError(throwable -> {
throw new InternalServerError(throwable.getMessage(), throwable);
});
}
}
How can we trigger this call in async without making the interception wait.. right now processNotification is always returning onComplete signal without executing. The chain is not executing as expected
#Target({ElementType.PARAMETER, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Log {
public String title() default "";
}
#SuppressWarnings({"unchecked"})
#Around("#annotation(operlog)")
public Mono<Result> doAround(ProceedingJoinPoint joinPoint, Log operlog) {
Mono<Result> mono;
try {
mono = (Mono<Result>) joinPoint.proceed();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
return mono.doOnNext(result -> {
//doSomething(result);
};
}

How to Redirect request as post using ResponseEntity

I trying to include response from other url from ResponseEntity for oauth authorization but it is failing as I am unable to specify request method.
Below is the code
#RequestMapping(value = "/login/otp", method = RequestMethod.POST, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
#ResponseBody
public ResponseEntity<?> getOTP(#Valid #RequestBody String loginDtls,UriComponentsBuilder ucBuilder) {
LoginDAO login = null;
ResponseEntity<?> resp = null;
try {
ObjectMapper mapper = new ObjectMapper();
String userId = "";
try {
JsonNode root = mapper.readTree(loginDtls);
userId = root.get("userId").textValue();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("UserController : getting otp for contact "+ userId);
login = loginService.findByUserId(userId);
if (login==null) {
System.out.println("A UserDAO with name " + userId + " does not exist");
resp = new ResponseEntity<String>(HttpStatus.NOT_FOUND);
}
String otp = GenUtil.generateOTP();
LoginDAO loginUpd = new LoginDAO(login);
loginUpd.setOtp(otp);
loginUpd.setOtpTimestamp(new Timestamp(System.currentTimeMillis()));
loginService.updateLogin(loginUpd);
System.out.println(loginUpd);
resp = getAuthenticated(ucBuilder);
System.out.println(resp.getStatusCodeValue());
System.out.println(resp.getBody());
}catch(Exception e) {
e.printStackTrace();
}
resp = new ResponseEntity<String>(login.toString(), HttpStatus.OK);
return resp;
}
private ResponseEntity<?> getAuthenticated(UriComponentsBuilder ucBuilder){
HttpHeaders headers = new HttpHeaders();
URI uri= ucBuilder.path("/oauth/token"+PASSWORD_GRANT).build().toUri();
List<MediaType> accept = new ArrayList<MediaType>();
accept.add(MediaType.APPLICATION_JSON_UTF8);
headers.setAccept(accept);
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
headers.setBasicAuth("my-trusted-client", "secret");
System.out.println(headers);
ResponseEntity<?> resp = ResponseEntity.created(uri).headers(headers).build();
return resp;
}

Consuming Soap Service in spring boot application

I need to consume a soap service in spring boot. How can i do that easily using annotations like we do for Rest. I need to send headers, form the body for my service. Please help me with the solution
public String sendMessage(String processInstanceId) {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
String request = "<SOAP:Envelope xmlns:" + "SOAP='http://schemas.xmlsoap.org/soap/envelope/'>" + "<SOAP:Body>"
+ "<SendMessage xmlns='http://schemas.cordys.com/bpm/execution/1.0'>" + "<receiver>" + processInstanceId
+ "</receiver>" + "<message overwrite='false' />" + "</SendMessage>" + "</SOAP:Body>"
+ "</SOAP:Envelope>";
SendMessageAPI sendMessageObject = new SendMessageAPI();
StreamSource source = new StreamSource(new StringReader(request));
StreamResult result = new StreamResult(System.out);
System.out.println("called service" + request);
webServiceTemplate.sendSourceAndReceiveToResult(
"url",
source, result);
return "Success";
You may use Spring Web Service where it's present the WebServiceTemplate similar to the RestTemplate
In order to add SOAP Header and/or HTTP Header you can implement the WebServiceMessageCallback interface.
Here a simple example for adding HTTP Headers
The WebServiceMessageCallback implementation (note I'm using Axiom as MessageFactory)
public class WsHttpHeaderCallback implements WebServiceMessageCallback
{
private String headerKey;
private String headerValue;
private String soapAction;
public WsHttpHeaderCallback(String headerKey, String headerValue, String soapAction)
{
super();
this.headerKey = headerKey;
this.headerValue = headerValue;
this.soapAction = soapAction;
}
public WsHttpHeaderCallback()
{
super();
}
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException
{
validateRequiredFields();
addRequestHeader(headerKey, headerValue);
if (StringUtils.hasText(this.soapAction))
{
AxiomSoapMessage axiomMessage = (AxiomSoapMessage) message;
axiomMessage.setSoapAction(this.soapAction);
}
}
private void addRequestHeader(String headerKey, String headerValue)
{
TransportContext context = TransportContextHolder.getTransportContext();
WebServiceConnection connection = context.getConnection();
if (connection instanceof HttpComponentsConnection)
{
HttpComponentsConnection conn = (HttpComponentsConnection) connection;
HttpPost post = conn.getHttpPost();
post.addHeader(headerKey, headerValue);
}
else if( connection instanceof ClientHttpRequestConnection )
{
ClientHttpRequestConnection conn = (ClientHttpRequestConnection)connection;
conn.getClientHttpRequest().getHeaders().add(headerKey, headerValue);
}
}
}
The WebServiceMessageCallback usage:
WebServiceResponse resp = (WebServiceResponse)webSvcTemplate.marshalSendAndReceive(wsUrl, request, new WsHttpHeaderCallback(headerKey, headerValue, "http://ws.com/soapAction") );
I hope it's usefull
Angelo

Resources