I have a controller:
#GetMapping("/startRoute")
#ApiOperation(value = "startDRoute",
response = ResponseEntity.class)
public ResponseEntity<BaseResponse<Boolean>> startRoute() {
logger.info("ReconciliationController - startDaily called");
ReconciliationBatchRequest reconciliationBatchRequest = new ReconciliationBatchRequest();
reconciliationBatchRequest.setDocumentTypeOfJob(0L);//hardcoded change
producerTemplate
.asyncSendBody(DOCUMENT_RECONCILIATION_ROUTE_START,
reconciliationBatchRequest);
logger.info("ReconciliationController - startDaily ended");
return ResponseEntity.ok(new BaseResponse<>(true));
}
it starts the camel route :
#Override
public void configure() {
logger.info("DocumentReconciliationRoute configure started");
onException(Exception.class)
.process(reconciliationExceptionProcessor)
// .handled(true).maximumRedeliveries(3).redeliveryDelay(0)
;
from("direct:DocumentReconciliationRoute.start")
.log(LoggingLevel.INFO, DOCUMENT_RECONCILIATION_ROUTE, "Daily camel route started")
.routeId("reconciliationCamelRoute")
.process(createFTPExpressionProcessor)
.to(GET_FILE_FROM_SFTP)
.log(LoggingLevel.INFO, DOCUMENT_RECONCILIATION_ROUTE, "Daily camel route ended")
// .end()
Beause it comes from controller async, it can be called multiple times. But i dont want this. It should not work if there is one running route.
I created this to stop if there is active route:
#GetMapping("/stop")
#ApiOperation(value = "stop",
notes = "For stopping reconciliation",
response = ResponseEntity.class)
public ResponseEntity<String> stop() throws Exception {
logger.info("ReconciliationController - stop called");
camelContext.stopRoute("reconciliationCamelRoute");
logger.info("ReconciliationController - stop ended");
return ResponseEntity.ok("ok");
}
but if there is runnin route, this does not stop. It waits :
Starting to graceful shutdown 1 routes (timeout 300 seconds)
How can I make only one instance is working even multiple people calls that controller endpoind?
I tried this:
boolean isRouteStarted =
((org.apache.camel.support.ServiceSupport)route).isStarted();
but is this safe?
Related
Staring with the tutorial code at benwilcock/spring-rsocket-demo I am trying to write a server that replicates messages to a second server before responding to a client.
To try to debug my issues I am only attempting a trivial ping-pong exchange between servers. Only when the second server responds to the pong message should the first server reply to the client:
#MessageMapping("request-response")
Mono<Message> requestResponse(final Message request) {
// register a mono that will be completed when replication to another server has happened
String uuid = UUID.randomUUID().toString();
Mono<Message> deferred = Mono.create(sink -> replicationNexus.registerRequest(uuid, sink));
// FIXME attempt to send a nested request-response message that will complete the outer message later
this.requesterMono.flatMap(requester -> requester.route("pong")
.data(uuid)
.retrieveMono(String.class))
.subscribeOn(Schedulers.elastic())
.subscribe( uuid2 -> replicationNexus.complete(uuid2, new Message(SERVER, RESPONSE)));
// return the deferred work that will be completed by the pong response
return deferred;
}
That logic is trying to use this answer to create a connection to the second server that will reconnect:
this.requesterMono = builder.rsocketConnector(connector -> connector
.reconnect(Retry.fixedDelay(Integer.MAX_VALUE, Duration.ofSeconds(1))))
.connectTcp("localhost", otherPort).cache();
To complete the picture here is the trivial ping-pong logic:
#MessageMapping("pong")
public Mono<String> pong(String m) {
return Mono.just(m);
}
and here is the logic that holds the state of the outer client response that is completed when the other server responds:
public class ReplicationNexus<T> {
final Map<String, MonoSink<T>> requests = new ConcurrentHashMap<>();
public void registerRequest(String v, MonoSink<T> sink) {
requests.put(v, sink);
}
public boolean complete(String uuid, T message) {
Optional<MonoSink<T>> sink = Optional.of(requests.get(uuid));
if( sink.isPresent() ){
sink.get().success(message);
}
return sink.isPresent();
}
}
Debugging the second server it never runs the pong method. It seems that the first server does not actually send the inner request message.
What is the correct way to run an inner request-response exchange that completes an outer message exchange with automated reconnection logic?
Not sure if I'm missing some of the complexity of your question, but if the middle server is just activing like a proxy I'd start with the simplest case of chaining through the calls. I feel like I'm missing some nuance of the question, so let's work through that next.
#MessageMapping("runCommand")
suspend fun runCommandX(
request: CommandRequest,
): Mono<String> {
val uuid = UUID.randomUUID().toString()
return requesterMono
.flatMap { requester: RSocketRequester ->
requester.route("pong")
.data("TEST")
.retrieveMono(String::class.java)
}
.doOnSubscribe {
// register request with uuid
}
.doOnSuccess {
// register completion
}
.doOnError {
// register failure
}
}
Generally if you can avoid calling subscribe yourself in typical spring/reactive/rsocket code. You want the framework to do this for you.
I'm trying to use Flux to generate asynchronous server sent events using Flux.create. When my client connects the request eventually times out with no event ever received. I hard-coded in an event to be sent by the Flux.create just to see data flow, but still nothing received client side.
#GetMapping(path = "/stream", headers = "Accept=*/*", consumes = MediaType.ALL_VALUE, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<PricingDTO>> getEventStream() {
final Flux<ServerSentEvent<PricingDTO>> flux = Flux.<ServerSentEvent<PricingDTO>>create(emitter -> {
final PricingDTO pricing = new PricingDTO();
pricing.setId(99L);
emitter.next(ServerSentEvent.builder(pricing).build());
});
return flux;
}
Client side (Angular) code:
const eventSource = new EventSource(url);
eventSource.onmessage = (event) => {
console.debug('Received event: ' + event);
const json = JSON.parse(event.data);
// Should be PricingDTO record here
};
eventSource.onerror = (error) => {
if (eventSource.readyState === EventSource.CLOSED) {
console.log('The stream has been closed by the server.');
eventSource.close();
} else {
console.log('Error here: ' + error);
}
};
I never see an event come through the EventSource. Eventually the request times out and I see the error: net::ERR_EMPTY_RESPONSE
I'm new to using WebFlux and I suspect I'm missing some initialization on the FluxStream before I return the Flux result. I have debugged and do see the request being received by my web service and the Flux object being returned. Any idea why I'm not receiving my events?
Your webflux code seems fine. I tested this with the following simplified example (without your custom classes).
#SpringBootApplication
#RestController
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> getEventStream() {
return Flux.create(emitter -> emitter.next("hi").next("hi2"));
}
}
When connecting to the steam in chrome you get to see the events coming in just fine:
data:hi
data:hi2
the problem either lies in your accept header filter, or on the client side. You could ofcourse validate this by connecting to your stream in a browser (or better, a test)
Suppose we have a spring rest controller that does a long operation:
#PostMapping(path = "someLongOperation")
public ResponseEntity<?> doOp(#RequestBody MyBody) {
...do A
...do B //client cancels request while execution is at this step
...do some calculations
...save result to database
return ResponseEntity.ok("my response body")
}
And at step B client cancels the request, eg. pressing cancel on Postman, closing web page on browser, will our method still continue executing and write the results to the database?
And are asynchronous methods different in this regard?
#PostMapping(path = "someLongOperation")
public Callable<ResponseEntity<?>> doOp(#RequestBody MyBody) {
return () -> {
...do A
...do B //client cancels request while execution is at this step
...do some calculations
...save result to database
return ResponseEntity.ok("my response body")
}
}
Here is the scenario
a)Tablet use http GET to get users info from another system
b)the other system can add/delete/update users info also and the operations result should show in tablet in time.
To resolve that I use reactor+sse as demo code shows below
In controller
#GetMapping
public Flux<List<User>> listUser() {
return service.listUser();
}
In service
public Flux<List<User>> listUser() {
Flux<List<User>> flux = Flux.generate(
() -> 0,
(state, sink) -> {
synchronized(users) {
users.wait();//other thread will notify with all users info is send to service
}
sink.next(users);
newData = false;
return state + 1;
});
return flux;
}
My questions are,
a)should I use Flux<User> to instead of Flux<List<User>>? I didn't see any usage of Flux<List<T>> so far. also one advantage is that with Flux<User>, I don't need to push all users info to the tablet, only the add/delete/update ones.
b)if I use Flux<List<User>, what should I write for bodyToFlux's parameters in below code
webClient.get()
.uri("/user")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux() //???
i'm using restful web service. In that i have a method, by using this method i send notification and mails. My method is working fine, only problem is that notification pops up 2,3 seconds before you get success response after calling this method.
#Override
#Transactional
public MedikmResponse createNPostCcbrQuestion(CreateNPostCcbrQuestionRequest createNPostCcbrQuestionRequest, HttpServletRequest request) {
logger.info("#########################Post Ccbr Question#############################");
MedikmResponse medikmResponse=new MedikmResponse();
try {
String token = request.getHeader("authToken");
User user = userDao.findUserByAuthToken(token);
if (user != null) {
/**
* Question Creation
*/
CcbrQuestion ccbrQuestion=new CcbrQuestion();
ccbrQuestion.setQuestiomtext(createNPostCcbrQuestionRequest.getCcbrQuestionText());
Integer questionId=ccbrQuestionDao.saveCcbrQuestion(ccbrQuestion);
ccbrQuestion.setQuestionId(questionId);
/* ***
* Question post
*/
TbDiscussionForumQuestion tbDiscussionForumQuestion=new TbDiscussionForumQuestion();
tbDiscussionForumQuestion.setCCBRQuestionId(ccbrQuestion);
tbDiscussionForumQuestion.setTBDiscussionId(new TbDiscussionForum(createNPostCcbrQuestionRequest.getTbdiscussionId()));
tbDiscussionForumQuestion.setPhysicianId(new Physician(user.getPhysicianId().getPhysicianId()));
tbDiscussionForumQuestion.setQuestion(createNPostCcbrQuestionRequest.getCcbrQuestionText());
tbDiscussionForumQuestion.setQuestionDate(new Date());
//tbDiscussionForumQuestionDao.saveTbDiscussionForumQuestion(tbDiscussionForumQuestion);
TbDiscussionForum discussionForum=tbDiscussionForumDao.findTbDiscussionForumbyTbId(createNPostCcbrQuestionRequest.getTbdiscussionId());
discussionForum.getTbDiscussionForumQuestionCollection().add(tbDiscussionForumQuestion);
tbDiscussionForumDao.updateTbDiscussionForum(discussionForum);
String[] deviceIdList=getParticpentDeviceIdList(discussionForum.getCaseId().getCaseId(), user);
medikmResponse.setAuthenticationKey(user.getAuthToken());
notificationSender.sendPostedCcbrQuestionToVMDCParticipant(discussionForum.getCaseId(), user.getPhysicianId(), createNPostCcbrQuestionRequest.getCcbrQuestionText());//sending mail
if(deviceIdList.length!=0){
notificationService.sendNotificationToIOS(deviceIdList,discussionForum.getCaseId().getCaseId(),createNPostCcbrQuestionRequest.getTbdiscussionId(),"Question");
}
medikmResponse.setResponseCode(MedikmConstants.SUCCESS_CODE);
medikmResponse.setResponseMessage(MedikmConstants.SUCCESS_MESSAGE);
}else{
medikmResponse.setResponseCode(MedikmConstants.FAILURE_CODE);
medikmResponse.setResponseMessage(MedikmConstants.USER_DOES_NOT_EXIST);
logger.info("############ USER_DOES_NOT_EXIST #############"+ MedikmConstants.USER_DOES_NOT_EXIST);
}
return medikmResponse;
}catch(Exception ex){
ex.printStackTrace();
return null;
}
}
for this i add #Async annotation in the sendNotificationToIOS method and it works for me after adding this annotation need to add in your xml