I am defining an IntegrationFlow to stream from SFTP to S3 with the DSL syntax this way :
return IntegrationFlows.from(Sftp.inboundStreamingAdapter(remoteFileTemplate)
.remoteDirectory("remoteDirectory"),
e -> e.poller(Pollers.fixedDelay(POLL, TimeUnit.SECONDS)))
.transform(new StreamTransformer())
.handle(s3UploadMessageHandler(outputFolderPath, "headers['file_remoteFile']")) // Upload on S3
.get();
private S3MessageHandler s3UploadMessageHandler(String folderPath, String spelFileName) {
S3MessageHandler s3MessageHandler = new S3MessageHandler(amazonS3, s3ConfigProperties.getBuckets().getCardManagementData());
s3MessageHandler.setKeyExpression(new SpelExpressionParser().parseExpression(String.format("'%s/'.concat(%s)", folderPath, spelFileName)));
s3MessageHandler.setCommand(S3MessageHandler.Command.UPLOAD);
return s3MessageHandler;
}
And it works as intended : the file is well uploaded to my S3 bucket. However, I would like to avoid SPEL syntax, and inject headers from the message to the s3uploadMessageHandler method, this way I could use a simple ValueExpression to set the keyExpression in the s3UploadMessageHandler method.
To do this, I changed
handle(s3UploadMessageHandler(outputFolderPath, "headers['file_remoteFile']")) // Upload on S3
to
handle(m -> s3UploadMessageHandler(outputFolderPath, (String) m.getHeaders().get("file_remoteFile"))) // Upload on S3
But now this handler doesn't seem to be triggered anymore. There is no errors in the logs, and I know from the logs that the SFTP polling is still working.
I tried to find the reason behind this, and I saw that when entering the handle method in IntegrationFlowdefinition.java, the messageHandler class type is different : it's an S3MessageHandler when called without lambda, and a MyCallingClass$lambda when calling with a lambda expression.
What did I miss to make my scenario working ?
There are two ways to handle a message. One is via a MessageHandler implementation - this is the most efficient approach and that's done in the framework for channel adapter implementation, like that S3MessageHandler. Another way is a POJO method invocation - this is the most user-friendly approach when you don't need to worry about any framework interfaces.
So, when you use it like this .handle(s3UploadMessageHandler(...)) you refer to a MessageHandler and the framework knows that a bean for that MessageHandler has to be registered since your s3UploadMessageHandler() is not a #Bean.
When you use it as a lambda, the framework treats it as a POJO method invocation and there is a bean registered for the MethodInvokingMessageHandler, but not your S3MessageHandler.
Anyway, even if you change your s3UploadMessageHandler() to be a #Bean method it is not going to work because you don't let the framework to call the S3MessageHandler.handleMessage(). What you do here is just call that private method at runtime to create an S3MessageHandler instance against every request message: the MethodInvokingMessageHandler calls your lambda in its handleMessage() and that's all - nothing is going to happen with S3.
The ValueExpression cannot help you here because you need to evaluate a destination file against every single request message. Therefore you need a runtime expression. There is indeed nothing wrong with the new SpelExpressionParser().parseExpression(). Just because we don't have a choice and have to have only single stateless S3MessageHandler and don't recreate it at runtime on every request like you try to achieve with that suspicious lambda and ValueExpression.
Related
I'm on learning phase of Spring boot
I've code where its written like below to handle exception in whole application. Not sure how its working, but I have NoDataFoundException class in code and its being used at place where no data found issues are happening.
#ControlAdvice
class ControllerAdvisor {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(NoDataFoundException.class)
#ResponseBody
public ResponseEntity<Object> handleNodataFoundException(
NoDataFoundException ex, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("message", "No cities found");
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}
Want to know, how and when handleNodataFoundException method automatically gets called when NoDataFoundException instance gets created ?
Does spring calls this method handleNodataFoundException on the basis of #ExceptionHandler(NoDataFoundException.class) which is bind to method itself and moreover irrespective of name of the method ?
how spring looks for parameters required for above method ? what if it has more parameters in it ?
This is done by proxying (Proxy.class).
Proxying is a mechanism in which a kind of pseudo class is created dynamically and mimics your own class (same methods) and is used to intercept all the method calls. This way it can act before and after the method calls as it please, and in the middle call the real method that you developed.
When you create a #Service in Spring, or a #Stateless in EJB, you never actually create the instances, you delegate the instance creation on the framework (Spring or EJB). Those frameworks proxy your classes and intercept every call.
In your case, and putting it simple, the proxy has a catch around the real call to your method, that catch captures exceptions, and acts upon the framework configuration built based on all the annotations that you created (#ControlAdvice, #ExceptionHandler and so on). And so it can call the handleNodataFoundException(...) in the cases that you defined with annotations.
Update visualization via stacktrace
For instance, if you have two Spring #Component (or #Service, #Repository or whatever), and one calls the other one in a plain call, and you get an exception, in the stacktrace you see plenty of method calls (involving all kind of different classes) between your two component classes, all those are proxies and framework classes that take care of proxying, invoking, configuration and all the magic that the framework does. And everything is triggered by the proxy of your second component just before calling the real code that you developed, because the first component, at execution time, doesn't really call an instance of your class, but an instance of the proxy of your class that the framework created.
If you run a plain main, with two classes calling one to the other, instantiated by you with new, you will only see 3 lines in the stacktrace, because there the instances are plain instances created by you.
Long story short: I need to have something like below.
PublishSubscribeChannel firstChannel = new PublishSubscribeSpec(executor).subscribe(subFlow -> ...).get();
Is there a way to create a pubsub channel with subflows which is not (yet) connected to any other flow?
The snippet is not working because of PublishSubscribeSpec(Executor) has protected access in PublishSubscribeSpec.
I will need to register channels like this dynamically without any information about which flow(s) will be using these channels.
has protected access in PublishSubscribeSpec
That was exactly a reason to make it protected - to avoid an unusual configuration problem like your. The subflow cannot be provided like this in the plain PublishSubscribeChannel definition. It is part of Java DSL parser in the framework to determine such a configuration and register respective beans in the application context. With that explicit get() call you just fully eliminate a hook for Java DSL parser to understand your configuration.
without any information about which flow(s) will be using these channels.
That's not true according your .subscribe(subFlow -> intention. Adding a subflow to the PublishSubscribeSpec is indeed "an information which flow will be using these channel".
Perhaps we need to look into your business requirement from another angle. There is no reason to be stuck with subflows approach when we simply can use a PublishSubscribeChannel from any other place where a MessageChannel is needed as an input. I mean if you just create a plain PublishSubscribeChannel and then use it for example for the IntegrationFlows.from(MessageChannel) factory, you'll get the same runtime result as you would expect from those .subscribe(subFlow -> connections.
I am building a service with RestAPI's. I want to put a custom annotation as mentioned below.
#CustomAnnnotation
public APIResponse apiMethod(APIRequest request) {
}
Functionality of this custom annotation :
Whenever there is a request to this apiMethod, before the execution of this method , i want to call an API in different server with some of the request parameters from this function. Example mentioned below. Basically for every method invocation i want to call a different server.
Instead of doing this
public APIResponse apiMethod(APIRequest request) {
newServiceClient.newAPI(request.getName())
}
I want to do this functionality by using a custom annotation. I know that i can use interceptors to intercept this request and call the API. Is there any other way ?
Edit :
To summarise this question. There is a method(This might not be API start point. It can also be a normal method in your application) in my java code. Whenever i annotate this method, for every invocation of this method in application, i want do some functionality. I want to have this in annotation because i am thinking of providing a library for this annotation so that any function can be annotated
I am creating a very basic controller using Kotlin with javax.ws and retrofit libraries.
I created a controller like this...
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
suspend fun sayHello(request: StudentRequest): StudentResponse {
that basically calls another service.
But when I run the app I get this error:
[FATAL] Method public final java.lang.Object MyResource.sayHello(StudentRequest,kotlin.coroutines.Continuation) on resource class MyResource contains multiple parameters with no annotation. Unable to resolve the injection source.;
handlerConstructors=[org.glassfish.jersey.server.model.HandlerConstructor#a0bf272]},
definitionMethod=public final java.lang.Object my.org.package.MyResource(sayHello,k**otlin.coroutines.Continuation**),
the weird part is that are couple of similar posts Jersey #PathParam : contains multiple parameters with no annotation
How can I pass multiple parameter to restful webservice using http post
https://github.com/dropwizard/dropwizard/issues/1115
but are not the same because my problem is with my ONLY parameter
There is no missing tag to my body request and I basically dont know what to look for at this point, any idea what could be wrong with this?
After debugging I noticed that there are two parameters, mine and one injected by Kotlin, when removing the "suspend" everything works fine, but then I am not able to make my async call.
To use coroutines from blocking code you need to use coroutine builder (e.g. launch {} or runBlocking {}).
Unfortunately in this case you can't just mark your glassfish controller as a suspendable function because framework don't know how to deal with continuations.
I want to build an Apache camel application to download a Jira
issue report, parse it, and store it into a .csv file.
I'm new at Apache camel, I do believe the jira here should be an endpoint, how to setup this configuration, I want to set is as from:("Jira") to (csv file).
I believe it could be something like this:
public void configure() throws Exception {
from("jira://pullRequestComment?ProjectKey=CAMEL-0000&IssueTypeId=1&IssueSummary=title")
.process(new MyLogProcessor())
.bean(new MyTransformer(),"transformContent")
.to("file:D:/camel/output");
}
I tried the above code, I got an exception for java conversion type.
Exception:
The JIRA component returns Java objects from the JIRA REST API. You need to either:
Support passing in the object type to your processor class as a method argument
Convert the JIRA Java Object to something else, then pass into your processor
BTW- The JIRA component caches "seen" data to know what is "new" to pass into the route. For really busy JIRA servers, this looks and acts like a memory leak so you'll need to be mindful to manage that scenario
The pullRequestComment endpoint is for a producer endpoint (i.e. it can only go in to("jira:pullRequestComment?..."). Since you want to poll for new comments, you should use the newComment endpoint. So your route would look something like:
from("jira:newComment?serverUrl={host}&username={username}password={password}")
.process(new MyLogProcessor())
.bean(new MyTransformer(),"transformContent")
.to("file:D:/camel/output");
Note that this endpoint returns an object of type com.atlassian.jira.rest.client.domain.Comment, so in MyLogProcessor if you do exchange.getIn().getBody(), it will return an object of type Comment (or maybe a List if there are multiple objects, you'll have to test this).
If you want to post a pull request comment, then you can use the pullRequestComment endpoint like the following:
from("direct://some/uri/name")
.header("ProjectKey", "CAMEL-0000")
.header("IssueTypeId", 1L)
.header("IssueSummary", "title")
.to("jira:pullRequestComment?serverUrl={host}&username={username}password={password}")
.... // More processing here
Then if you invoke the route from("direct://some/uri/name"), it should post the comment that's in the exchange body.