I am a student trying to learn about apache kafka.
I am trying to send a JSON object as a message using kafka in spring boot using java.
But as I try to send it,it throws an error saying that my model class cannot be cast to a string even though i have mentioned the Json serializer in application.properties file.
The exception is:
java.lang.ClassCastException: class com.example.demo.model.BookES cannot be cast to class java.lang.String (com.example.demo.model.BookES is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
My application.properties file is
server.port=8081
spring.kafka.consumer.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=myGroup
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.producer.bootstrap-servers=localhost:9092
spring.kafka.producer.key-serializer=org.apache.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
My controller class where i am trying to send my message
#PostMapping("/publish")
public ResponseEntity<String> publish(#RequestBody BookES bookES){
logger.info("in publish method");
kafkaProducer.sendMessage(bookES);
return ResponseEntity.ok("Json message sent to kafka topic");
}
My Kafka producer class which has the sendMessage method:
package com.example.demo.kafka;
import org.apache.kafka.clients.admin.NewTopic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import com.example.demo.controller.BookController;
import com.example.demo.model.Book;
import com.example.demo.model.BookES;
#Service
public class KafkaProducer {
#Autowired
private NewTopic topic;
Logger logger = LoggerFactory.getLogger(KafkaProducer.class);
private String topicName = "bookmanagement";
#Autowired
private KafkaTemplate<String, BookES> kafkaTemplate;
public void sendMessage(BookES bookES) {
logger.info("in sendMessage method");
logger.info(String.format("Message sent -> %s",bookES.toString()));
Message<BookES> message = MessageBuilder.withPayload(bookES).setHeader(KafkaHeaders.TOPIC, topic.name()).build();
kafkaTemplate.send(message);
}
}
My model class:
package com.example.demo.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
#Document(indexName="my-application")
#Data
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class BookES{
#Override
public String toString() {
return "BookES [bookId=" + bookId + ", bookName=" + bookName + ", description=" + description + "]";
}
#Id
private String bookId;
private String bookName;
private String description;
public String getBookId() {
// TODO Auto-generated method stub
return this.bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void setBookId(String bookId) {
this.bookId = bookId;
}
}
my project is on the github link: github link
I tried anotating my model class with with different annotations like #JsonSerializer,etc.. but with no success.
The response i got on postman on trying to post is:
{
"timestamp": "2022-11-22T11:24:30.738+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "Can't convert value of class com.example.demo.model.BookES to class org.apache.kafka.common.serialization.StringSerializer specified in value.serializer",
"path": "/books/publish"
}
You need to provide your own serializer that can serialize your BookES into byte array. Here is a class I use as generic Json serializer. It should handle your class just fine:
package ***;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.mgnt.utils.JsonUtils;
import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.serialization.Serializer;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
#Component
public class GenericJsonSerializer implements Serializer<Object> {
#Override
public byte[] serialize(String s, Object obj) {
byte[] result;
try {
result = JsonUtils.writeObjectToJsonString(obj).getBytes(StandardCharsets.UTF_8);
} catch (JsonProcessingException e) {
throw new SerializationException("Error occurred while serializing " + obj.getClass().getCanonicalName() + " to byte[]", e);
}
return result;
}
}
In your properties you will need to register this class as your deserializer. Note that when you read your message back, if you want it to be read as BookES class you will need to provide deserializer class that would take byte array and convert it back into class BookES. (You will have to implement org.apache.kafka.common.serialization.Deserializer interface). In my example I used class JsonUtils that comes from Open Source Java library MgntUtils (written and maintained by me). You can easily replace it and just use ObjectMapper class from Json-Jackson library or use GSON library. But It might make it simpler to use JsonUtils. If you wish to use it here is the Javadoc for JsonUtils class. The MgntUtils library could be obtained as Maven Artifact or on The Github
Related
I am completely new to spring boot and I am now trying to insert some data to my database from spring boot. What is the correct way to do this?
file structure
NewUser.java
package com.example.demo.pojo;
public class NewUser {
private String CompanyName;
public String getCompanyName() {
return CompanyName;
}
public void setCompanyName(String CompanyName) {
this.CompanyName = CompanyName;
}
}
RegistrationController.java
package com.example.demo.controller;
import com.example.demo.result.Result;
import com.example.demo.pojo.NewUser;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.demo.service.RegistrationService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
#RestController
public class RegistrationController {
#CrossOrigin
#PostMapping(value = "api/registration")
#ResponseBody
public Result registration(#RequestBody NewUser user) {
System.out.println(user.toString());
return new Result(200);
}
}
Above is how I get data from frontend and below is what I tried to insert data. How should I call the service to insert data?
AccApplMapper.java
package com.example.demo.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
#Mapper
public interface AccApplMapper {
#Insert("INSERT INTO ACCT_APPL(ENG_COMP_NAME) VALUES(#{CompanyName}")
public int addAcctAppl(#Param("CompanyName") String CompanyName);
}
RegistrationService.java
package com.example.demo.service;
import com.example.demo.mapper.AccApplMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class RegistrationService {
private AccApplMapper accApplMapper;
public int addAcctAppl(String CompanyName) {
return accApplMapper.addAcctAppl(CompanyName);
}
}
Based on question above, you can modify your registration method in RegistrationController something like below, along with using Autowired annotation in controller :
public class RegistrationController {
..
#Autowired
RegistrationService registrationService;
...
public Result registration(#RequestBody NewUser user) {
System.out.println(user.toString());
if(user!=null && user.getCompanyName()!=null) {
int insert = registrationService.addAcctAppl(user.getCompanyName());
return insert>0 ? new Result(200) : new Result(500);
}
else {
return new Result(400);
}
}
here based on input data, calling service method & returning appropriate httpStatus code as argument to Result.
Hello friend I suggest you use Spring Data JPA dependency, it makes alot easier to perform any database operation.
Spring Data JPA provides repository support for the Java Persistence
API (JPA). It eases development of applications that need to access
JPA data sources.
Here are some good reference links
Spring Data JPA - Reference Documentation
Introduction to Spring Data JPA
I created the following service interface:
import javax.validation.constraints.NotBlank;
import org.springframework.lang.NonNull;
import org.springframework.validation.annotation.Validated;
#Validated
public interface UserService {
User create(#NonNull Long telegramId, #NotBlank String name, #NonNull Boolean isBot);
}
but the following invocation:
userService.create(telegramId, "Mike", null);
passes the #NotNull validation for isBot parameter. How to correctly configure Spring Boot and my service in order to take into account #NonNull annotation and prevent method execution in case of null parameter?
I played around with this problem for a bit.
Your code looks fine to me: Make sure that the implementation of UserService also has the validation annotations present.
Ensure that you allow Spring to create the Bean; it should work as you expect.
Example
Service Definition
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
#Validated
public interface GreetingService {
String greet(#NotNull #NotBlank String greeting);
}
Service Implementation
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
#Service
public class HelloGreetingService implements GreetingService {
public String greet(#NotNull #NotBlank String greeting) {
return "hello " + greeting;
}
}
Testcase
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import javax.validation.ConstraintViolationException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
#SpringBootTest
class HelloGreetingServiceTest {
#Autowired
private GreetingService helloGreetingService;
#Test
void whenGreetWithStringInput_shouldDisplayGreeting() {
String input = "john doe";
assertEquals("hello john doe", helloGreetingService.greet(input));
}
#Test
void whenGreetWithNullInput_shouldThrowException() {
assertThrows(ConstraintViolationException.class, () -> helloGreetingService.greet(null));
}
#Test
void whenGreetWithBlankInput_shouldThrowException() {
assertThrows(ConstraintViolationException.class, () -> helloGreetingService.greet(""));
}
}
Testcases are green for me.
Github: https://github.com/almac777/spring-validation-playground
Source: https://www.baeldung.com/javax-validation-method-constraints
HTH!
Use the same thing in Implementation class instead interface.
Also can write one global exception like:
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice
public class GlobalRestException extends ResponseEntityExceptionHandler {
...
...
/**
* Handle MethodArgumentNotValidException. Triggered when an object fails #Valid
* validation.
*
* #param ex the MethodArgumentNotValidException that is thrown when #Valid
* validation fails
* #param headers HttpHeaders
* #param status HttpStatus
* #param request WebRequest
* #return the ApiException object
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Error apiError = new Error(BAD_REQUEST);
apiError.setMessage("Validation error");
apiError.addValidationErrors(ex.getBindingResult().getFieldErrors());
apiError.addValidationError(ex.getBindingResult().getGlobalErrors());
return buildResponseEntity(apiError);
}
}
There are more method that can be override to handle different kind of exception like :
/**
* Handles javax.validation.ConstraintViolationException. Thrown when #Validated
* fails.
*
* #param ex the ConstraintViolationException
* #return the ApiException object
*/
#ExceptionHandler(javax.validation.ConstraintViolationException.class)
protected ResponseEntity<Object> handleConstraintViolation(javax.validation.ConstraintViolationException ex) {
Error apiError = new Error(BAD_REQUEST);
apiError.setMessage("Validation error");
apiError.addValidationErrors(ex.getConstraintViolations());
return buildResponseEntity(apiError);
}
You need to make sure that #Validated annotation is used on 'class' which method arguments will need to be validated and Spring configuration need to be added
#Configuration
public class MethodValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
I am not well versed with Java. Here is the webservice, I am trying to implement - a basic example and I am facing compilation error.
I am not sure what am I missing here.
Here is the code.
package com.joshis1.jaxws;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
#WebService
#SOAPBinding(style = Style.DOCUMENT)
public interface IwebServiceInterface {
#WebMethod String sayHello(String name);
}
Next, implementing the interface
package com.joshis1.jaxws;
import javax.jws.WebService;
#WebService(endpointInterface = "com.joshis1.jaxws")
public class webServiceImpl implements IwebServiceInterface {
#Override
public String sayHello(String name)
{
return "Hello Shreyas " + name;
}
}
Next, the main class to publish the endpoint
package com.joshis1.publisher;
import javax.xml.ws.Endpoint;
import com.joshis1.jaxws.*;
public class WebServicePublisher {
public static void main(String[] args) {
Endpoint.publish("http://localhost:8888/webservice/helloworld", new webServiceImpl());
}
}
Next, very basic question - Do I need to install a web server here?
You are pointing your endpointInterface to your package:
#WebService(endpointInterface = "com.joshis1.jaxws")
It needs to reference your interface:
#WebService(endpointInterface = "com.joshis1.jaxws.IwebServiceInterface")
It is very important to look on what the error is saying
class:com.joshis1.jaxws could not be found
I'm using Spring boot 2 webflux with reactive mongo repository. I'm trying to persist and query entities with Class<> field type. But I can't figure out how to configure my application to work with this type.
Here is my entity that I would like to store in mongo:
import lombok.Data;
import org.springframework.data.annotation.Id;
#Data
public class ServiceEntity {
#Id
private String id;
private String name;
private Class inputClass;
}
And here is my mongo configuration:
import static java.util.Arrays.asList;
import com.mongodb.MongoClient;
import com.mongodb.MongoException;
import com.mongodb.async.client.MongoClientSettings;
import lombok.extern.slf4j.Slf4j;
import org.bson.BsonReader;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
#Configuration
#Slf4j
public class MongoConfiguration {
#Bean
public MongoClientSettings settings() {
log.debug("Configure mongo settings");
return MongoClientSettings.builder()
.codecRegistry(CodecRegistries.fromRegistries(
CodecRegistries.fromProviders(new ClassCodecProvider()),
MongoClient.getDefaultCodecRegistry()))
.build();
}
// #Bean
public MongoCustomConversions customConversions() {
log.debug("Configure mongo custom conversions");
return new MongoCustomConversions(asList(new StringToClassConverter()));
}
private static class ClassCodec implements Codec<Class> {
#Override
public Class decode(BsonReader reader, DecoderContext decoderContext) {
try {
return Class.forName(reader.readString());
} catch (ClassNotFoundException e) {
throw new MongoException("Couldn't read value as class type", e);
}
}
#Override
public void encode(BsonWriter writer, Class value, EncoderContext encoderContext) {
writer.writeString(value.getName());
}
#Override
public Class<Class> getEncoderClass() {
return Class.class;
}
}
private static class ClassCodecProvider implements CodecProvider {
#Override
public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
if (clazz == Class.class) {
return (Codec<T>) new ClassCodec();
}
return null;
}
}
private static class StringToClassConverter implements Converter<String, Class> {
#Override
public Class convert(String source) {
try {
return Class.forName(source);
} catch (ClassNotFoundException e) {
throw new MongoException("Couldn't read string as class type", e);
}
}
}
}
The problem is when I try to read entities from mongo I get error:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.lang.Class<?>]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:321)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:194)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:174)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getPotentiallyConvertedSimpleRead(MappingMongoConverter.java:887)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1392)
Also I created String to Class converter (see StringToClassConverter in the config) and tried to use it as custom converter. But when I put such configuration to the context(to do that uncomment #Bean annotation on customConversions() method) it had been used for any string conversion. As result, while saving entities:
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Class<?>] for value 'service1'; nested exception is com.mongodb.MongoException: Couldn't read string as class type
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:46)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:191)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:174)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getPotentiallyConvertedSimpleWrite(MappingMongoConverter.java:849)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeSimpleInternal(MappingMongoConverter.java:829)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeProperties(MappingMongoConverter.java:488)
Full code is available on github. Please see com.example.mongoclasspertist.ServiceRepositoryTest#test to reproduce.
I see next ways how to solve this problem(but them could lead to another issues probably) by submitting PR to Spring repo:
* To Create ClassToStringConverter and StringToClassConverter in org.springframework.data.mongodb.core.convert.MongoConverters
* To change org.springframework.data.mongodb.core.convert.MappingMongoConverter#getPotentiallyConvertedSimpleWrite method logic for converting only if source and target types are applicable to custom converter.
Please help me to solve this problem.
Using the annotation #ReadingConverter on the converter seems to do the job. Please give it a try.
#ReadingConverter
private static class StringToClassConverter implements Converter<String, Class>
{
For the following classes Texts ...
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.Collections;
import java.util.List;
import hrisey.Parcelable;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
#Parcelable
public final class Texts implements android.os.Parcelable {
#NonNull List<Text> texts = Collections.emptyList();
public boolean hasTexts() {
return !texts.isEmpty() && textsHaveValues();
}
private boolean textsHaveValues() {
for (Text text : texts) {
if (TextUtils.isEmpty(text.getValue())) {
return false;
}
}
return true;
}
}
... and Text ...
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import hrisey.Parcelable;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
#Parcelable
public final class Text implements android.os.Parcelable {
private String textKey;
private String value;
}
... I wrote this unit test:
#RunWith(JUnit4.class)
public class TextsTest {
private Texts texts;
#Before
public void setUp() {
texts = new Texts();
}
#Test
public void hasTextsWithSingleEmptyItem() throws Exception {
texts.setTexts(Collections.singletonList(new Text()));
assertThat(texts.hasTexts()).isFalse();
}
}
The test succeeds in Android Studio 2.1.3 but it fails when I run ./gradlew clean test on my machine (MacOS 10.11.6, El Capitain, Java 1.7.0_79). Here is the error output:
com.example.model.TextsTest > hasTextsWithSingleEmptyItem FAILED
org.junit.ComparisonFailure: expected:<[fals]e> but was:<[tru]e>
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(
NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(
DelegatingConstructorAccessorImpl.java:45)
at com.example.model.TextsTest.hasTextsWithSingleEmptyItem(TextsTest.java:31)
Related posts
creating instance of object using reflection , when the constructor takes an array of strings as argument
IllegalArgumentException with constructing class using reflection and array arguments
How do you mock TextUtils? The part TextUtils.isEmpty(text.getValue()) should always be false when using the default Android test stubs.
Be sure to use a suitable implementation or consider using a different set of string utilities you already might have available with some other dependencies.
Edit by JJD
You are right, thanks! I use the Unmock plugin. So I had to unmock the relevant package to expose TextUtils in the unit tests:
unMock {
keepStartingWith "android.text."
}