HTTP Outbound Gateway - Passing URI parameters - spring-integration

I need to invoke a REST API via Spring Integration's HTTP outbound gateway. How do I substitute the path variable with a value from payload. Payload has just one String value. The following code snippet is sending the place holder as such. Any help is appreciated.
#Bean
public MessageHandler httpGateway(#Value("http://localhost:8080/api/test-resource/v1/{parameter1}/codes") URI uri) {
HttpRequestExecutingMessageHandler httpHandler = new HttpRequestExecutingMessageHandler(uri);
httpHandler.setExpectedResponseType(Map.class);
httpHandler.setHttpMethod(HttpMethod.GET);
Map<String, Expression> uriVariableExp = new HashMap();
SpelExpressionParser parser = new SpelExpressionParser();
uriVariableExp.put("parameter1", parser.parseExpression("payload.Message"));
httpHandler.setUriVariableExpressions(uriVariableExp);
return httpHandler;
}

Let take a look what #Value is for first of all!
* Annotation at the field or method/constructor parameter level
* that indicates a default value expression for the affected argument.
*
* <p>Typically used for expression-driven dependency injection. Also supported
* for dynamic resolution of handler method parameters, e.g. in Spring MVC.
*
* <p>A common use case is to assign default field values using
* "#{systemProperties.myProp}" style expressions.
Looking to your sample there is nothing to resolve as a dependency injection value.
You can just use:
new HttpRequestExecutingMessageHandler("http://localhost:8080/api/test-resource/v1/{parameter1}/codes");
Although it doesn't matter in this case...
You code looks good, unless we don't know what your payload is.
That payload.Message expression looks odd. From big height it may mean like MyClass.getMessage(), but it can't invoke the getter because you use a property name as capitalized. If you really have there such a getter, so use it like payload.message. Otherwise, please, elaborate more. Some logs, StackTrace, the info about payload etc... It's fully unclear what is the problem.

Related

how to map from JaxbElement<X> to JaxbElement<Y> with MapStruct

I was going to create an issue on github, but the issue template says I'd rather discuss it here first, so here we go:
I am trying to use Mapstruct to generate mappings from one WSDL-generated set of entities to another. However, without adding a "default" method (manual mapping) it does not work ! That seems to be strange, as I would expect this kind of mapping should not be difficult for Mapstruct. repro case is here: https://github.com/62mkv/wsdl-mapstruct-repro-case
the gist of the code is here:
#Mapper(uses = {
org.system.wsdl.legacy.ObjectFactory.class
})
public interface WsMapper {
org.system.wsdl.legacy.SellGarlicRequest fromCloud(org.system.wsdl.cloud.SellGarlicRequest request);
}
this code above will fail to compile, with such message:
Can't map property "javax.xml.bind.JAXBElement inputParameters" to "javax.xml.bind.JAXBElement inputParameters". Consider to declare/implement a mapping method: "javax.xml.bind.JAXBElement map(javax.xml.bind.JAXBElement value)". org.system.wsdl.legacy.SellGarlicRequest fromCloud(org.system.wsdl.cloud.SellGarlicRequest request);
basically, mapping would go as follows: EntityNew -> JaxbElement -> FieldNew -> FieldOld -> JaxbElement -> EntityOld
as I've read here (https://stackoverflow.com/a/46015381/2583044), mapping from JaxbElement to T is trivial for MapStruct, and to map from T to JaxbElement one has to use "uses" annotation and provide ObjectFactory.class, which I do; however... this seems to not be enough.
if I add these two methods, code compiles good:
org.system.wsdl.legacy.GarlicParameterCollection garlicParameterCollectionToGarlicParameterCollection(org.system.wsdl.cloud.GarlicParameterCollection collection);
default JAXBElement<org.system.wsdl.legacy.GarlicParameterCollection> garlicParameterCollectionToGarlicParameterCollection(JAXBElement<org.system.wsdl.cloud.GarlicParameterCollection> parameterCollectionJAXBElement) {
return new org.system.wsdl.legacy.ObjectFactory().createSellGarlicRequestInputParameters(
this.garlicParameterCollectionToGarlicParameterCollection(parameterCollectionJAXBElement.getValue())
);
}
is it a potential issue in mapstruct or I just don't know how to cook it well?
The problem is that MapStruct sees your object factory method (with argument) as a mapping method. So, it delivers a target, but it has a source as well. If you realise this, then the mapping suddenly is not symmetrical (as it initially appears).
The simple solution is to instruct MapStruct how to handle this.
So: try this:
#Mapper(uses = {
org.system.wsdl.legacy.ObjectFactory.class
})
public interface WsMapper {
org.system.wsdl.legacy.GarlicParameterCollection garlicParameterCollectionToGarlicParameterCollection(org.system.wsdl.cloud.GarlicParameterCollection collection);
#Mapping( target = "inputParameters", source = "inputParameters.value") // this instructs MapStruct to use value of the source JAXBElement (for which it has an object factory method) instead of trying to map JAXBElement to JAXBElement.
org.system.wsdl.legacy.SellGarlicRequest fromCloud(org.system.wsdl.cloud.SellGarlicRequest request);
}
Last but not least, you need to define the first method garlicParameterCollectionToGarlicParameterCollection which surprised me initially.
The reason: MapStruct either tries to:
1. find a mapping method (which is not there if you leave this one out)
or
2. tries to generate a direct mapping (by inspecting if it can find methods for all the attributes on source and target).
However, MapStruct cannot find a direct case for this mapping (it would in principle needs to apply all other possible mappings on its path (e.g. all the methods in the object factory) and then try to generate a mapping method as explained in 2, which could be a lot of combinations. That functionality is not there (and would be load intensive as well I guess).

Adding ConversionService to Spring Cloud Stream

I'm using Spring Integration and String Cloud Stream. I have a header that I want my HTTP gateway to use, which has a Long value, but it can't convert from Long to String by default and so displays the error Consider registering a Converter with ConversionService.
Therefore I tried adding my own LongToStringConverter class and the following Bean so that LongToStringConverter can be used:
#Bean
public ConversionService conversionService()
{
DefaultConversionService service = new DefaultConversionService();
service.addConverter( new LongToStringConverter() );
return service;
}
Then then received the following error: Dispatcher has no subscribers.
If I only return an instance of DefaultConversionService from the above bean I still receive the error.
When I remove the above bean and instead simply convert the Long value to String when setting the header value and that works without errors. Is it possible to use ConversionService instead? If so then how?
First of all there is already a ConversionService: https://docs.spring.io/spring-integration/docs/4.3.12.RELEASE/reference/html/messaging-endpoints-chapter.html#payload-type-conversion. And it has some set of predefined converters. So, you should consider to use #IntegrationConverter on the matter.
On the other hand it is unclear why do you need to do that at all. I wonder why Long.toString() isn't enough for you when you declare that header in first place.

Design: Spring Integration jdbc best practice

After using Spring Integration in a project, my observation is to use jdbc adapter or gateway only at the start or end of the flow. If we use them in the middle of flow then it will become too verbose and complex.
For example:
<jdbc:outbound-gateway
query="select * from foo where
c1=:headers[c1] AND
c2=:headers[c2] AND
c3=:headers[c3] AND
c4=:headers[c4]"
row-mapper="fooMapper" data-source="myDataSource" max-rows-per-poll="100000" />
<int:service-activator ref="serviceActivator" method="processFoo" />
In the above <jdbc:outbound-gateway>, we need to pass all the placeholders (c1, c2, c3, c4) in the header of Message. We need to look back and forth in java code and xml file for any change in where condition or when there are too many where clauses.
It is also error prone. For example, if we misspelled :headers[c1] to :headers[d1] then it will not throw any exception and replace :headers[d1] with null.
If query does not return any row then it will throw exception by default. So, we have to use requires-reply="false" to change default behaviour.
If we want to proceed when query does not return any value then we have to add advice to gateway, as shown below:
<jdbc:outbound-gateway ... >
<jdbc:request-handler-advice-chain>
<bean class="com.service.NullReplyAdvice" />
</jdbc:request-handler-advice-chain>
</jdbc:outbound-gateway>
Please correct me if there are flaws in understanding of the concept.
We need to look back and forth in java code and xml file for any change in where condition or when there are too many where clauses.
It's true even for raw Java code around the JDBC: if you change the model you, of course, should change the SELECT, because it is just a String. And that's why there is a lot of work to make it type-safe - ORM, QueryDSL, Spring-Data etc.
if we misspelled :headers[c1] to :headers[d1] then it will not throw any exception and replace :headers[d1] with null.
That's because the headers is just a Map and it's truth that you get null, if there is no such a key in the map. To overcome that typo issue you can use POJO payload with getters, or some custom header, and again - the POJO with getters. In this case you end up with exception that there is no such a property against object. Although you'll see that issue only at runtime, not on compile. And again the same is with Hashtable - only at runtime.
So, we have to use requires-reply="false" to change default behaviour.
You should understand it at design time: allow or not to return nothing for the component.
The last idea is good. Wouldn't you mind to share your NullReplyAdvice?
Actually I achieve the same with <filter> before the JDBC gateway: to determine if there is something to fetch by count(*) query. From there I can lead my flow to the different logic, rather than the direct flow, when SELECT returns rows.
UPDATE
When you want to use Model object to keep business-specific values within Message, it's just enough to put this object to the header:
public class Foo {
private String foo1;
private String foo2;
public String getFoo1() {
return foo1;
}
public String getFoo2() {
return foo2;
}
}
...
MessageBuilder.withPayload(payload).setHeader("foo", foo).build();
...
<jdbc:outbound-gateway
query="select * from foo where
c1=:headers[foo].foo1 AND
c1=:headers[foo].foo2"/>

JsConfig<MyClass>.ExcludePropertyNames example, not working for me

Trying to exclude properties from a model from being included during serialization.
I am using the following syntax:
JsConfig<MyTestClass>.ExcludePropertyNames = new[] { "ShortDescription" };
Just after that I have the following:
return (from o in __someProvider.GetAll() select (new
{
o.Name,
o.ShortDescription
o.InsertDate
}).TranslateTo<MyTestClass>()).ToList()
However once result is returned from the method, it still contains "ShortDescription" field in the Json. Am I doing something wrong?
JsConfig<T>.ExcludePropertyNames appears to be checked only once for each type, in a static constructor for TypeConfig<T>. Thus, if you are configuring ExcludePropertyNames in your service class, just before returning your response, it might be too late -- the TypeConfig properties may already be set up and cached for MyTestClass. I was able to reproduce this.
A more reliable alternative is to move all of your JsConfig<T> configuration to your AppHost setup code.
If you really do need to do this in your service class, e.g. if you are only conditionally excluding property names, then an alternative approach would be to ensure that JsConfig.IncludeNullValues is false (I believe it is by default) and in your service code set ShortDescription to null when appropriate.

Unit test rest service without specifying URL

Using servicestack, there are examples of unit testing using types, etc. Here is an example:
GetFactorial
I would like to test my REST style service with a test similar to the above.
Here is an example REST unit test FileService
Notice how in the PUT unit test, the Path argument has to be specified in the URL text instead of in the class argument. Another example is here, where we have perfectly good request models that have to be translated into the URL. For testing, I would like to get away from having to build the arguments in the url and use a system similar to the one above like this:
var response = restClient.Put<FilesResponse>(new Files { TextContents = ReplacedFileContents, Path = "README.txt" });
or
var singleCustomer = restClient.Get<Customer>(new Customer {Id=1};
Is this possible?
Then there is the DirectServiceClient. Would that help? In the end, with servicestack, we get to write services and they can be called from many different type clients - I would like to write my unit test like that.
Is this possible?
If you decorate your DTOs with the route variable and use ServiceStack's "New API" then it can discover the routes automatically. You can get away with writing very minimal code and still get a strong typed rest API.
Your code could look something like this:
Customer singleCustomer = restClient.Get(new Customer {Id=1});
See https://github.com/ServiceStack/ServiceStack/wiki/New-Api
In response to your comments, your DTO needs to adhere to the IReturn interface:
[Route("/customer/{Id}")]
public Customer : IReturn<Customer> {
public int Id {get;set;}
}
The IRestClient interface below will now be able to work with your DTO without specify the type since it is expecting an IReturn object.
public interface IRestClient
{
TResponse Get<TResponse>(IReturn<TResponse> request);
...
}

Resources