Spring Integration MongoDbStoringMessageHandler ClassCastException: BasicDBObject cannot be cast to BasicDBList - spring

I have developed an integration flow where I get the users from a MongoDbMessageSource and for each social medium associated with the user I get the comments addressed to him.
Those comments I want to persist them in MongoDB with help of MongoDbStoringMessageHandler linked to channel storeChannel.
The flow is as follows:
#Configuration
#IntegrationComponentScan
public class InfrastructureConfiguration {
private static Logger logger = LoggerFactory.getLogger(InfrastructureConfiguration.class);
/**
* The Pollers builder factory can be used to configure common bean definitions or
* those created from IntegrationFlowBuilder EIP-methods
*/
#Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return Pollers.fixedDelay(10, TimeUnit.SECONDS).get();
}
#Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
return executor;
}
/**
*
* MongoDbMessageSource is an instance of MessageSource which returns a Message with a payload
* which is the result of execution of a Query
*/
#Bean
#Autowired
public MessageSource<Object> mongoMessageSource(MongoDbFactory mongo) {
MongoDbMessageSource messageSource = new MongoDbMessageSource(mongo, new LiteralExpression("{}"));
messageSource.setExpectSingleResult(false);
messageSource.setEntityClass(UserEntity.class);
messageSource.setCollectionNameExpression(new LiteralExpression("users"));
return messageSource;
}
#Bean
#ServiceActivator(inputChannel = "storeChannel")
public MessageHandler mongodbAdapter(MongoDbFactory mongo) throws Exception {
MongoDbStoringMessageHandler adapter = new MongoDbStoringMessageHandler(mongo);
adapter.setCollectionNameExpression(new LiteralExpression("comments"));
return adapter;
}
#Bean
#Autowired
public IntegrationFlow processUsers(MongoDbFactory mongo, PollerMetadata poller) {
return IntegrationFlows.from(mongoMessageSource(mongo), c -> c.poller(poller))
.<List<UserEntity>, Map<ObjectId, List<SocialMediaEntity>>>transform(userEntitiesList
-> userEntitiesList.stream().collect(Collectors.toMap(UserEntity::getId, UserEntity::getSocialMedia))
)
.split(new AbstractMessageSplitter() {
#Override
protected Object splitMessage(Message<?> msg) {
return ((Map<ObjectId, List<SocialMediaEntity>>) msg.getPayload()).entrySet();
}
})
.channel("directChannel_1")
.enrichHeaders(s -> s.headerExpressions(h -> h.put("user-id", "payload.key")))
.split(new AbstractMessageSplitter() {
#Override
protected Object splitMessage(Message<?> msg) {
return ((Entry<ObjectId, List<SocialMediaEntity>>) msg.getPayload()).getValue();
}
})
.channel(MessageChannels.executor("executorChannel", this.taskExecutor()))
.<SocialMediaEntity, SocialMediaTypeEnum>route(p -> p.getType(),
m
-> m.subFlowMapping(SocialMediaTypeEnum.FACEBOOK, sf -> sf.handle(new GenericHandler<SocialMediaEntity>() {
#Override
public Object handle(SocialMediaEntity payload, Map<String, Object> headers) {
ObjectId userId = (ObjectId)headers.get("user-id");
logger.info("TEST FACEBOOK Channel for user id: " + userId);
return Arrays.asList(new CommentEntity[] {
new CommentEntity("Comentario 1 from facebook dirigido a " + userId, userId),
new CommentEntity("Comentario 2 from facebook dirigido a " + userId, userId)
});
}
}))
.subFlowMapping(SocialMediaTypeEnum.YOUTUBE, sf -> sf.handle(new GenericHandler<SocialMediaEntity>() {
#Override
public Object handle(SocialMediaEntity payload, Map<String, Object> headers) {
ObjectId userId = (ObjectId)headers.get("user-id");
logger.info("TEST YOUTUBE Channel for user id: " + userId);
return Arrays.asList(new CommentEntity[] {
new CommentEntity("Comentario 1 from youtube dirigido a " + userId, userId),
new CommentEntity("Comentario 2 from youtube dirigido a " + userId, userId)
});
}
}))
.subFlowMapping(SocialMediaTypeEnum.INSTAGRAM, sf -> sf.handle(new GenericHandler<SocialMediaEntity>() {
#Override
public Object handle(SocialMediaEntity payload, Map<String, Object> headers) {
ObjectId userId = (ObjectId)headers.get("user-id");
logger.info("TEST INSTAGRAM Channel for user id: " + userId);
return Arrays.asList(new CommentEntity[] {
new CommentEntity("Comentario 1 from instagram dirigido a " + userId, userId),
new CommentEntity("Comentario 2 from instagram dirigido a " + userId, userId)
});
}
}))
)
.channel("directChannel_2")
.aggregate()
.channel("directChannel_3")
.<List<List<CommentEntity>>, List<CommentEntity>>transform(comments ->
comments.stream().flatMap(List::stream).collect(Collectors.toList()))
.aggregate()
.channel("directChannel_4")
.<List<List<CommentEntity>>, List<CommentEntity>>transform(comments ->
comments.stream().flatMap(List::stream).collect(Collectors.toList()))
.channel("storeChannel")
.get();
}
}
The debug messages before the error are these:
2017-07-24 15:43:03.265 DEBUG 15152 --- [ taskExecutor-3] o.s.integration.channel.DirectChannel : preSend on channel 'storeChannel', message: GenericMessage [payload=[sanchez.sanchez.sergio.persistence.entity.CommentEntity#4de61faa, sanchez.sanchez.sergio.persistence.entity.CommentEntity#587d9f81, sanchez.sanchez.sergio.persistence.entity.CommentEntity#21075b47, sanchez.sanchez.sergio.persistence.entity.CommentEntity#653d282, sanchez.sanchez.sergio.persistence.entity.CommentEntity#4b790cef, sanchez.sanchez.sergio.persistence.entity.CommentEntity#662a5dcd, sanchez.sanchez.sergio.persistence.entity.CommentEntity#1a82309c, sanchez.sanchez.sergio.persistence.entity.CommentEntity#1b99ebf2, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6d1a6380, sanchez.sanchez.sergio.persistence.entity.CommentEntity#13b4363c, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6c5952d0, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3b3e7b7d, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3859229, sanchez.sanchez.sergio.persistence.entity.CommentEntity#786af66, sanchez.sanchez.sergio.persistence.entity.CommentEntity#271b5a0e, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3e45e786, sanchez.sanchez.sergio.persistence.entity.CommentEntity#ae0edfb, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6955ab16, sanchez.sanchez.sergio.persistence.entity.CommentEntity#7ae0fb73, sanchez.sanchez.sergio.persistence.entity.CommentEntity#4ed5e239, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6da79744, sanchez.sanchez.sergio.persistence.entity.CommentEntity#39352779, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3a12507d, sanchez.sanchez.sergio.persistence.entity.CommentEntity#51345bc3, sanchez.sanchez.sergio.persistence.entity.CommentEntity#7d95ad, sanchez.sanchez.sergio.persistence.entity.CommentEntity#32ca5648, sanchez.sanchez.sergio.persistence.entity.CommentEntity#616e3510, sanchez.sanchez.sergio.persistence.entity.CommentEntity#53a15bc4, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3aa84ac4, sanchez.sanchez.sergio.persistence.entity.CommentEntity#ed8ac69], headers={sequenceNumber=5, sequenceDetails=[[eda06022-4472-76b2-4ab5-1b24c1929cc2, 5, 5]], mongo_collectionName=users, sequenceSize=5, user-id=5975f9523681ac3b30e547c8, correlationId=eda06022-4472-76b2-4ab5-1b24c1929cc2, id=644a7577-7033-e669-505a-901172364790, timestamp=1500903783265}]
2017-07-24 15:43:03.267 DEBUG 15152 --- [ taskExecutor-3] ssor$ReplyProducingMessageHandlerWrapper : infrastructureConfiguration.mongodbAdapter.serviceActivator.handler received message: GenericMessage [payload=[sanchez.sanchez.sergio.persistence.entity.CommentEntity#4de61faa, sanchez.sanchez.sergio.persistence.entity.CommentEntity#587d9f81, sanchez.sanchez.sergio.persistence.entity.CommentEntity#21075b47, sanchez.sanchez.sergio.persistence.entity.CommentEntity#653d282, sanchez.sanchez.sergio.persistence.entity.CommentEntity#4b790cef, sanchez.sanchez.sergio.persistence.entity.CommentEntity#662a5dcd, sanchez.sanchez.sergio.persistence.entity.CommentEntity#1a82309c, sanchez.sanchez.sergio.persistence.entity.CommentEntity#1b99ebf2, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6d1a6380, sanchez.sanchez.sergio.persistence.entity.CommentEntity#13b4363c, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6c5952d0, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3b3e7b7d, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3859229, sanchez.sanchez.sergio.persistence.entity.CommentEntity#786af66, sanchez.sanchez.sergio.persistence.entity.CommentEntity#271b5a0e, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3e45e786, sanchez.sanchez.sergio.persistence.entity.CommentEntity#ae0edfb, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6955ab16, sanchez.sanchez.sergio.persistence.entity.CommentEntity#7ae0fb73, sanchez.sanchez.sergio.persistence.entity.CommentEntity#4ed5e239, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6da79744, sanchez.sanchez.sergio.persistence.entity.CommentEntity#39352779, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3a12507d, sanchez.sanchez.sergio.persistence.entity.CommentEntity#51345bc3, sanchez.sanchez.sergio.persistence.entity.CommentEntity#7d95ad, sanchez.sanchez.sergio.persistence.entity.CommentEntity#32ca5648, sanchez.sanchez.sergio.persistence.entity.CommentEntity#616e3510, sanchez.sanchez.sergio.persistence.entity.CommentEntity#53a15bc4, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3aa84ac4, sanchez.sanchez.sergio.persistence.entity.CommentEntity#ed8ac69], headers={sequenceNumber=5, sequenceDetails=[[eda06022-4472-76b2-4ab5-1b24c1929cc2, 5, 5]], mongo_collectionName=users, sequenceSize=5, user-id=5975f9523681ac3b30e547c8, correlationId=eda06022-4472-76b2-4ab5-1b24c1929cc2, id=644a7577-7033-e669-505a-901172364790, timestamp=1500903783265}]
2017-07-24 15:43:03.267 DEBUG 15152 --- [ taskExecutor-3] o.s.i.m.o.MongoDbStoringMessageHandler : mongodbAdapter received message: GenericMessage [payload=[sanchez.sanchez.sergio.persistence.entity.CommentEntity#4de61faa, sanchez.sanchez.sergio.persistence.entity.CommentEntity#587d9f81, sanchez.sanchez.sergio.persistence.entity.CommentEntity#21075b47, sanchez.sanchez.sergio.persistence.entity.CommentEntity#653d282, sanchez.sanchez.sergio.persistence.entity.CommentEntity#4b790cef, sanchez.sanchez.sergio.persistence.entity.CommentEntity#662a5dcd, sanchez.sanchez.sergio.persistence.entity.CommentEntity#1a82309c, sanchez.sanchez.sergio.persistence.entity.CommentEntity#1b99ebf2, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6d1a6380, sanchez.sanchez.sergio.persistence.entity.CommentEntity#13b4363c, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6c5952d0, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3b3e7b7d, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3859229, sanchez.sanchez.sergio.persistence.entity.CommentEntity#786af66, sanchez.sanchez.sergio.persistence.entity.CommentEntity#271b5a0e, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3e45e786, sanchez.sanchez.sergio.persistence.entity.CommentEntity#ae0edfb, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6955ab16, sanchez.sanchez.sergio.persistence.entity.CommentEntity#7ae0fb73, sanchez.sanchez.sergio.persistence.entity.CommentEntity#4ed5e239, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6da79744, sanchez.sanchez.sergio.persistence.entity.CommentEntity#39352779, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3a12507d, sanchez.sanchez.sergio.persistence.entity.CommentEntity#51345bc3, sanchez.sanchez.sergio.persistence.entity.CommentEntity#7d95ad, sanchez.sanchez.sergio.persistence.entity.CommentEntity#32ca5648, sanchez.sanchez.sergio.persistence.entity.CommentEntity#616e3510, sanchez.sanchez.sergio.persistence.entity.CommentEntity#53a15bc4, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3aa84ac4, sanchez.sanchez.sergio.persistence.entity.CommentEntity#ed8ac69], headers={sequenceNumber=5, sequenceDetails=[[eda06022-4472-76b2-4ab5-1b24c1929cc2, 5, 5]], mongo_collectionName=users, sequenceSize=5, user-id=5975f9523681ac3b30e547c8, correlationId=eda06022-4472-76b2-4ab5-1b24c1929cc2, id=644a7577-7033-e669-505a-901172364790, timestamp=1500903783265}]
Where it is clear that the channel "storeChannel" comes a list of "CommentEntity"
#Document(collection="comments")
public class CommentEntity {
#Id
private ObjectId id;
#Field("message")
private String message;
private ObjectId user;
#PersistenceConstructor
public CommentEntity(String message, ObjectId user) {
this.message = message;
this.user = user;
}
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public ObjectId getUser() {
return user;
}
public void setUser(ObjectId user) {
this.user = user;
}
}
This exception then occurs:
2017-07-24 15:43:03.271 ERROR 15152 --- [ taskExecutor-3] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessageHandlingException: error occurred in message handler [mongodbAdapter]; nested exception is java.lang.ClassCastException: com.mongodb.BasicDBObject cannot be cast to com.mongodb.BasicDBList, failedMessage=GenericMessage [payload=[sanchez.sanchez.sergio.persistence.entity.CommentEntity#4de61faa, sanchez.sanchez.sergio.persistence.entity.CommentEntity#587d9f81, sanchez.sanchez.sergio.persistence.entity.CommentEntity#21075b47, sanchez.sanchez.sergio.persistence.entity.CommentEntity#653d282, sanchez.sanchez.sergio.persistence.entity.CommentEntity#4b790cef, sanchez.sanchez.sergio.persistence.entity.CommentEntity#662a5dcd, sanchez.sanchez.sergio.persistence.entity.CommentEntity#1a82309c, sanchez.sanchez.sergio.persistence.entity.CommentEntity#1b99ebf2, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6d1a6380, sanchez.sanchez.sergio.persistence.entity.CommentEntity#13b4363c, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6c5952d0, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3b3e7b7d, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3859229, sanchez.sanchez.sergio.persistence.entity.CommentEntity#786af66, sanchez.sanchez.sergio.persistence.entity.CommentEntity#271b5a0e, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3e45e786, sanchez.sanchez.sergio.persistence.entity.CommentEntity#ae0edfb, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6955ab16, sanchez.sanchez.sergio.persistence.entity.CommentEntity#7ae0fb73, sanchez.sanchez.sergio.persistence.entity.CommentEntity#4ed5e239, sanchez.sanchez.sergio.persistence.entity.CommentEntity#6da79744, sanchez.sanchez.sergio.persistence.entity.CommentEntity#39352779, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3a12507d, sanchez.sanchez.sergio.persistence.entity.CommentEntity#51345bc3, sanchez.sanchez.sergio.persistence.entity.CommentEntity#7d95ad, sanchez.sanchez.sergio.persistence.entity.CommentEntity#32ca5648, sanchez.sanchez.sergio.persistence.entity.CommentEntity#616e3510, sanchez.sanchez.sergio.persistence.entity.CommentEntity#53a15bc4, sanchez.sanchez.sergio.persistence.entity.CommentEntity#3aa84ac4, sanchez.sanchez.sergio.persistence.entity.CommentEntity#ed8ac69], headers={sequenceNumber=5, sequenceDetails=[[eda06022-4472-76b2-4ab5-1b24c1929cc2, 5, 5]], mongo_collectionName=users, sequenceSize=5, user-id=5975f9523681ac3b30e547c8, correlationId=eda06022-4472-76b2-4ab5-1b24c1929cc2, id=644a7577-7033-e669-505a-901172364790, timestamp=1500903783265}]
I am currently using an embedded MongoDB:
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>
Does anyone know I'm doing wrong? Thanks in advance.

Well, that exception clearly says that MongoDbStoringMessageHandler doesn't support collection for saving:
protected void handleMessageInternal(Message<?> message) throws Exception {
Assert.isTrue(this.initialized, "This class is not yet initialized. Invoke its afterPropertiesSet() method");
String collectionName = this.collectionNameExpression.getValue(this.evaluationContext, message, String.class);
Assert.notNull(collectionName, "'collectionNameExpression' must not evaluate to null");
Object payload = message.getPayload();
this.mongoTemplate.save(payload, collectionName);
}
You don't need to .aggregate() to build collections to save. You only can save them with that component only one by one.
I think that should be a good addition to let that component to perform:
/**
* Insert a mixed Collection of objects into a database collection determining the collection name to use based on the
* class.
*
* #param collectionToSave the list of objects to save.
*/
void insertAll(Collection<? extends Object> objectsToSave);
Please, raise a JIRA on the matter and don't hesitate in contribution!

Related

no String-argument constructor/factory method to deserialize from String value with spring boot client

I am using spring boot application with frontend (spring boot application using thymeleaf) and backend (spring boot REST application ) are separated using REST api. The frontend uses HttpClient to send request to backend. Whenever I try to update an object the HttpClient creates an error for json parsing. The request is not accepted by the backend (ProcessDTORequest object ) with error as follows.
The exception is as follows:
{"message":"JSON parse error: Cannot construct instance of `com.app.dataaccess.entity.Process` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('68d22e4d-7116-4130-aa06-9ba120aadc66'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.app.dataaccess.entity.Process` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('68d22e4d-7116-4130-aa06-9ba120aadc66')\n at [Source: (PushbackInputStream); line: 1, column: 10310] (through reference chain: com.app.ui.dto.request.ProcessDTORequest[\"answeredQuestionnaires\"]->java.util.HashSet[0]->com.app.dataaccess.entity.AnsweredQuestionnaire[\"process\"])","httpStatus":"INTERNAL_SERVER_ERROR","timeStamp":"2022-11-04T08:44:35.9108286Z"}
HttpClient method for post request is as follows:
public String executePost(
final String url, final Object payLoad, final Map<String, String> headers,
final Map<String, String> params) throws Exception {
final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
// Add query strings to URL
URIBuilder builder = new URIBuilder(url);
for (final Map.Entry<String, String> elm : params.entrySet()) {
builder = builder.setParameter(elm.getKey(), elm.getValue());
}
// can change for HttpPut, HttpPost, HttpPatch
final HttpPost request = new HttpPost(builder.build());
// Add headers from input map
for (final Map.Entry<String, String> elm : headers.entrySet()) {
request.addHeader(elm.getKey(), elm.getValue());
}
request.setHeader("Accept", "application/json");
request.setHeader("Content-type", "application/json");
// Send Json String as body, can also send UrlEncodedFormEntity
final StringEntity entity = new StringEntity(objectMapper.writeValueAsString(payLoad));
request.setEntity(entity);
try {
final CloseableHttpResponse response = httpClient.execute(request);
System.out.println("Return response status code: "+response.getStatusLine().getStatusCode());
System.out.println("Return response status code: "+response.getStatusLine());
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// Read response string using EntityUtils class of Apache http client library
// Serialize json string into map or any other object
return EntityUtils.toString(response.getEntity());
} else {
throw new Exception(EntityUtils.toString(response.getEntity()));
// throw new Exception(String.format("Response status code was and response was ",
// response.getStatusLine().getStatusCode(), EntityUtils.toString(response.getEntity())));
}
} catch (final ClientProtocolException e) {
throw new Exception("Client protocol Exception occurred while executing request", e);
} catch (final Exception e) {
System.out.println(e);
throw new Exception(e);
}
}
I used the configuration for object mapper as follows:
#Configuration
public class AppConfig {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
objectMapper.registerModule(new JavaTimeModule());
return objectMapper; }
}
Process.java (this is used for serializing/deserializing)
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
public class Process {
private UUID processId;
private List<User> users = new ArrayList<>();
private List<UnitType> units = new ArrayList<>();
private String furtherComment;
private List<AnsweredQuestionnaire> answeredQuestionnaires = new ArrayList<>()
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Process)) return false;
Process process = (Process) o;
return getProcessId().equals(process.getProcessId());
}
#Override
public int hashCode() {
return Objects.hash(getProcessId());
}
}
The json from the server is like the following
{
"#id": "bba35e58-5d4b-44ce-9a5a-486f55f79af7",
"processId": "21ef7f9d-4fcc-417c-96e8-4327206d2592",
"users": [
{
"#id": "69d2f392-8213-4f34-9cb5-f0c403170787",
"userId": "5a17ec5f-c20a-4873-93af-bf69fad4eb26",
"roles": [
{
"roleId": "f6ad33a7-9d03-4260-81c2-a4a4c791e30a",
"users": []
}
],
"processes": []
}
],
"units": [
{
"unitTypeId": "c784d197-1dc7-446e-b3e5-6468a7954878",
"unit": {
"unitId": "aba76d05-e2ea-4b5a-828b-349966595258"
},
"isResponsibleUnit": true
}
],
"furtherComment": "",
"answeredQuestionnaires": [
{
"#id": "7ca1af09-eefd-4c56-9587-581858fbbc57"
}
]
}
The relation between the entities Process, AnsweredQuestionnaire and User is as follows:
Between Process and AnsweredQuestionnaire (One-to-many) respectively.
Between Process and User (many-to-many).
Between Process and UnitType (one-to-many) respectively.
AnsweredQuestionnaire.java
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class AnsweredQuestionnaire {
private UUID answeredQuestionnaireId;
private Questionnaire questionnaire;
private Process process;
public void addProcessToAnsweredQuestionnaire(Process process){
//remove old association
if(this.process != null){
this.process.getAnsweredQuestionnaires().remove(this);
}
this.process = process;
//add new association
if(process != null){
this.process.getAnsweredQuestionnaires().add(this);
}
}
}
User.java
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class User {
private UUID userId;
private String firstName;
private String lastName;
private String phoneNumber;
private String email;
private List<Role> roles = new ArrayList<>();
private List<Process> processes = new ArrayList<>();
public void addProcessToUser(Process process){
this.processes.add(process);
process.getUsers().add(this);
}
public void removeProcessFromUser(Process process){
this.processes.remove(process);
process.getUsers().remove(this);
}
}
ProcessDTORequest.java (this class is on the backend accepting the request from the frontend)
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class ProcessDTORequest {
private UUID processId;
private Set<User> users = new HashSet<>();
private Set<AnsweredQuestionnaire> answeredQuestionnaires = new HashSet<>();
private Set<UnitType> units = new HashSet<>();
}
UnitType.java
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class UnitType {
private UUID unitTypeId;
private Unit unit;
private Boolean isResponsibleUnit = false;
}

Springboot Application Failed to start because of a bean problem

I am studying basic development practice.
While creating a CRUD Service and trying to abstract the service, an error related to the bean occurred.
here is my code
1.CrudInterface
public interface CrudInterface<Req, Res> {
Header<Res> create(Header<Req> request);
Header<Res> read(Long id);
Header<Res> update(Header<Req> request);
Header delete(Long id);
}
2.CrudController.java
#Component
public class CrudController<Req,Res,Entity> implements
CrudInterface<Req,Res> {
#Autowired(required = false)
protected BaseService<Req,Res,Entity> entityBaseService;
#Override
#PostMapping("")
public Header<Res> create(#RequestBody Header<Req> request) {
return entityBaseService.create(request);
}
#Override
#GetMapping("{id}")
public Header<Res> read(#PathVariable Long id) {
return entityBaseService.read(id);
}
#Override
#PutMapping("")
public Header<Res> update(#RequestBody Header<Req> request) {
return entityBaseService.update(request);
}
#Override
#DeleteMapping("{id}")
public Header delete(#PathVariable Long id) {
return entityBaseService.delete(id);
}
}
3.UserApiController.java
#Slf4j
#RestController
#RequestMapping("/api/user")
public class UserApiController extends
CrudController<UserApiRequest, UserApiResponse, User> {
}
4.BaseService.java
#Component
public abstract class BaseService<Req,Res,Entity> implements
CrudInterface<Req,Res> {
#Autowired(required = false)
protected JpaRepository<Entity,Long> baseRepository;
}
5.UserApiLogicService
#Service
public class UserApiLogicService extends
BaseService<UserApiRequest, UserApiResponse,User> {
#Override
public Header<UserApiResponse> create(Header<UserApiRequest> request) {
UserApiRequest body = request.getData();
User user = User.builder()
.account(body.getAccount())
.password(body.getPassword())
.status(UserStatus.REGISTERED)
.phoneNumber(body.getPhoneNumber())
.email(body.getEmail())
.registeredAt(LocalDateTime.now())
.build();
User newUser = baseRepository.save(user);
return response(newUser);
}
#Override
public Header<UserApiResponse> read(Long id) {
return baseRepository.findById(id)
.map(user -> response(user))
.orElseGet(
() -> Header.ERROR("데이터 없음")
);
}
#Override
public Header<UserApiResponse> update(Header<UserApiRequest> request) {
UserApiRequest body = request.getData();
Optional<User> optional = baseRepository.findById(body.getId());
return optional.map(user -> {
user.setAccount(body.getAccount())
.setPassword(body.getPassword())
.setStatus(body.getStatus())
.setPhoneNumber(body.getPhoneNumber())
.setEmail(body.getEmail())
.setRegisteredAt(body.getRegisteredAt())
.setUnregisteredAt(body.getUnregisteredAt());
return user;
})
.map(user -> baseRepository.save(user)) // update -> newUser
.map(updateUser -> response(updateUser)) // userApiResponse
.orElseGet(() -> Header.ERROR("데이터 없음"));
}
#Override
public Header delete(Long id) {
Optional<User> optional = baseRepository.findById(id);
// 2. repository -> delete
return optional.map(user -> {
baseRepository.delete(user);
return Header.OK();
})
.orElseGet(() -> Header.ERROR("데이터 없음"));
}
private Header<UserApiResponse> response(User user) {
UserApiResponse userApiResponse = UserApiResponse.builder()
.id(user.getId())
.account(user.getAccount())
.password(user.getPassword())
.email(user.getEmail())
.phoneNumber(user.getPhoneNumber())
.status(user.getStatus())
.registeredAt(user.getRegisteredAt())
.unregisteredAt(user.getUnregisteredAt())
.build();
return Header.OK(userApiResponse);
}
}
6.CategoryApiLogicService.java
#Service
public class CategoryApiLogicService extends
BaseService<CategoryApiRequest, CategoryApiResponse,Category> {
#Override
public Header<CategoryApiResponse> create(Header<CategoryApiRequest> request) {
CategoryApiRequest body = request.getData();
Category category = Category.builder()
.type(body.getType())
.title(body.getTitle())
.createdAt(body.getCreatedAt())
.createdBy(body.getCreatedBy())
.build();
Category newCategory = baseRepository.save(category);
return response(newCategory);
}
#Override
public Header<CategoryApiResponse> read(Long id) {
return baseRepository.findById(id)
.map(category -> response(category))
.orElseGet(()-> Header.ERROR("데이터 없음"));
}
#Override
public Header<CategoryApiResponse> update(Header<CategoryApiRequest> request) {
CategoryApiRequest body = request.getData();
return baseRepository.findById(body.getId())
.map(category -> {
category
.setType(body.getType())
.setTitle(body.getTitle())
.setCreatedAt(body.getCreatedAt())
.setCreatedBy(body.getCreatedBy());
return category;
})
.map(changeCategory -> baseRepository.save(changeCategory))
.map(newCategory -> response(newCategory))
.orElseGet(()->Header.ERROR("데이터 없음"));
}
#Override
public Header delete(Long id) {
return baseRepository.findById(id)
.map(category -> {
baseRepository.delete(category);
return Header.OK();
})
.orElseGet(()->Header.ERROR("데이터 없음"));
}
private Header<CategoryApiResponse> response(Category category){
CategoryApiResponse body = CategoryApiResponse.builder()
.id(category.getId())
.type(category.getType())
.title(category.getTitle())
.createdAt(category.getCreatedAt())
.createdBy(category.getCreatedBy())
.build();
return Header.OK(body);
}
}
8.ItemApiLogicService.java
#Service
public class ItemApiLogicService extends
BaseService<ItemApiRequest,ItemApiResponse,Item> {
#Autowired
private PartnerRepository partnerRepository;
#Override
public Header<ItemApiResponse> create(Header<ItemApiRequest> request) {
ItemApiRequest body = request.getData();
Item item = Item.builder()
.status(body.getStatus())
.name(body.getName())
.title(body.getTitle())
.content(body.getContent())
.price(body.getPrice())
.brandName(body.getBrandName())
.registeredAt(LocalDateTime.now())
.partner(partnerRepository.getOne(body.getPartnerId()))
.build();
Item newItem = baseRepository.save(item);
return response(newItem);
}
#Override
public Header<ItemApiResponse> read(Long id) {
return baseRepository.findById(id)
.map(user -> response(user))
.orElseGet(
() -> Header.ERROR("데이터 없음")
);
}
#Override
public Header<ItemApiResponse> update(Header<ItemApiRequest> request) {
ItemApiRequest body = request.getData();
return baseRepository.findById(body.getId())
.map(entityItem -> {
entityItem
.setStatus(body.getStatus())
.setName(body.getName())
.setTitle(body.getTitle())
.setContent(body.getContent())
.setPrice(body.getPrice())
.setBrandName(body.getBrandName())
.setRegisteredAt(body.getRegisteredAt())
.setUnregisteredAt(body.getUnregisteredAt());
return entityItem;
})
.map(newEntityItem -> baseRepository.save(newEntityItem))
.map(item -> response(item))
.orElseGet(() -> Header.ERROR("데이터 없음"));
}
#Override
public Header delete(Long id) {
return baseRepository.findById(id)
.map(item -> {
baseRepository.delete(item);
return Header.OK();
})
.orElseGet(() -> Header.ERROR("데이터없음"));
}
private Header<ItemApiResponse> response(Item item) {
ItemApiResponse body = ItemApiResponse.builder()
.id(item.getId())
.status(item.getStatus())
.name(item.getName())
.title(item.getTitle())
.content(item.getContent())
.price(item.getPrice())
.brandName(item.getBrandName())
.registeredAt(item.getRegisteredAt())
.unregisteredAt(item.getUnregisteredAt())
.partnerId(item.getPartner().getId())
.build();
return Header.OK(body);
}
}
and here is my error message
ERROR 4516 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
Field entityBaseService in com.example.admin.controller.CrudController required a single bean, but 6 were found:
- categoryApiLogicService: defined in file [D:\~\service\CategoryApiLogicService.class]
- itemApiLogicService: defined in file [D:\~\service\ItemApiLogicService.class]
- orderDetailApiLogicService: defined in file [D:\~\service\OrderDetailApiLogicService.class]
- orderGroupApiLogicService: defined in file [D:\~\service\OrderGroupApiLogicService.class]
- partnerApiLogicService: defined in file [D:\~\service\PartnerApiLogicService.class]
- userApiLogicService: defined in file [D:\~\service\UserApiLogicService.class]
Thanks for reading the long story.
I hope you catch the error.
Maybe has 6 classes implement BaseService and you decide the abstract class with name entityBaseService, so Spring cannot bind exactly the bean you want.
You can use #primary mask on a class for default bean or using #qualifier to bind with the bean name
prefer: http://zetcode.com/springboot/qualifier

UpdateById method in Spring Reactive Mongo Router Handler

In Spring Reactive Java how can I write an updateById() method using the Router and Handler?
For example, the Router has this code:
RouterFunctions.route(RequestPredicates.PUT("/employees/{id}").and(RequestPredicates.accept(MediaType.APPLICATION_JSON))
.and(RequestPredicates.contentType(MediaType.APPLICATION_JSON)),
employeeHandler::updateEmployeeById);
My question is how to write the employeeHandler::updateEmployeeById() keeping the ID as same but changing the other members of the Employee object?
public Mono<ServerResponse> updateEmployeeById(ServerRequest serverRequest) {
Mono<Employee> employeeMono = serverRequest.bodyToMono(Employee.class);
<And now what??>
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(employeeMono, Employee.class);
}
The Employee class looks like this:
#Document
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Employee {
#Id
int id;
double salary;
}
Thanks for any help.
First of all, you have to add ReactiveMongoRepository in your classpath. You can also read about it here.
#Repository
public interface EmployeeRepository extends ReactiveMongoRepository<Employee, Integer> {
Mono<Employee> findById(Integer id);
}
Then your updateEmployeeById method can have the following structure:
public Mono<ServerResponse> updateEmployeeById(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(Employee.class)
.doOnSubscribe(e -> log.info("update employee request received"))
.flatMap(employee -> {
Integer id = Integer.parseInt(serverRequest.pathVariable("id"));
return employeeRepository
.findById(id)
.switchIfEmpty(Mono.error(new NotFoundException("employee with " + id + " has not been found")))
// what you need to do is to update already found entity with
// new values. Usually map() function is used for that purpose
// because map is about 'transformation' what is setting new
// values in our case
.map(foundEmployee -> {
foundEmployee.setSalary(employee.getSalary());
return foundEmployee;
});
})
.flatMap(employeeRepository::save)
.doOnError(error -> log.error("error while updating employee", error))
.doOnSuccess(e -> log.info("employee [{}] has been updated", e.getId()))
.flatMap(employee -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(employee), Employee.class));
}
UPDATE:
Based on Prana's answer, I have updated the code above merging our solutions in one. Logging with a help of Slf4j was added. And switchIfEmpty() functions for the case when the entity was not found.
I would also suggest your reading about global exception handling which will make your API even better. A part of it I can provide here:
/**
* Returns routing function.
*
* #param errorAttributes errorAttributes
* #return routing function
*/
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private HttpStatus getStatus(Throwable error) {
HttpStatus status;
if (error instanceof NotFoundException) {
status = NOT_FOUND;
} else if (error instanceof ValidationException) {
status = BAD_REQUEST;
} else {
status = INTERNAL_SERVER_ERROR;
}
return status;
}
/**
* Custom global error handler.
*
* #param request request
* #return response
*/
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> errorPropertiesMap = getErrorAttributes(request, false);
Throwable error = getError(request);
HttpStatus errorStatus = getStatus(error);
return ServerResponse
.status(errorStatus)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
A slightly different version of the above worked without any exceptions:
public Mono<ServerResponse> updateEmployeeById(ServerRequest serverRequest) {
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<Employee> employeeMono = serverRequest.bodyToMono(Employee.class);
Integer employeeId = Integer.parseInt(serverRequest.pathVariable("id"));
employeeMono = employeeMono.flatMap(employee -> employeeRepository.findById(employeeId)
.map(foundEmployee -> {
foundEmployee.setSalary(employee.getSalary());
return foundEmployee;
})
.flatMap(employeeRepository::save));
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(employeeMono, Employee.class).switchIfEmpty(notFound);
}
Thanks to Stepan Tsybulski.

Handler Goblal Exceptions Spring - add data when sending exception

I have a doubt about how to pass more data to throw an exception, I want to pass more data at the time of launching it, to put that data in the service response ..
I have an exception handler class labeled #ControllerAdvice in spring, but I don't know the best way to pass the data.
This is the code I have
throw new OcspException("Exception OCSP");
public class OcspException extends RuntimeException {
private static final long serialVersionUID = 1L;
public OcspException(String businessMessage) {
super(businessMessage);
}
public OcspException(String businessMessage, Throwable throwable) {
super(businessMessage, throwable);
}
}
#ExceptionHandler(OcspException.class)
public ResponseEntity<Object> exception(OcspException exception,HttpServletRequest request) {
ResponseException response = new ResponseException();
response.setCode("404");
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
I have the idea to do it, but I don't know if it is a good practice ... in the OcspException class to create attributes with their setter and getters, and create the constructor that receives this data, to then extract the data in exception controller
throw new OcspException("Exception OCSP","Hello");
public class OcspException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String m;
public OcspException(String businessMessage) {
super(businessMessage);
}
public OcspException(String businessMessage, Throwable throwable) {
super(businessMessage, throwable);
}
public OcspException(String businessMessage, String message) {
super(businessMessage);
setM(message);
}
public String getM() {
return m;
}
public void setM(String m) {
this.m = m;
}
}
#ExceptionHandler(OcspException.class)
public ResponseEntity<Object> exception(OcspException exception,HttpServletRequest request) {
ResponseException response = new ResponseException();
response.setCode("404");
response.setDetails(exception.getM() );
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
Try making an model called ErrorDetails which will hold a timestamp, message, and details.
It may look like this:
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class ErrorDetails {
private LocalDateTime timeStamp;
private String message;
private String details;
}
Here's a sample of what my custom exceptions usually look like:
#Data
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class OrderNotFoundException extends RuntimeException {
private final String message;
public OrderNotFoundException(String message) {
super(message);
this.message = message;
}
}
Then for the #ExceptionHandler:
#ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorDetails>
orderNotFoundException(OrderNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = ErrorDetails.builder()
.timeStamp(LocalDateTime.now())
.message(ex.getMessage())
.details(request.getDescription(false))
.build();
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
The error response for an order not found ends up being this:
{
"timeStamp": "2019-10-07T21:31:37.186",
"message": "Order with id 70 was not found.",
"details": "uri=/api/v1/order"
}
This way you can add whatever extra details in the ErrorDetails object. I hope that helps!

How to perform Spring validation in MultiActionController?

How to perform Spring validation in MultiActionController?
Let's write the following one
public class Person {
private String name;
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And your MultiActionController
import static org.springframework.validation.ValidationUtils.*;
#Component
public class PersonController extends MultiActionController {
public PersonController() {
setMethodNameResolver(new InternalPathMethodNameResolver());
setValidators(new Validator[] {new Validator() {
public boolean supports(Class clazz) {
return clazz.isAssignableFrom(Person.class);
}
public void validate(Object command, Errors errors) {
rejectIfEmpty(errors, "age", "", "Age is required");
rejectIfEmptyOrWhitespace(errors, "name", "", "Name is required");
}
}});
}
public ModelAndView add(HttpServletRequest request, HttpServletResponse response, Person person) throws Exception {
// do something (save our Person object, for instance)
return new ModelAndView();
}
}
MultiActionController defines a property called validators where you should provide any Validator used by your MultiActionController. Here you can see a piece of code which is responsible for validating your Command object inside MultiActionController
ServletRequestDataBinder binder = ...
if (this.validators != null)
for (int i = 0; i < this.validators.length; i++) {
if (this.validators[i].supports(command.getClass())) {
ValidationUtils.invokeValidator(this.validators[i], command, binder.getBindingResult());
}
}
}
/**
* Notice closeNoCatch method
*/
binder.closeNoCatch();
closeNoCatch method says
Treats errors as fatal
So if your Validator returns any Error, closeNoCatch will throw a ServletRequestBindingException. But, you can catch it inside your MultiActionController method, as follows
public ModelAndView hanldeBindException(HttpServletRequest request, HttpServletResponse response, ServletRequestBindingException bindingException) {
// do what you want right here
BindException bindException = (BindException) bindingException.getRootCause();
return new ModelAndView("personValidatorView").addAllObjects(bindException.getModel());
}
In order to test, let's do the following one
#Test
public void failureValidation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setRequestURI("http://127.0.0.1:8080/myContext/person/add.html");
/**
* Empty values
*/
request.addParameter("name", "");
request.addParameter("age", "");
PersonController personController = new PersonController();
ModelAndView mav = personController.handleRequest(request, new MockHttpServletResponse());
BindingResult bindingResult = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + "command");
/**
* Our Validator rejected 2 Error
*/
assertTrue(bindingResult.getErrorCount() == 2);
for (Object object : bindingResult.getAllErrors()) {
if(object instanceof FieldError) {
FieldError fieldError = (FieldError) object;
System.out.println(fieldError.getField());
}
}
}

Resources