Currently I have an application with Spring Integration DSL that has AMQP inbound gateway with different service activators, each service activator has kind of logic to decide, transform and call external web services (currently with CXF), but all this logic is in code without Spring Integration components.
These service activators are monitored, in the output channel that returns data from this application is an AMQP adapter that sends headers to a queue (after that, all headers are processed and saved in a database for future analysis). This works well, these service activators even have elapsed time in headers.
Now, the problem is, that I need to monitor external web service calls, like elapsed time in each operation, which service endpoint and operation was called, if an error occurred.
I've been thinking that logic code in each service activator should be converted into a Spring Integration flow, in each service activator, would call a new gateway with the name of the operation of the web service in a header, and monitoring every flow as currently I had been doing.
So, I'm not sure if this manual approach is the better approach, so I wonder if there is a way to get the name of the service operation with some kind of interceptor or something similar with CXF or Spring WS to avoid setting the name of the operation in headers in a manual way? What would be your recommendation?
To have more context here is the Spring Integration configuration:
#Bean
public IntegrationFlow inboundFlow() {
return IntegrationFlows.from(Amqp.inboundGateway(simpleMessageListenerContainer())
.mappedReplyHeaders(AMQPConstants.AMQP_CUSTOM_HEADER_FIELD_NAME_MATCH_PATTERN)
.mappedRequestHeaders(AMQPConstants.AMQP_CUSTOM_HEADER_FIELD_NAME_MATCH_PATTERN)
.errorChannel(gatewayErrorChannel())
.requestChannel(gatewayRequestChannel())
.replyChannel(gatewayResponseChannel())
)
.enrichHeaders(new Consumer<HeaderEnricherSpec>() {
#Override
public void accept(HeaderEnricherSpec t) {
t.headerExpression(AMQPConstants.START_TIMESTAMP, "T(java.lang.System).currentTimeMillis()");
}
})
.transform(getCustomFromJsonTransformer())
.route(new HeaderValueRouter(AMQPConstants.OPERATION_ROUTING_KEY))
.get();
}
#Bean
public MessageChannel gatewayRequestChannel() {
return MessageChannels.publishSubscribe().get();
}
#Bean
public MessageChannel gatewayResponseChannel() {
return MessageChannels.publishSubscribe().get();
}
private IntegrationFlow loggerOutboundFlowTemplate(MessageChannel fromMessageChannel) {
return IntegrationFlows.from(fromMessageChannel)
.handle(Amqp.outboundAdapter(new RabbitTemplate(getConnectionFactory()))
.exchangeName(LOGGER_EXCHANGE_NAME)
.routingKey(LOGGER_EXCHANGE_ROUTING_KEY)
.mappedRequestHeaders("*"))
.get();
}
And here is a typical service activator, as you can see, all this logic could be an integration flow:
#ServiceActivator(inputChannel="myServiceActivator", outputChannel = ConfigurationBase.MAP_RESPONSE_CHANNEL_NAME)
public Message<Map<String, Object>> myServiceActivator(Map<String, Object> input, #Header(AMQPConstants.SESSION) UserSession session) throws MyException {
Message<Map<String, Object>> result = null;
Map<String, Object> mapReturn = null;
ExternalService port = serviceConnection.getExternalService();
try {
if (input.containsKey(MappingConstants.TYPE)) {
Request request = transformer
.transformRequest(input, session);
Response response = port
.getSomething(request);
utils.processBackendCommonErrors(response.getCode(), response.getResponse());
mapReturn = transformer.convertToMap(response);
} else {
Request request = transformer
.transformRequest(input, session);
Response response = port
.getSomethingElse(request);
utils.processBackendCommonErrors(response.getCode(),
response.getResponse());
mapReturn = transformer.convertToMap(response);
}
} catch (RuntimeException e) {
String message = "unexcepted exception from the back-end";
logger.warn(message, e);
throw MyException.generateTechnicalException(message, null, e);
}
result = MessageBuilder.withPayload(mapReturn)
.build();
return result;
}
So far so good. Or I don't understand the problem, or you are not clear where it is.
Anyway you always can proxy any Spring Service with the AOP, since it looks like you are pointing to the code:
Response response = port
.getSomething(request);
When this (or similar) method is called, some MethodInterceptor can perform desired tracing logic and send result to some MessageChannel for further analysis or anything else to do:
public Object invoke(MethodInvocation invocation) throws Throwable {
// Extract required operation name and start_date from the MethodInvocation
Object result = invocation.proceed();
// Extract required data from the response
// Build message and send to the channel
return result;
}
Related
I'm new to Spring Boot and web applications. I have to send notifications of unhandled/unread messages from a Spring Boot backend to a web client. I decided to use Server Sent Events since I think I don't need a bidirectional connection (otherwise I'd have thought of WebSockets).
I made a very simple REST controller which finds all unhandled messages in a db and sends them to the client. The problem is that it keeps sending forever all the messages, while I'd like to send a message only when it is added to the db, or when the client first connects to the server.
The behaviour I'd like to achieve is similar to a mail client or a messaging app, in which the user is notifyed not only on new messages but also of previous ones if he/she didn't mark them as read. The notification should happen only once when the client connects, not loop forever.
Here is my code:
#RestController
#CrossOrigin(origins = "*")
public class SseEmitterController {
private MessageDAO messageDAO;
private ExecutorService nonBlockingService = Executors
.newCachedThreadPool();
#Autowired
public SseEmitterController(MessageDAO messageDAO) {
this.messageDAO = messageDAO;
}
#GetMapping("/incoming_messages")
public SseEmitter handleSse() {
SseEmitter emitter = new SseEmitter();
nonBlockingService.execute(() -> {
try {
List<Message> messages = messageDAO.findByHandledFalse();
for (Message message: messages) {
emitter.send(message, MediaType.APPLICATION_JSON);
}
emitter.complete();
} catch (Exception ex) {
emitter.completeWithError(ex);
}
});
return emitter;
}
}
I know that the problem is caused by the fact that I query the db inside handleSse method, but I couldn't figure out how to do it outside.
Could you please help me?
Update October 05, 2021
I found out how to solve the problem, I didn't update the question because I didn't have the time, but since someone asked me to do so in the comments, I'm gonna explain my solution, hoping it may be helpful.
Here's my code:
The SseEmitterController is responsible for invoking the SseService on frontend's request:
#RestController
#CrossOrigin(origins = "*")
public class SseEmitterController {
private final SseService sseService;
#Autowired
SseEmitterController(SseService sseService) {
this.sseService = sseService;
}
#GetMapping("/incoming_messages")
public ResponseEntity<SseEmitter> handleSse() {
final SseEmitter emitter = new SseEmitter();
sseService.addEmitter(emitter);
emitter.onCompletion(() -> sseService.removeEmitter(emitter));
emitter.onTimeout(() -> sseService.removeEmitter(emitter));
return new ResponseEntity<>(emitter, HttpStatus.OK);
}
}
The SseService is called on a new message arrival (from another part of the application) and sends the notification (actually a server sent event) to the frontend (which previously called the endpoint in the controller above.
The service is called like so: sseService.sendHelpRequestNotification(helpRequest);
#Service
public class SseService {
private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
public void addEmitter(final SseEmitter emitter) {
emitters.add(emitter);
}
public void removeEmitter(final SseEmitter emitter) {
emitters.remove(emitter);
}
public void sendMessagesNotification(Message message) {
List<SseEmitter> sseEmitterListToRemove = new ArrayList<>();
emitters.forEach((SseEmitter emitter) -> {
try {
emitter.send(message, MediaType.APPLICATION_JSON);
} catch (Exception e) {
sseEmitterListToRemove.add(emitter);
}
});
emitters.removeAll(sseEmitterListToRemove);
}
}
And finally there is another controller to get all previous unhandled messages (not involving server sent events):
#GetMapping(value = "/unhandled_help_requests")
public ResponseEntity<List<HelpRequest>> getUnhandledMessages() {
List<Message> resultSet = messageDAO.findByHandledFalse(Sort.by("date").and(Sort.by("time")));
return new ResponseEntity<>(resultSet, HttpStatus.OK);
}
So, to sum it up: the frontend calls the SseEmitterController to listen for new SSEs. These SSEs are created and sent whenever a new message arrives to the backend, via the SseService. Finally, to get all unhandled (for whatever reason) messages, there is a specific old fashioned controller.
From my backend application(springboot, java8) i will make multiple external api call's. I have a requirement to log all the requests and response data's (including headers, request and response body) into database(MongoDB).
Below is my sample code, this how i am trying to capture request and responses on each external api calls. On exception i will store status as 'FAILED'.
In my project multiple modules will be added on new 3rd party api integration, so in each module for every different external api calls i have to capture all the requests and reponses like this. I am not satisfied with below approach. Kindly suggest best approach to solve this.
Sample Service layer method
public ResponseDTOFromExternalApi externalApiCallServiceMethod(String clientId, RequestDTO requestDTO) {
ExternalApiCallRequestObj externalApiCallRequestObj = prepareExternalApiRequestObjFromRequestDTO(requestDTO);
ApiCall apiCall = ApiCall.builder()
.clientId(clientId)
.status("SUBMITTED")
.requestPayload(externalApiCallRequestObj)
.build();
apiCall = apiCallRepository.save(apiCall);
ExternalApiCallReponseObj externalApiCallReponseObj = externalApiCallService.callExternalApi1(externalApiCallRequestObj);
apiCall = apiCallRepository.findById(apiCall.getId());
apiCall.setResponsePayload(externalApiCallReponseObj);
apiCall.setStatus("COMPLETED");
apiCallRepository.save(apiCall);
return toDTO(externalApiCallReponseObj);
}
Sample Domain for api calls
#Document("api_calls")
#Builder
#Data
public class ApiCall {
#Id
private String id;
private String clientId;
private String status;
private Object requestPayload;
private Object responsePayload;
}
Spring's WebClient already has the ability to log all request and response data by adding exchange filters.
By using it for your network requests the only thing left to do is to write this information in your mongodb.
Here is tutorial on logging requests and responses:
https://www.baeldung.com/spring-log-webclient-calls
Cheers
You may use Spring AOP to address this cross cutting concern.
Assuming ExternalApiCallService is a spring managed bean , following code will intercept all the callExternalApi1() and can log the same to database.
#Component
#Aspect
public class ExternalCallLoggerAspect {
#Autowired
ApiCallRepository apiCallRepository;
#Pointcut("execution(* *..ExternalApiCallService.callExternalApi1(..))")
public void externalApiCallService(){}
#Around("externalApiCallService() && args(request)")
public ExternalApiCallReponseObj logCalls(ProceedingJoinPoint pjp,ExternalApiCallRequestObj request){
Object result=null;
String status = "COMPLETED";
ExternalApiCallReponseObj response = null;
// Build the apiCall from request
ApiCall apiCall = ApiCall.builder()
.clientId(clientId)
.status("SUBMITTED")
.requestPayload(request)
.build();
//save the same to db
apiCall = apiCallRepository.save(apiCall);
// Proceed to call the external Api and get the result
try {
result = pjp.proceed();
} catch (Throwable e) {
status = "FAILED";
}
//Update the response
apiCall = apiCallRepository.findById(apiCall.getId());
apiCall.setStatus(status);
apiCallRepository.save(apiCall);
if(result != null) {
response = (ExternalApiCallReponseObj)result;
apiCall.setResponsePayload(response);
}
//continue with response
return response;
}
}
Note
1.There is a typo with the name ExternalApiCallReponseObj
2.The aspect code is verified that it works and the logic was included later on untested. Please make the required corrections
Ideally the original method should be stripped down to this
public ResponseDTOFromExternalApi externalApiCallServiceMethod(String clientId, RequestDTO requestDTO) {
return toDTO(externalApiCallService.callExternalApi1(prepareExternalApiRequestObjFromRequestDTO(requestDTO)));
}
More about Spring AOP here
Update : on a second thought , if all the external api calls are through a single method , say ExternalApiCallService.callExternalApi1() , this logging logic can be moved to that common point , isn't it ?
I am trying to request an object via oauth2 authentification with spring security and then try to use those credentials in my service, which is supposed to request data in fixed intervals.
The SleepController's purpose is to initially get data and as a side effect cache the important data for the service below.
#RestController
public class SleepController {
#Autowired
OAuth2RestTemplate oAuth2RestTemplate;
public static OAuth2RestTemplate currentOAuth2RestTemplate;
#RequestMapping("/sleep")
public OverallSleep getSleep(String date) {
if(date != null) {
OverallSleep overallSleep = null;
try {
overallSleep = oAuthRestTemplate.getForObject("https://api.fitbit.com/1.2/user/-/sleep/date/" + date + ".json", OverallSleep.class);
currentOAuth2RestTemplate = oAuth2RestTemplate;
} catch (Exception e) {
e.printStackTrace();
}
OverallSleep tmp = overallSleepDao.findOverallSleepByToday();
return overallSleep;
}
return null;
}
}
The BeatService has a scheduled function that tries to request data from the server without user interaction.
#Service
public class BeatService {
private static final long INTERVAL = 60_000L;
private static final long INITIAL_DELAY = 3_000L;
public BeatService() { }
#Scheduled(fixedRate = INTERVAL, initialDelay = INITIAL_DELAY)
public void beat() {
if(SleepController.currentOAuth2RestTemplate != null) {
OverallSleep overallSleep = null;
try {
overallSleep = SleepController.currentOAuth2RestTemplate.getForObject("https://api.fitbit.com/1.2/user/-/sleep/date/" + "2018-02-11" + ".json", OverallSleep.class);
overallSleepDao.save(overallSleep);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Sadly, my approach of statically storing the oauthRestTemplate and trying to use that in my scheduled service does not work.
This Exception is thrown
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'session' is not active for the current thread; consider defining a
scoped proxy for this bean if you intend to refer to it from a singleton;
nested exception is java.lang.IllegalStateException: No thread-bound request
found: Are you referring to request attributes outside of an actual web
request, or processing a request outside of the originally receiving thread?
If you are actually operating within a web request and still receive this
message, your code is probably running outside of
DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current
request.
at [...]
Caused by: java.lang.IllegalStateException:
No thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of the
originally receiving thread? If you are actually operating within a web
request and still receive this message, your code is probably running
outside of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current
request.
Is there an appriopriate or right way to reuse the credentials from a prior authentification in oauth2 in Spring? I found a post sadly it doesn't work and actually make the controller crash so I can't test the service at all.
If needed, I can add other classes or what #Configuration I use.
I'm facing the following problem and I found no working solution yet.
I have 3 different applications that should communicate with each other:
the UI part (1)
the backend application (2)
the microservice "in the cloud" (3)
The backend application provides a Webservice (REST) for the UI to get and put information from/to the microservice.
Everything I want to grab from the microservice works fine, but:
If I want to put data to the microservice, the specs require a websocket connection. This works fine too, but the microservice returns a message after the (un-)successful command, like
{"statusCode":200,"messageId":"1234567890"}
The problem now is: How can I grab this message in my application and send it back to the UI, so the user knows if the command was successful?
For the moment I tried this:
WebSocketClient.java
#OnMessage
public void onMessage(Session session, String msg) {
if (this.messageHandler != null) {
this.messageHandler.handleMessage(msg);
}
}
public void addMessageHandler(MessageHandler msgHandler) {
this.messageHandler = msgHandler;
}
public static interface MessageHandler {
public String handleMessage(String message);
}
MyTotalAwesomeController.java
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
...
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
...
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
return message;
}
});
return ResponseEntity.ok("worked");
}
I can see the console output from the MessageHandler return, but I don't know how I can pass this to the parent method for return insted of just returning the ResponseEntity.ok().
I'm not very used to WebSocket connections in Java yet, so please don't judge me ;-)
Thank you for your help.
The code below will work under the assumption that the #OnMessage method is executed in a thread managed by the WebSocket client runtime. Please inspect the thread that runs the #OnMessage method.
If the above premise is true, the putDataToMicroservice() method, executed by a thread in the global scope, will wait until the WebSocket response arrives at the WS client thread, which will repass the message to the global scope thread. Then the execution in your controller class will continue.
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
// Queue for communication between threads.
private BlockingQueue<String> queue;
#PostConstruct
void init() {
queue = new SynchronousQueue<>(true);
// This callback will be invoked by the WebSocket thread.
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
#Override
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
// Pass message to the controller thread.
queue.put(message);
// Note that the return value is not necessary.
// You can take it out of the interface as well.
return null;
}
});
}
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
// At this point you make a WebSocket request, is that right?
doWebSocketRequest();
// This poll call will block the current thread
// until the WebSocket server responds,
// or gives up waiting after the specified timeout.
//
// When the WebSocket server delivers a response,
// the WS client implementation will execute the
// #OnMessage annotated method in a thread
// managed by the WS client itself.
//
// The #OnMessage method will pass the message
// to this thread in the queue below.
String message = queue.poll(30, TimeUnit.SECONDS);
if (message == null) {
// WebSocket timeout.
}
return ResponseEntity.ok("worked");
}
}
I have a number of classes exposed as JAX-RS request "handlers", using javax.ws.rs.Path annotations. I want to add certain actions before every request and after each request. Also, I need to create a global application-wide exception handler, which will catch everything thrown by these handlers and protocol.
Is it possible to achieve this with standard JAX-RS without creating of a custom class inherited from com.sun.jersey.spi.container.servlet.ServletContainer (I'm using Jersey).
You can also use ExceptionMappers. This mechanism which catch the exception thrown by your service and convert it to the appropriate Response:
#Provider
public class PersistenceMapper implements ExceptionMapper<PersistenceException> {
#Override
public Response toResponse(PersistenceException arg0) {
if(arg0.getCause() instanceof InvalidDataException) {
return Response.status(Response.Status.BAD_REQUEST).build();
} else {
...
}
}
}
For more information see:
JAX-RS using exception mappers
You could create a proxy RESTful service and use this as the entry point to all your other RESTful services. This proxy can receive requests, do any pre-processing, call the RESTful service required, process the response and then return something to the caller.
I have a set up like this in a project I've been working on. The proxy performs functions like authentication, authorisation and audit logging. I can go into further details if you like.
Edit:
Here is an idea of how you might want to implement a proxy that supports GET requests;
#Path("/proxy")
public class Proxy
{
private Logger log = Logger.getLogger(Proxy.class);
#Context private UriInfo uriInfo;
#GET
#Path("/{webService}/{method}")
public Response doProxy(#Context HttpServletRequest req,
#PathParam("webService") String webService,
#PathParam("method") String method)
{
log.debug("log request details");
//implement this method to work out the URL of your end service
String url = constructURL(req, uriInfo, webService, method);
//Do any actions here before calling the end service
Client client = Client.create();
WebResource resource = client.resource(url);
try
{
ClientResponse response = resource.get(ClientResponse.class);
int status = response.getStatus();
String responseData = response.getEntity(String.class);
log.debug("log response details");
//Do any actions here after getting the response from the end service,
//but before you send the response back to the caller.
return Response.status(status).entity(responseData).build();
}
catch (Throwable t)
{
//Global exception handler here
//remember to return a Response of some kind.
}
}
You can use filters to read and modify all requests and responses.