Kafka Listener issues giving null values - java

I have below code in my kafka reciever listener class
#EnableKafka
#Service
#Component
#Slf4j
public class MessageListener{
#Autowired
#Qualifier("MessageBeanService")
#KafkaListenter(topic={"test-topic}, autoStartup="true" groupId="test-consumer-group")
#Transactional(propagation=Propagation.REQUIRES_NEW)
public void receiveMessage(#Payload String message) throws JsonProcessingException
ObjectMapper objMapper=new ObjectMapper;
objMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, FALSE);
CartSt msg=objMapper.readValue(messages, CartSt.class)
CartSt has sub objects like Item, Price, Quantity.
But i see those sub objects getting populated as null values.
ObjectMapper is from fasterxml.jackson.databind jar package.
How to resolve these issues and populate correct non null values for the sub objects Item. Price, Quantity of CartSt main object.
Please advise

Related

SpringBoot JmsListener: Ignore TypeId

I am currently working with SpringBoot JMS and I am a bit confused.
When I use the RestTemplate on the web the contract between sender and receiver is the JSON format. It doesn't matter how the sender generates the JSON (or from which class). That means the sender does not have to have the same DTO class as the receiver.
I assumed that the same would apply to JMS. Unfortunately, the sender and the receiver must have exactly the same class. I find that somehow impractical.
Of course I could just send a string (containing a JSON) on both sides. But I actually expect Spring to do this by itself. To keep the code simple I just want to tell Spring "here send this object as JSON". And on the other side just "parse that JSON into this object".
When I run the example below I get the following error message:
org.springframework.messaging.converter.MessageConversionException: Cannot convert from [com.example.demo.MyObject2] to [com.example.demo.MyObject] for org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#3c60bcf
Did I just not understand the principle or is my thought so absurd that it is not the "normal" way and you have to do a lot by hand?
Here is my code (Note: MyObject and MyObject2 have both just one attribute (String key)):
#SpringBootApplication
#EnableJms
public class DemoApplication implements CommandLineRunner {
#Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) throws IOException {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
#JmsListener(destination = "test")
public void test(MyObject dto) {
System.out.println(dto);
}
#Override
public void run(String... args) throws Exception {
MyObject2 obj = new MyObject2();
obj.setKey("Test me");
jmsTemplate.convertAndSend("test", obj);
}
}
See
/**
* Specify mappings from type ids to Java classes, if desired.
* This allows for synthetic ids in the type id message property,
* instead of transferring Java class names.
* <p>Default is no custom mappings, i.e. transferring raw Java class names.
* #param typeIdMappings a Map with type id values as keys and Java classes as values
*/
public void setTypeIdMappings(Map<String, Class<?>> typeIdMappings) {
on the converter.
On sending side, map MyObject.class from myObject and on the consumer side, map myObject to MyOtherObject.class.

How to provide converter service to POJO class without passing it as method argument

I have ShoppingList service which is responsible for generating shopping list and a IngredientConverter service which is a helping tool for converting objects. My current implementation looks like this
#Service
#AllArgsConstructor
public class ShoppingListService {
private final RecipeService recipeService;
private final IngredientConverter ingredientConverter;
public ShoppingList generateShoppingList(List<UUID> uuidsOfRecipes) {
List<Recipe> recipes = recipeService.getAllByIDIn(uuidsOfRecipes);
ShoppingList shoppingList = ShoppingList.empty();
for (Recipe recipe : recipes) {
shoppingList.addIngredients(recipe.getIngredients());
}
shoppingList.finishAddition(ingredientConverter);
return shoppingList;
}
}
#RequiredArgsConstructor
public class ShoppingList {
#Getter
private final List<IngredientQuantity> optimizedList;
private final Map<Ingredient, Integer> ingredientAmountMap;
public static ShoppingList empty() {
return new ShoppingList(new ArrayList<>(), new HashMap<>());
}
public void addIngredients(List<IngredientQuantity> ingredients) { ... }
public void addIngredient(IngredientQuantity ingredientQuantity) { ... }
public void finishAddition(IngredientConverter ingredientConverter) {
for (Ingredient ingredient : ingredientAmountMap.keySet()) {
IngredientQuantity ingredientQuantity = ingredientConverter.convertWithAmount(
ingredient.getName(),
ingredientAmountMap.get(ingredient),
ingredient.getUnit());
optimizedList.add(ingredientQuantity);
}
}
}
#Service
public class IngredientConverter {
public IngredientQuantity convertWithAmount(String name, int amount, Unit unit) { ... }
}
Is there a better strategy for providing IngredientConverter service to this class? Could I Autowire it somehow despite ShoppingList being POJO class? Should ShoppingList be marked as Component maybe? Not sure what is the best approach.
You cannot autowire service class into POJO. Autowire can be done only within spring managed classes. I can see that ShoppingList is not a spring managed class. Adding #Component will also not be ideal solution. AFAIK, The best solution to use here would be mapStruct. mapStruct can be used to map fields between entity and POJO. And in cases where any field has to be calculated separately, you can write your custom logic and autowire services. Below are steps
Add mapStruct library to pom.xml
Add below mapper class to your project. componentModel="spring" tells the system that this mapper is managed by spring.
All the fields that have same name will be automapped.
For fields which require conversions, you can write #BeforeMapping
Mapper(componentModel="spring")
public abstract class ShoppingListMapper
{
#Autowired
IngredientConverter ingredientConverter; //autowire method you use.
public abstract shoppingListToShoppingListDTO(ShoppingList shoppingList) throws Exception;
public abstract List<ShoppingList> mapShoppingListsToDTOs(List<ShoppingList> shoppingLists) throws Exception;
#BeforeMapping
public void convertLogic(ShoppingList la, #MappingTarget ShoppingListDTO slDto) throws Exception
{
//your logic to set required shoppinglist field using converter
}
}
If this example is not clear, you can refer to web for various mapstruct examples. Let me know if you need further help

Kafka JSON Deserializer for interfaces

I've got problem similar to this:
Kafka Deserialize Nested Generic Types
In my kafka producer I am sending object that looks like this:
public class ExternalTO implements Serializable
{
private static final long serialVersionUID = 7949808917892350503L;
private List<IExternalData> externalDatas;
public ExternalTO()
{}
}
The cornerstone is this: List<IExternalData> externalDatas.
This interface looks like:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public interface IExternalData
{
String getOne();
}
In my application there can by generated multiple types of IExternalBetData interface implementations (about 10 different). In this case, for instance, my producer generated ExternalTO with inner list of ConcreteExternalData objects. Sent JSON looks like:
{
"externalDatas":
[{"#class":"com.api.external.to.ConcreteExternalData",
"one":false,
"two":false}]
}
Field #class was added because of #JsonTypeInfo annotation, and I thought that this is enough for deserializer to "understend" what type of IExternalData to use in deserialization.
Unfortunately, on the side of kafka listener I am getting the exception:
Cannot construct instance of com.api.external.to.IExternalData (no
Creators, like default construct, exist): abstract types either need
to be mapped to concrete types, have custom deserializer, or contain
additional type information
Consumer looks similar to:
#Service
public class Consumer
{
private final ObjectMapper objectMapper;
public Consumer(ObjectMapper objectMapper)
{
this.objectMapper = objectMapper;
}
#KafkaListener(topics = {"${kafka.topic}"})
public void listen(ConsumerRecord<String, String> record)
{
objectMapper.readValue(record.value(), ExternalTO.class)
}
Please, help to solve this issue with deseriatization.
The deserializer doesn't know, out of all the implementations of IExternalData, to which it should deserialize the consumer record data to. We must resolve that ambiguity.
I was able to resolve this using #JsonDeserialize annotation.
#JsonDeserialize(as = <Implementation>.class
above the declaration of the List
The solution for me was to set property to objectMapper.
ObjectMapper mapper = new ObjectMapper();
// deserializes IExternalData into certain implementation.
mapper.enableDefaultTyping();

Spring Managed Custom Validator not being used from endpoint

I've been at this for a while, but I have a Spring managed custom validator that looks like the below, I have some print statements in there which I'll get to later
#Component
public class BulkUpdateValidator implements ConstraintValidator<ValidBulkUpdate, BulkUpdate> {
#Autowired
ObjectMapper mapper;
public BulkUpdateValidator(){
System.out.println(this.toString());
}
#PostConstruct
public void post(){
System.out.println(mapper);
System.out.println(this.toString());
}
public boolean isValid(BulkUpdate update, ConstraintValidatorContext context){
System.out.println(this.toString());
System.out.println(mapper);
}
... other validator methods ...
}
My controller method: (NOTE: my controller class is annotated with #Validated at the top)
#RequestMapping(...)
public #ResponseBody RestResponse bulkUpdate(#Valid #ValidBulkUpdate Bulkupdate bulkUpdate){
... stuff here ...
}
My Bean:
public class BulkUpdate {
#NotEmpty
public List<String> recordIds;
#NotEmpty
#Valid
public List<FieldUpdate> updates;
.... getters and setters ....
}
Here's my problem, when I execute the endpoint it get a NullPointerException when I attempt to use the autowired mapper. The output from the print statements I posted above are quite telling. In both the constructor and the #PostConstruct sections I get the same Object ID for the validator and I also get an ID for the mapper. However, once isValid is called, it prints out a different Object ID. I know the spring managed validator is being created, but it's not being used.
Furthermore, I've tried to remove the #ValidBulkUpdate annotation from the REST endpoint and put it inside a wrapper object, thinking that maybe #Valid was necessary to get spring to take over, like below:
public #ResponseBody RestResponse bulkUpdate(#Valid BulkupdateWrapper bulkUpdate){
... stuff here ...
}
And wrapper
public class BulkUpdateWrapper {
#ValidBulkUpdate
private BulkUpdate update;
.... getter and setter ....
}
This leaves me with a whole new error which is even weirder:
"JSR-303 validated property 'update.org.hibernate.validator.internal.engine.ConstraintViolationImpl' does not have a corresponding accessor"
I'm not sure where to turn, hopefully someone has an idea. Either how to get it to use the Spring managed validator, or how to remove that vague error when I use the object wrapper;
What's worse, is I have MockMvc based Integration tests for this that run flawlessly, this only happens when I deploy it.
UPDATE
So I kept my wrapper and changed #Valid to #Validated and now my error is the following: "NotReadablePropertyException: Bean property 'update.field' does not have a corresponding accessor for Spring data binding"
Fun fact, there is no property called "field"

XmlElement ignored by Jackson during serialization

i'm using Jersey to build a REST service and as Json Processor i set Jackson in my application.
#javax.ws.rs.ApplicationPath("/")
public class MyApplication extends ResourceConfig {
public MyApplication() {
packages("controller");
register(JacksonFeature.class);
}
I implement a ContextResolver for Jacksons ObjectMapper (as it's suggested in this post Configure Jersey/Jackson to NOT use #XmlElement field annotation for JSON field naming) which creates an ObjectMapper that doesn't fail on unknown properties during deserialization:
#Provider
public class MyJsonObjectMapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type)
{
System.out.println("mapper!!!");
ObjectMapper result = new ObjectMapper();
result.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return result;
}
}
and then i register this class in my application inserting register(MyJsonObjectMapperProvider.class) in the class MyApplication shown above. I obtain what i want, in sense that if there are unknown properties in the json the object mapper doesn't fail.
My problem is another; i have this class that i use to map a specified Json, in order to deserialize it and subsequently serialize it:
public class Version {
private String status;
private String updated;
private String id;
private List<Link> links;
#XmlElement(name = "media-types")
private List<MediaTypes> media_types;
//constructor + getter and setter
}
The problem is about the element media_types and the use of the annotation #XmlElement. Before i insert the ContextResolver to personalize ObjectMapper all works fine, in fact after serialization i obtain a json in which the element/attribute media_types has as name media-types; on the contrary with ContextResolver this element doesn't change it's name and has media_types. I think that, during serialization, the annotation XmlElement doesn't work, but i'm not sure that this is the correct reason.
Another attempt i try to do is to put #JsonProperty("media-types") annotation instead of #XmlElement annotation but with no result; in fact with this annotation i obtain also a Processing Exception.
The last attempt (in addition to what has been suggested by the previous post) was that of insert these lines of code in the ContextResolver:
AnnotationIntrospector intr = new AnnotationIntrospector.Pair(new JaxbAnnotationIntrospector(),new JacksonAnnotationIntrospector());
// usually we use same introspector(s) for both serialization and deserialization:
result.getDeserializationConfig().withAnnotationIntrospector(intr);
result.getSerializationConfig().withAnnotationIntrospector(intr);
in order to use both JaxbAnnotation and JacksonAnnotation but the name of the field in question remain media_types.
I hope i was clear in explain my problem and thanks you in advance for your help!

Categories