spring-projects / spring-integration-samples

You are looking for examples, code snippets, sample applications for Spring Integration? This is the place.

Home Page:http://www.springsource.org/spring-integration

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Payload Enricher with request-payload-expression="payload.username"

roro0290 opened this issue · comments

While trying out the enricher example by using Java DSL, I am unable to replace the payload that contains User object with only username field using the method:
.enrich(p -> p.requestPayloadExpression("payload.username"))
--> https://docs.spring.io/spring-integration/reference/html/content-enrichment.html#payload-enricher

However, the payload functionality works when I use the transform method that takes in an expression parameter
.transform("payload.username")

Is the transform method the only way to update the payload?

Additionally, while trying out the use case 3, I notice that the XML does not do any explicit transformation between the returned User object and the User map. However, with the Java DSL, I am getting the below error. Would you be able to suggest on this payload transformation?

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.spring.integration.basic.enricher.User] to type [java.util.Map<?, ?>]

Referred XML:

<int:enricher id="findUserWithMapEnricher"
                  input-channel="findUserWithMapEnricherChannel"
                  request-channel="findUserByUsernameServiceChannel"
                  request-payload-expression="payload.username">
        <int:property name="user"    expression="payload"/>
    </int:enricher>

Would you mind to the share with us a the whole configuration for this task and explain what you try to achieve?

It feels like you are missing the reasoning behind an Enricher pattern: https://www.enterpriseintegrationpatterns.com/patterns/messaging/DataEnricher.html.

In two words it does this: takes some additional data and populates it as an addition to the request data.

To be more specific here is what is going on in that findUserWithMapEnricher sample:

  1. It received some POJO from a findUserWithMapEnricherChannel
  2. Performs a request-payload-expression taking just a username property value from that POJO
  3. Creates a message based on a payload from step 2 and send it to that findUserByUsernameServiceChannel
  4. When replies arrives, it sets its payload into a user property of that request POJO.
  5. A reply message with modified POJO is produced from this component.

The Java DSL for this XML might look like this:

.enrich(e -> e
		.requestChannel("findUserByUsernameServiceChannel")
		.requestPayloadExpression("payload.username")
		.propertyExpression("user", "payload"))

There is no automatic conversion to a Map.

Example#2: Pass only the username to the request channel by using the request-payload-expression attribute

Implementation: Enrich the payload with the username -> send to a handle method (Service Activator)

Bean
    IntegrationFlow findUserByUsernameEnricherChannel(){
        return f -> f
                .enrich(p -> p.requestPayloadExpression("payload.username"))
                .handle(systemService,"findUserByUsername");
    }```

```public User findUserByUsername(String username) {

        LOGGER.info(String.format("Calling method 'findUserByUsername' with parameter: %s", username));

        return new User(username, "secret", username + "@springintegration.org");

    }```

However, from the debug of this code, I can see that username is passed as User [username=foo, password=moo, email=fooMooEmail]

thus resulting in an output of: 
**Expected** :User [username=foo, password=secret, email=foo@springintegration.org]
**Actual**   :User [username=User [username=foo, password=moo, email=fooMooEmail], password=secret, email=User [username=foo, password=moo, email=fooMooEmail]@springintegration.org]

The expected output could be achieved when I used the transform method together with a SPeL expression. But as you mentioned, this defeats the purpose of the enricher where we should add on to the object instead of modifying the existing payload 

```Bean
    IntegrationFlow findUserByUsernameEnricherChannel(){
        return f -> f
                .transform("payload.username")
                .handle(systemService,"findUserByUsername");
    }```

Your enrich() doesn't call that findUserByUsername.
You also don't show what is an input payload.
And I don't see to what property you try to set a result of the enrichment.
If your goal is to fully replace the payload for downstream process, then indeed a transform() is the right way.

Yeah... You definitely need to learn what is that Enricher pattern.
Right now it looks like you trying to do something with it which is essentially a transform(): you just take a property from the payload and try to produce it as a reply: there is just no any enrichment in your current configuration.
No any property to populate, so a request payload is produced as a reply without any changes.

Based on your comments, I used the requestSubFlow() method to handle the sample payload

@Bean
    public IntegrationFlow practiceFlow(){
        return f -> f
                .enrich(enricherSpec -> enricherSpec
                        .requestPayloadExpression("payload.username") // use the value of the username as the payload of this message
                        .requestSubFlow(flow -> flow
                                .log()
                                .handle(String.class, (payload,headers)->new User(payload,"password","email"))
                                .enrichHeaders(h -> h.headerExpression("email","payload.email"))
                                .enrichHeaders(h -> h.headerExpression("password","payload.password"))
                                .log()
                        )
                )
                .log()
                .channel("outputChannel");
    }

Based on this I observe the below changes in payload through the flow but am unable to get any message in the main flow. Is there still some issue in the implementation?

  • Input payload: User [username=foo, password=null, email=null]
  • After requestPayloadExpression: payload=foo
  • After requestSubFlow: User [username=foo, password=password, email=email]
  • In the main flow (outputChannel): No available message (test case is loading)

You still use enrich() a wrong way.
You are fully missing its purpose: you request some extra info and you have to populate it into the output.
There is no any propertyExpression() or headerExpression() for your enricherSpec.
What you are doing now is fully out of enricher scope.
With your current logic you could just do:

@Bean
    public IntegrationFlow practiceFlow(){
        return f -> f
                .log()
                .handle(User.class, (payload,headers)->new User(payload.getUsername(),"password","email"))
                .enrichHeaders(h -> h.headerExpression("email","payload.email"))
                .enrichHeaders(h -> h.headerExpression("password","payload.password"))
                .log()
                .channel("outputChannel");
    }

Not sure if you will still need those enrichHeader() though...

After analyzing, the confusion started due to 2 issues:

  1. we pass a User object but the service method accepts a String username
  2. the service handle method must return the object to the main thread

Issue 1 is resolved by using the requestPayloadExpression since it can "send only a subset of the original payload by using the request-payload-expression attribute." - https://docs.spring.io/spring-integration/reference/html/content-enrichment.html#payload-enricher

I am unable to resolve issue 2. I get the response: User [username=foo, password=null, email=null]

I tried it together with replyChannel where the handle method should send the output to a channel on which the gateway will listen for a response

Gateway

@Gateway(requestChannel = "findUserByUsernameEnricherChannel.input", replyChannel = "findUserReplyChannel")
    User findUserByUsername(User user);

Service

@Bean
    public IntegrationFlow findUserByUsernameEnricherChannel() {
        return f->f
                .enrich((enricher) -> enricher
                                .requestChannel("findUserByUsernameServiceChannel")
                                .replyChannel("findUserReplyChannel")
                                .requestPayloadExpression("payload.username")
                )
                .handle(String.class,(p, h) -> SystemService.findUserByUsername(p));
    }
@Bean
    public IntegrationFlow findUserByUsernameServiceFlow() {
        return IntegrationFlows.
                from("findUserByUsernameServiceChannel")
                .handle(String.class,(p, h) -> SystemService.findUserByUsername(p))
                .channel("findUserReplyChannel")
                .get();
    }

If I provide the handle method directly, I get this as response: User [username=User [username=foo, password=null, email=null], password=secret, email=User [username=foo, password=null, email=null]@springintegration.org]

@Bean
    public IntegrationFlow findUserByUsernameEnricherChannel() {
        return f->f
                .enrich((enricher) -> enricher
                                .requestPayloadExpression("payload.username")
                )
                .handle(String.class,(p, h) -> SystemService.findUserByUsername(p));
    }

How do we ensure the output of the handle method goes back to the main flow?

Based on your earlier comments, I tried this and it worked. Would this be the right approach to this?

Output: User [username=foo, password=secret, email=foo@springintegration.org]

@Bean
    public IntegrationFlow findUserByUsernameEnricherChannel() {
        return f->f
                .enrich((enricher) -> enricher
                                .requestChannel("findUserByUsernameServiceChannel")
                                .requestPayloadExpression("payload.username")
                                .propertyExpression("username", "payload.username")
                        .propertyExpression("email", "payload.email")
                        .propertyExpression("password","payload.password")
                );
    }

the propertyExpression method is used to set values to the properties. If the property is not defined in the User object, it throws the error:
org.springframework.expression.spel.SpelEvaluationException: EL1010E: Property or field 'User' cannot be set on object of type 'com.spring.integration.basic.enricher.User' - maybe not public or not writable?

You need to step back and see if just a transform() is OK for you. Because from your explanation it sounds like you got a full User as a result of your service call.
That means you fully replace the request payload with a reply payload.
And therefore you don't need an enrich() since it doesn't fit into your service model.
The point of an enrich() is to add info into an existing data.
In your case you got the full data back, so you just simply can discard the quest and go on with that that reply.
What you have with those propertyExpression()s are fully an overhead since you got a full User object back.

What else would you expect if there is no getter for the property you request from SpEL?