How does json String to Object auto conversion works in Spring cloud Stream? - apache-kafka-streams

I am looking at this example - https://github.com/spring-cloud/spring-cloud-stream-samples/blob/master/kafka-streams-samples/kafka-streams-product-tracker/src/main/java/kafka/streams/product/tracker/KafkaStreamsProductTrackerApplication.java
Trying to do something similar, but for me it is not working. How does product json string is received as Product Object ?

By default, the deserialization on the inbound KStream is done by Spring Cloud Stream. The default content-type used is application/json (equivalent to providing the property: spring.cloud.stream.bindings.input.contentType: application/json). This is why the product json string is properly converted.
You can disable the framework level conversion and let Kafka do that in which case you need to provide the Serdes through properties. In order to enable native deserialization, you can set the property - spring.cloud.stream.bindings.input.consumer.useNativeDecoding: true. Then you need to provide the appropriate Serdes. More on all these are here: https://docs.spring.io/spring-cloud-stream/docs/Elmhurst.BUILD-SNAPSHOT/reference/htmlsingle/#_message_conversion

Related

Conditional (content-based) routing on Kafka headers in Spring Cloud Stream

I'm using Spring Cloud Stream 3.x in Spring Boot 2.x application to consume messages from a Kafka topic.
I want to have a listener that consumes messages conditionally on some custom header value, as per doc:
#StreamListener(value = "someTopic", condition = "headers['SomeHeader']=='SomeHeaderValue'")
public void onMessage(Message<?> message) {
LOGGER.info("Received: {}", message);
}
However listener never gets notified, and if condition is removed I see the following in the log:
Received: ... SomeHeader: [B#1055e4af ...
It turns out that custom headers are left in Kafka byte array raw format, making them not eligible for condition evaluation.
Is some additional configuration needed or am I missing something?
After some digging in sources and stackoveflow I have found the following:
Spring Cloud Stream delegates to Spring Kafka message and headers conversion (KafkaMessageChannelBinder ~ getHeaderMapper)
Headers are left in raw format by default headers conversion implementation (BinderHeaderMapper)
Spring Cloud Stream allows customization of headers mapping and particularly conversion of headers from byte array to String (How can I map incoming headers as String instead of byte[] in my Spring Cloud Stream project?)
So I added my custom header mapper bean (bean name is important, it allows to omit additional configuration property), which maps my custom header to String:
#Bean
public KafkaHeaderMapper kafkaBinderHeaderMapper() {
SimpleKafkaHeaderMapper headerMapper = new SimpleKafkaHeaderMapper();
headerMapper.setRawMappedHeaders(Map.of(
"SomeHeader", true
));
return headerMapper;
}
That fixed the problem:
Received: ... SomeHeader: SomeHeaderValue ...
P.S. It seems like a bug in Spring Cloud Stream:
It introduces its own implementation of header mapper (BinderHeaderMapper), but the latter doesn't respect conditional routing feature.
Header mapper is subclassed in KafkaMessageChannelBinder, this added behaviour is non-obvious and will be lost if custom header mapper is provided.

How to validate request against XSD and return an error object?

My task is to implement a webservice that:
consumes an XML file on a POST endpoint
in happy flow, it returns a DTO as JSON + HTTP 2xx
the incoming XML file is validated against a XSD; if the validation fails, a JSON with a list of all validation errors is returned (including the line, column, error) with HTTP Bad request
the application exposes two endpoints, only one of them should be validated
I have started the implementation with Spring Boot + web, using regular #PostMapping which has "consumes" and "produces" set to application/xml and application/json, respectively. The usual flow works perfectly fine. Now, I stumbled upon the issue of validating the incoming payload. What I figured out:
1) I have to validate the payload before it is converted (marshalled) to an object.
2) Once validated, I have to either:
allow further processing
stop any further processing, write the error object to the response and set the status code to 400 Bad request
My approaches were:
1) using a RequestBodyAdvice, more specifically the beforeBodyRead method implementation. I had the following issue here: I don't know how to write anything to the output in case the validation fails.
2) using a Filter (I've extended OncePerRequestFilter) - fortunately, I can read the request (request.getInputStream()) and write to the response (response.getOutputStream()).
However, how can I do the selective filtering (as mentioned, I only want to validate one single endpoint)?
Are there any other alternatives for placing the incoming request XSD validation? Is spring-web the appropriate choice here? Would you recommend some other library / framework?
To validate xml against xsd schema, my preference is XML Beans. It is very easy to use. Other options are JABX, Castor. Take a look at Java to XML conversions?.
You will need to jar using xsd schmema and will need to put it in the classpath of your application so that it's classes are available for you for validation. Please take a look at this blog.
You can use validation API as mentioned here.
I would prefer to write validation code in the aspect so that it can be reused with other APIs.
If validation fails, throw valid exception from the aspect itself.
If validation is passed, process your input string that you receive.
Please let us know if you need any more information.

Can I get saml-token as string?

I am using spring-security-saml2 1.0.0.RELEASE.
It works well and pretty good for me.
But New requirement is rised. I need saml-token as string.
can I get the saml-token as string. I find saml-token in log.
But how to get the saml-token as string format?
Good question, I've just added a new chapter to the Spring SAML manual which addresses this issue:
Authentication assertion
Assertion used to authenticate user is stored in the
SAMLCredential object under property authenticationAssertion. By
default the original content (DOM) of the assertion is discarded and
system only keeps an unmarshalled version which might slightly differ
from the original, e.g. in white-spaces. In order to instruct Spring
SAML to keep the assertion in the original form (keep its DOM) set
property releaseDOM to false on bean WebSSOProfileConsumerImpl.
Assertion can be serialized to String using the following call:
XMLHelper.nodeToString(SAMLUtil.marshallMessage(credential.getAuthenticationAssertion()))

How to specify content type produced by a handler in Spring Messaging?

I'm experimenting with Spring 4 WebSocket STOMP application. Is there a way to explicitly specify content type of the returned message produced by a handler? By default the handler below produces application/json and processed by corresponding message converter.
#Controller
public class ProductController {
#MessageMapping("/products/{id}")
public String getProduct(#DestinationVariable int id) {
return getProductById(id);
}
}
I'm looking for something like #RequestMapping(produces = "text/xml") in Spring MVC.
UPDATE (reply to Rossen's answer):
Ideally I would like to be able to return both formats depending on what the user asks for. But if I have to choose, I would say XML and almost never JSON (XML is just an example, we use binary format). I went the second way you suggested - configuring custom converters instead of the default ones.
I have implemented custom MessageConverter extending AbstractMessageConverter. In the constructor I've registered appropriate supported MimeType.
Then I've registered my custom converter by overriding WebSocketMessageBrokerConfigurer's configureMessageConverters, and return false from the method to not add default converters.
As soon as my controller returns the value I get NPE in SendToMethodReturnValueHandler's postProcessMessage. This happens because CompositeMessageConverter contains only a single converter - my custom one. But my converter fails AbstractMessageConverter's supportsMimeType check and AbstractMessageConverter's toMessage returns null. This null causes the exception in postProcessMessage.
As the workaround I can register additional default MimeType application/json for my custom converter. But it looks like too dirty to me.
There is nothing like the produces condition. The only way to do this right now is to inject SimpMessagingTemplate and then use the convertAndSend method variation that takes a MessagePostProcessor. It could be made simpler. What's your use case? Are you using JSON primarily and need to use XML in a few places? Or do you need to use XML primarily? If it is the latter you can simply configure the converters to use instead of the default ones.
I see that you can change the headers changing the Message converter.
For example:
SockJsClient sockJsClient = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(sockJsClient);
..
stompClient.setMessageConverter(new StringMessageConverter());
or
stompClient.setMessageConverter(new SimpleMessageConverter());
when you use StringMessageConverter then the Java client add an content-type header.
GenericMessage [payload=byte[33], headers={simpMessageType=MESSAGE, stompCommand=SEND, nativeHeaders={destination=[/app/chatchannel], content-type=[text/plain;charset=UTF-8], content-length=[33]}, simpSessionAttributes={ip=/127.0.0.1:59629}, simpHeartbeat=[J#147a2bf, contentType=text/plain;charset=UTF-8, lookupDestination=/chatchannel, simpSessionId=79431feb8b5f4a9497492ccc64f8965f, simpDestination=/app/chatchannel}]
But if you use SimpleMessageConverter, the content-type header is not add.
this is a clue about what you want to do?
Check this question, I put some code that could help you:
How configure the Spring Sockjs Java Client message converters

Set default response type in WCF Web Api

I've a set of services hosted with WCF Web Api and I communicate with them in JSON from javascript. In most cases I'm okay modifying the accepts bit of the header to require a JSON response but there are some cases arising where I can't do this. This is due the the javascript framework that I'm using (Ext JS). For some things it only lets me specify a URL and not the proxy defaults such as headers.
This isn't an Ext JS question however. Web Api seems to default to returning XML, and I'd like to know whether it's possible to change this default so that it can return JSON instead. Thanks in advance!
A bit of experimentation seems to indicate that the order of the configured formatters matter (which is quite intuitive).
By default, when you create an instance of HttpConfiguration, its Formatters collection contains these formatters:
XmlMediaTypeFormatter
JsonValueMediaTypeFormatter
JsonMediaTypeFormatter
FormUrlEncodedMediaTypeFormatter
The reason why XML is the default formatting is because it's the first formatter. To make JSON the default value, you can reorder the collection to look like this:
JsonValueMediaTypeFormatter
JsonMediaTypeFormatter
XmlMediaTypeFormatter
FormUrlEncodedMediaTypeFormatter
Given an instance config of HttpConfiguration, here's one way to reorder the collection:
var jsonIndex = Math.Max(
config.Formatters.IndexOf(config.Formatters.JsonFormatter),
config.Formatters.IndexOf(config.Formatters.JsonValueFormatter));
var xmlIndex = config.Formatters.IndexOf(
config.Formatters.XmlFormatter);
config.Formatters.Insert(jsonIndex + 1, config.Formatters.XmlFormatter);
config.Formatters.RemoveAt(xmlIndex);
Whether or not this is supported I don't know, but it seems to work on WebApi 0.6.0.
I actually found a simple way of dealing with this. First make sure that the default JSON formatter is first. And then set its type to text/html. This will insure that the browser gets JSON even if it does not set the header. Nice aspect of the below is that you never have to remember to set the accept header in client code. It just works and always default to JSON.
var jsonformatter = config.Formatters.Where(t => t.GetType() == typeof(JsonMediaTypeFormatter)).FirstOrDefault());
config.Formatters.Remove(jsonformatter );
config.Formatters.Insert(0, jsonformatter);
config.Formatters[0].SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
You could use the a delegating channel as described here http://blog.alexonasp.net/post/2011/07/26/Look-Ma-I-can-handle-JSONP-(aka-Cross-Domain-JSON)-with-WCF-Web-API-and-jQuery!.aspx which maps URIs like http://myserver/myresource/1/json to http://myserver/myresource/1 and sets accept header to application/json.
The delegating channel is part of the ContactManager_Advanced sample when you're downloading WCF Web API from http://wcf.codeplex.com.
It is contained in the UriFormatExtensionMessageChannel.cs file.
Look at the global.asax.cs of the sample on how to get it running.
According to the code the WCF Web API will always default to the XmlFormatter if it is in the collection of usable formatters. If it isn't the JsonFormatter is used instead if this is present. There is also a DefaultFormatter property but that is internal so you can't set that. Maybe a useful feature request to add?

Resources