JSF 2 managed bean is being shared across mulitple users logged in different window - spring

I am using JSF2, Spring 3 and Mybatis. On user login, I am doing authentication from ConfigAutomationLoginBean.java which has other beans as its Managed properties. The problem is that my beans are being shared across multiple users in different browser window. All my beans are SessionScoped and if I don't keep it that way, I may not get navigation screen after login. I guess JSF creates all managed bean with SessionScoped attribute by default singleton. Would it be good idean if I make the initial bean authentication bean ie. ConfigAutomationLoginBean and other beans autorwired to it using Spring 3 and remove the JSF for intial bean loading? Below is my login code:
import java.io.Serializable;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import com.telus.routerconfigurationtool.dto.UserProfileDTO;
import com.telus.routerconfigurationtool.service.UserManagementService;
import com.telus.routerconfigurationtool.util.CommonUtil;
#Component
#ManagedBean(name = "configAutomationLoginBean")
#SessionScoped
public class ConfigAutomationLoginBean implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.getLogger(ConfigAutomationLoginBean.class);
private String id;
private String password;
private String role;
#ManagedProperty(value = "#{userManagementService}")
private UserManagementService userManagementService;
#ManagedProperty(value = "#{treeNavigationBean}")
private TreeNavigationBean treeNavigationBean;
#ManagedProperty(value = "#{breadCrumbBean}")
private BreadCrumbBean breadCrumbBean;
public String authenticateUser() {
/** Reset and Update BreadCrumb - Add Nodes for Create User **/
breadCrumbBean.resetBreadCrumbModel();
Boolean authenticUser = false;
//TODO logic to authenticate user. authenticUser set true if authentication
//TODO Temporary setting to true
authenticUser = true;
if (authenticUser) {
return authorizeUser();
} else {
CommonUtil.displayFacesMessage(this.getClass(), FacesContext.getCurrentInstance(),
FacesMessage.SEVERITY_ERROR, "ERR1", id);
return "index";
}
}
private String authorizeUser() {
UserProfileDTO userProfileDTO = new UserProfileDTO();
CommonUtil.copyProperties(userProfileDTO, this);
Boolean authorizedUser = false;
// logic to authorize user. authorizedUser set true if authorization is
// successful
authorizedUser = userManagementService.authorizeUser(userProfileDTO);
if (authorizedUser) {
// Set User Role fetched from Database
this.role = userProfileDTO.getRole();
treeNavigationBean.setLoggedInUserId(id);
treeNavigationBean.setLoggedInUserRole(role);
treeNavigationBean.createTreeByUserRole();
treeNavigationBean.setViewCenterContent(null);
return "treeNavigation";
} else {
// Display Error Message that user is not authorized.
CommonUtil.displayFacesMessage(this.getClass(), FacesContext.getCurrentInstance(),
FacesMessage.SEVERITY_ERROR, "ERR2", id);
return "index";
}
}
/**
* #return the id
*/
public String getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(String id) {
if (StringUtils.isBlank(id)) {
this.id = id;
} else {
this.id = id.toUpperCase();
}
}
/**
* #return the password
*/
public String getPassword() {
return password;
}
/**
* #param password the password to set
*/
public void setPassword(String password) {
this.password = password;
}
/**
* #return the role
*/
public String getRole() {
return role;
}
/**
* #param role the role to set
*/
public void setRole(String role) {
this.role = role;
}
/**
* #return the userManagementService
*/
public UserManagementService getUserManagementService() {
return userManagementService;
}
/**
* #param userManagementService the userManagementService to set
*/
public void setUserManagementService(UserManagementService userManagementService) {
this.userManagementService = userManagementService;
}
/**
* #return the treeNavigationBean
*/
public TreeNavigationBean getTreeNavigationBean() {
return treeNavigationBean;
}
/**
* #param treeNavigationBean the treeNavigationBean to set
*/
public void setTreeNavigationBean(TreeNavigationBean treeNavigationBean) {
this.treeNavigationBean = treeNavigationBean;
}
/**
* #return the breadCrumbBean
*/
public BreadCrumbBean getBreadCrumbBean() {
return breadCrumbBean;
}
/**
* #param breadCrumbBean the breadCrumbBean to set
*/
public void setBreadCrumbBean(BreadCrumbBean breadCrumbBean) {
this.breadCrumbBean = breadCrumbBean;
}
}
Note: Since TreeNavigation bean is sessionscoped and single instance is shared, loggedInUserName is changed everytime different user is logging in. If user1 and user 2 logged in, then user1 who logged in first will see the screen of user2.
#ManagedBean(name = "treeNavigationBean")
#SessionScoped
public class TreeNavigationBean implements Serializable {
private static final long serialVersionUID = 1892577430001834938L;
private static final Logger LOGGER = Logger.getLogger(TreeNavigationBean.class);
private TreeNode root;
private TreeNode selectedNode;
private String loggedInUserId;
private String loggedInUserRole;
private String loggedInUserName;
private String viewCenterContent;
private String userAction;
#ManagedProperty(value = "#{userProfileBean}")
private UserProfileBean userProfileBean;
#ManagedProperty(value = "#{createConfigurationBean}")
private CreateConfigurationBean createConfigurationBean;
#ManagedProperty(value = "#{placeholderBean}")
private CreateConfigPlaceholderBean placeholderBean;
#ManagedProperty(value = "#{ncParamMappingBean}")
private NCParamMappingBean ncParamMappingBean;
#ManagedProperty(value = "#{breadCrumbBean}")
private BreadCrumbBean breadCrumbBean;
#ManagedProperty(value = "#{createTemplateBean}")
private CreateTemplateBean createTemplateBean;
#ManagedProperty(value = "#{configurationManagementBean}")
private ConfigurationManagementBean configurationManagementBean;
public void createTreeByUserRole() {
root = new DefaultTreeNode("Root", null);
if (TreeNodesEnum.SUPER_USER.equals(loggedInUserRole)) {
addCreateConfigurationNodes();
addTemplateAdministrationNodes();
addUserAdministrationNodes();
} else if (TreeNodesEnum.ADMIN_USER.equals(loggedInUserRole)) {
addCreateConfigurationNodes();
addTemplateAdministrationNodes();
} else if (TreeNodesEnum.NORMAL_USER.equals(loggedInUserRole)) {
addCreateConfigurationNodes();
}
}
.....................

With #Component you are using spring mvc beans instead of JSF beans. You can either switch to JSF beans or use Spring scopes, for example #Scope("session").

Related

How to retrieve the repository from JHipster spring controller?

I have a JHipster microservice application, and I've added a spring controller. However, it is generated without a repository and I don't know how to retrieve it to perform data tasks.
This is the code:
#RestController
#RequestMapping("/api/data")
public class DataResource {
private final Logger log = LoggerFactory.getLogger(DataResource.class);
private final DeviceRepository deviceRepository;
public DataResource() {
}
/**
* GET global
*/
#GetMapping("/global")
public ResponseEntity<GlobalStatusDTO[]> global() {
List<Device> list=deviceRepository.findAll();
GlobalStatusDTO data[]=new GlobalStatusDTO[]{new GlobalStatusDTO(list.size(),1,1,1,1)};
return ResponseEntity.ok(data);
}
}
EDIT: I need to inject an already existing repository, here is the CRUD part where the repository is initialized:
#RestController
#RequestMapping("/api")
#Transactional
public class DeviceResource {
private final Logger log = LoggerFactory.getLogger(DeviceResource.class);
private static final String ENTITY_NAME = "powerbackDevice";
#Value("${jhipster.clientApp.name}")
private String applicationName;
private final DeviceRepository deviceRepository;
public DeviceResource(DeviceRepository deviceRepository) {
this.deviceRepository = deviceRepository;
}
/**
* {#code POST /devices} : Create a new device.
*
* #param device the device to create.
* #return the {#link ResponseEntity} with status {#code 201 (Created)} and with body the new device, or with status {#code 400 (Bad Request)} if the device has already an ID.
* #throws URISyntaxException if the Location URI syntax is incorrect.
*/
#PostMapping("/devices")
public ResponseEntity<Device> createDevice(#Valid #RequestBody Device device) throws URISyntaxException {
...
I might misunderstand you, but your first code part doesn't work, because, you didn't inject DeviceRepository by the constructor. Of course, there are other methods of injections.
#RestController
#RequestMapping("/api/data")
public class DataResource {
private final Logger log = LoggerFactory.getLogger(DataResource.class);
private final DeviceRepository deviceRepository;
//changes are here only, constructor method of injection
public DataResource(DeviceRepository deviceRepository) {
this.deviceRepository = deviceRepository;
}
/**
* GET global
*/
#GetMapping("/global")
public ResponseEntity<GlobalStatusDTO[]> global() {
List<Device> list=deviceRepository.findAll();
GlobalStatusDTO data[]=new GlobalStatusDTO[]{new GlobalStatusDTO(list.size(),1,1,1,1)};
return ResponseEntity.ok(data);
}
}

Repository returns empty list in Spring boot

I am trying to write a simple REST to pull records from a table that was shared with me. Since the table doesn't have a default ID column, I embedded a pk column to the entity object. Please find the code below for your review.
The issue I'm facing is that the repository.findByMediaType, where mediaType is one of the entity properties, returns empty list. I made sure the query param is not null and there are records in the table for the param passed. I tried findAll as well but didn't work. I can't seem to find what's wrong with the code. I am new to spring boot and would like to know the different ways I can debug this.
Service implementation class
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hyb.enterprisedashboard.api.entity.Tenders;
import com.hyb.enterprisedashboard.api.repository.DashboardRepository;
#Service
public class DashboardServiceImpl implements DashboardService{
Logger logger = LoggerFactory.getLogger(DashboardServiceImpl.class);
#Autowired
DashboardRepository dashboardRepository;
#Override
public List<Tenders> getTenderByMediaType(String mediaType) {
List<Tenders> tenderList = dashboardRepository.findAll();
//findByMediaType(mediaType);
tenderList.stream().forEach(tender -> {
logger.info("Order {} paid via {}",tender.getId().getOrderNumber(), tender.getMediaType());
});
return tenderList;
}
}
Entity class
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;
#Entity
#Table(name = "TENDERS")
public class Tenders {
/** The id. */
#EmbeddedId
private TendersPK id;
/** The dateTime. */
#Column(name="DATE_TIME")
private Date dateTime;
/** The tenderMedia. */
#Column(name="TENDERED_MEDIA")
private String tenderMedia;
/** The mediaType. */
#Column(name="MEDIA_TYPE")
private String mediaType;
/** The tenderAmount. */
#Column(name="TENDERED_AMOUNT")
private BigDecimal tenderAmount;
/**
* #return the id
*/
public TendersPK getId() {
return id;
}
/**
* #param id the id to set
*/
public void setId(TendersPK id) {
this.id = id;
}
/**
* #return the dateTime
*/
public Date getDateTime() {
return dateTime;
}
/**
* #param dateTime the dateTime to set
*/
public void setDateTime(Date dateTime) {
this.dateTime = dateTime;
}
/**
* #return the tenderMedia
*/
public String getTenderMedia() {
return tenderMedia;
}
/**
* #param tenderMedia the tenderMedia to set
*/
public void setTenderMedia(String tenderMedia) {
this.tenderMedia = tenderMedia;
}
/**
* #return the mediaType
*/
public String getMediaType() {
return mediaType;
}
/**
* #param mediaType the mediaType to set
*/
public void setMediaType(String mediaType) {
this.mediaType = mediaType;
}
/**
* #return the tenderAmount
*/
public BigDecimal getTenderAmount() {
return tenderAmount;
}
/**
* #param tenderAmount the tenderAmount to set
*/
public void setTenderAmount(BigDecimal tenderAmount) {
this.tenderAmount = tenderAmount;
}
#Override
public String toString() {
return "Tenders [id=" + id + ", dateTime=" + dateTime + ", tenderMedia=" + tenderMedia + ", mediaType="
+ mediaType + ", tenderAmount=" + tenderAmount + "]";
}
}
PK Embedded class
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
#Embeddable
public class TendersPK implements Serializable{
/** The Constant serialVersionUID.*/
private static final long serialVersionUID = 1L;
/**
*
*/
public TendersPK() {
}
/**
* #param storeNumber
* #param orderNumber
*/
public TendersPK(long storeNumber, long orderNumber) {
super();
this.storeNumber = storeNumber;
this.orderNumber = orderNumber;
}
#Column(name = "STORE_NUMBER")
private long storeNumber;
#Column(name = "ORDER_NUMBER")
private long orderNumber;
/**
* #return the storeNumber
*/
public long getStoreNumber() {
return storeNumber;
}
/**
* #param storeNumber the storeNumber to set
*/
public void setStoreNumber(long storeNumber) {
this.storeNumber = storeNumber;
}
/**
* #return the orderNumber
*/
public long getOrderNumber() {
return orderNumber;
}
/**
* #param orderNumber the orderNumber to set
*/
public void setOrderNumber(long orderNumber) {
this.orderNumber = orderNumber;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (orderNumber ^ (orderNumber >>> 32));
result = prime * result + (int) (storeNumber ^ (storeNumber >>> 32));
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof TendersPK))
return false;
TendersPK other = (TendersPK) obj;
if (orderNumber != other.orderNumber)
return false;
if (storeNumber != other.storeNumber)
return false;
return true;
}
#Override
public String toString() {
return "TendersPK [storeNumber=" + storeNumber + ", orderNumber=" + orderNumber + "]";
}
}
Repository class
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import com.hyb.enterprisedashboard.api.entity.Tenders;
import com.hyb.enterprisedashboard.api.entity.TendersPK;
#Repository
public interface DashboardRepository extends JpaRepository<Tenders, TendersPK>{
#Query("select t from Tenders t where t.mediaType = ?1")
List<Tenders> findByMediaType(String mediaType);
}
And I see the below query passed in the console
Hibernate: select tenders0_.order_number as order_number1_0_, tenders0_.store_number as store_number2_0_, tenders0_.date_time as date_time3_0_, tenders0_.media_type as media_type4_0_, tenders0_.tendered_amount as tendered_amount5_0_, tenders0_.tendered_media as tendered_media6_0_ from tenders tenders0_
Could anyone please help to find the cause?
This was happening to me and it turns out my spring.datasource.* properties were not being set. I had them in the wrong file and the were not being read.
I would think that my repository query would error out if I had not provided datasource url, username, and password - instead I would simply just get an empty list returned.
I ended up figuring out that I was not pulling my datasource credentials by adding this in my RestController:
#Value("${spring.datasource.username}")
String username;
Then I just printed username to the system.out.println. When starting the application I would get an error that spring.datasource.username was undefined. Hence I knew I was not loading datasource information that I thought I was.

What are the best practice for audit log(user activity) in micro-services?

In our microservice architecture, we are logging user-activity to mongo database table? Is there any good way to store and retrieve audit log?
You can think of a solution something similar to the below by storing AuditLogging into the Mongo db by using DAO pattern.
#Entity
#Table(name = "AuditLogging")
public class AuditLogging implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "auditid", updatable = false, nullable = false)
private Long auditId;
#Column(name = "event_type", length = 100)
#Enumerated(EnumType.STRING)
private AuditingEvent event;
#Column(name = "event_creator", length = 100)
#Enumerated(EnumType.STRING)
private EventCreator eventCreator;
#Column(name = "adminid", length = 20)
private String adminId;
#Column(name = "userid", length = 20)
private String userId;
#Column(name = "event_date")
private Date eventDate;
}
public class Constants {
public static final String EVENT_TYPE = "eventType";
public static final String EVENT_CREATOR = "eventCreator";
public static final String NEW_EMAIL_ID = "newEmailId";
public static final String OLD_EMAIL_ID = "oldEmailId";
}
public enum AuditEvent {
USER_REGISTRATION,
USER_LOGIN,
USER_LOGIN_FAIL,
USER_ACCOUNT_LOCK,
USER_LOGOFF,
USER_PASSWORD_CHANGE,
USER_PASSWORD_CHANGE_FAIL,
USER_FORGOT_PASSWORD,
USER_FORGOT_PASSWORD_FAIL,
ADMIN_LOGIN
}
public enum EventCreator {
ADMIN_FOR_SELF,
USER_FOR_SELF,
ADMIN_FOR_USER
}
public interface AuditingDao {
/**
* Stores the event into the DB/Mongo or Whatever
*
* #param auditLogging
* #return Boolean status
*/
Boolean createAuditLog(final AuditLogging auditLogging);
/* Returns the log occurrence of a specific event
*
* #param event
* #return List of logged events by type
*/
List<AuditLogging> getLogsForEvent(final AuditingEvent event);
}
public interface AuditingService {
/**
* Creates an Audit entry in the AuditLogging table using the
* DAO layer
*
* #param auditingEvent
* #param eventCreator
* #param userId
* #param adminId *
* #return {#link Boolean.TRUE} for success and {#link Boolean.FALSE} for
* failure
*/
Boolean createUserAuditEvent(final AuditEvent auditEvent,
final EventCreator eventCreator, final String userId, final String adminId,
final String newEmailId,final String oldEmailId);
/**
*
* Returns all event for a user/admin based on the id
*
* #param id
* #return List of logged events for an id
*/
List<AuditLogging> fetchLoggedEventsById(final String id);
/***
* Returns all event based on event type
*
* #param eventName
* #return List of logged events for an event
*/
List<AuditLogging> fetchLoggedEventsByEventName(final String eventName);
}
#Service("auditingService")
public class AuditServiceImpl implements AuditingService {
#Autowired
private AuditingDao auditingDao;
private static Logger log = LogManager.getLogger();
#Override
public Boolean createUserAuditingEvent(AuditEvent auditEvent,
EventCreator eventCreator, String userId, String adminId,
String newEmailId,String oldEmailId) {
AuditLogging auditLogging = new AuditLogging();
auditLogging.setEvent(auditingEvent);
auditLogging.setEventCreator(eventCreator);
auditLogging.setUserId(userId);
auditLogging.setAdminId(adminId);
auditLogging.setEventDate(new Date());
return Boolean.TRUE;
}
#Override
public List<AuditLogging> fetchLoggedEventsByEventName(
final String eventName) {
AuditEvent event = null;
try {
event = AuditingEvent.valueOf(eventName);
} catch (Exception e) {
log.error(e);
return Collections.emptyList();
}
return auditingDao.getLogsForEvent(event);
}
public void setAuditingDao(AuditingDao auditingDao) {
this.auditingDao = auditingDao;
}
}
Writing an aspect is always good for this type of scenarios by pointing to the appropriate controller method to trigger the event.
#Aspect
#Component("auditingAspect")
public class AuditingAspect {
#Autowired
AuditingService auditingService;
/* The below controllers you can think of your microservice endpoints
*/
#Pointcut("execution(* com.controller.RegistrationController.authenticateUser(..)) ||execution(* com.controller.RegistrationController.changeUserPassword(..)) || execution(* com.controller.RegistrationController.resetPassword(..)) ||execution(* com.controller.UpdateFunctionalityController.updateCustomerDetails(..))")
public void aroundPointCut() {}
#Around("aroundPointCut()")
public Object afterMethodInControllerClass(ProceedingJoinPoint joinPoint)
throws Throwable {
joinPoint.getSignature().getName();
joinPoint.getArgs();
// auditingService
Object result = joinPoint.proceed();
ResponseEntity entity = (ResponseEntity) result;
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
if(!((request.getAttribute(Constants.EVENT_TYPE).toString()).equalsIgnoreCase(AuditEvent.USER_LOGIN.toString()) || (((request.getAttribute(Constants.EVENT_TYPE).toString()).equalsIgnoreCase(AuditEvent.ADMIN_LOGIN.toString()))))){
auditingService.createUserAuditEvent(
(AuditingEvent) request.getAttribute(Constants.EVENT_TYPE),
(EventCreator) request.getAttribute(Constants.EVENT_CREATOR),
(request.getAttribute(Constants.USER_ID)!= null ? request.getAttribute(Constants.USER_ID).toString():""), null,
(request.getAttribute(Constants.NEW_EMAIL_ID) == null ? ""
: request.getAttribute(Constants.NEW_EMAIL_ID).toString()),
(request.getAttribute(Constants.OLD_EMAIL_ID) == null ? ""
: request.getAttribute(Constants.OLD_EMAIL_ID).toString()));
}
return entity;
}
}
From the REST controller the Aspect will be triggered when it finds the corresponding event.
#RestController
public class RegistrationController {
#RequestMapping(path = "/authenticateUser", method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
/* This method call triggers the aspect */
#ResponseBody
public ResponseEntity<String> authenticateUser(HttpServletRequest request, #RequestBody User user)
throws Exception {
request.setAttribute(Constants.EVENT_TYPE, AuditingEvent.USER_LOGIN);
request.setAttribute(Constants.EVENT_CREATOR, EventCreator.USER_FOR_SELF);
request.setAttribute(Constants.USER_ID, user.getUserId());
ResponseEntity<String> responseEntity = null;
try {
// Logic for authentication goes here
responseEntity = new ResponseEntity<>(respData, HttpStatus.OK);
} catch (Exception e) {
request.setAttribute(Constants.EVENT_TYPE, AuditEvent.USER_LOGIN_FAIL);
responseEntity = new ResponseEntity<>(respData, HttpStatus.INTERNAL_SERVER_ERROR);
}
return responseEntity;
}
}
I hope this answer make sense and you can implement similar functionality for Mongo as well.
Cheers !

methods in an ejb session bean for an entity dont work in a spring MVC project

I created a maven entreprise application project, in the ejb module i put my entities in a package and i generated my session beans in an other package and i deployed my ejb module in glassfish,in the web module i added dependencies of spring and I created a controller that search the session bean and call the methode find all() but it doesnt get my objects stored in database, what should i do?
Category entity
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.mycompany.entities;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
/**
*
* #author DELL
*/
#Entity
#Table(name = "categorie")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Categorie.findAll", query = "SELECT c FROM Categorie c"),
#NamedQuery(name = "Categorie.findByIdCategorie", query = "SELECT c FROM Categorie c WHERE c.idCategorie = :idCategorie"),
#NamedQuery(name = "Categorie.findByDescription", query = "SELECT c FROM Categorie c WHERE c.description = :description"),
#NamedQuery(name = "Categorie.findByName", query = "SELECT c FROM Categorie c WHERE c.name = :name"),
#NamedQuery(name = "Categorie.findByNomPhoto", query = "SELECT c FROM Categorie c WHERE c.nomPhoto = :nomPhoto")})
public class Categorie implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idCategorie")
private Integer idCategorie;
#Size(max = 255)
#Column(name = "description")
private String description;
#Size(max = 20)
#Column(name = "name")
private String name;
#Size(max = 255)
#Column(name = "nomPhoto")
private String nomPhoto;
#Lob
#Column(name = "photo")
private byte[] photo;
#OneToMany(mappedBy = "idCategorie")
private List<Produit> produitList;
public Categorie() {
}
public Categorie(Integer idCategorie) {
this.idCategorie = idCategorie;
}
public Integer getIdCategorie() {
return idCategorie;
}
public void setIdCategorie(Integer idCategorie) {
this.idCategorie = idCategorie;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNomPhoto() {
return nomPhoto;
}
public void setNomPhoto(String nomPhoto) {
this.nomPhoto = nomPhoto;
}
public byte[] getPhoto() {
return photo;
}
public void setPhoto(byte[] photo) {
this.photo = photo;
}
#XmlTransient
public List<Produit> getProduitList() {
return produitList;
}
public void setProduitList(List<Produit> produitList) {
this.produitList = produitList;
}
#Override
public int hashCode() {
int hash = 0;
hash += (idCategorie != null ? idCategorie.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Categorie)) {
return false;
}
Categorie other = (Categorie) object;
if ((this.idCategorie == null && other.idCategorie != null) || (this.idCategorie != null && !this.idCategorie.equals(other.idCategorie))) {
return false;
}
return true;
}
#Override
public String toString() {
return "com.mycompany.entities.Categorie[ idCategorie=" + idCategorie + " ]";
}
}
AbstractFacade
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.mycompany.dao;
import java.util.List;
import javax.persistence.EntityManager;
/**
*
* #author DELL
*/
public abstract class AbstractFacade<T> {
private Class<T> entityClass;
public AbstractFacade(Class<T> entityClass) {
this.entityClass = entityClass;
}
protected abstract EntityManager getEntityManager();
public void create(T entity) {
getEntityManager().persist(entity);
}
public void edit(T entity) {
getEntityManager().merge(entity);
}
public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}
public T find(Object id) {
return getEntityManager().find(entityClass, id);
}
public List<T> findAll() {
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).getResultList();
}
public List<T> findRange(int[] range) {
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
javax.persistence.Query q = getEntityManager().createQuery(cq);
q.setMaxResults(range[1] - range[0] + 1);
q.setFirstResult(range[0]);
return q.getResultList();
}
public int count() {
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
cq.select(getEntityManager().getCriteriaBuilder().count(rt));
javax.persistence.Query q = getEntityManager().createQuery(cq);
return ((Long) q.getSingleResult()).intValue();
}
}
CategorieFacade
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.mycompany.dao;
import com.mycompany.entities.Categorie;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
/**
*
* #author DELL
*/
#Stateless
public class CategorieFacade extends AbstractFacade<Categorie> implements CategorieFacadeLocal {
#PersistenceContext(unitName = "com.mycompany_ProjetCommerce-ejb_ejb_1.0-SNAPSHOTPU")
private EntityManager em;
#Override
protected EntityManager getEntityManager() {
return em;
}
public CategorieFacade() {
super(Categorie.class);
}
}
CategorieFacadeLocal
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.mycompany.dao;
import com.mycompany.entities.Categorie;
import java.util.List;
import javax.ejb.Local;
/**
*
* #author DELL
*/
#Local
public interface CategorieFacadeLocal {
void create(Categorie categorie);
void edit(Categorie categorie);
void remove(Categorie categorie);
Categorie find(Object id);
List<Categorie> findAll();
List<Categorie> findRange(int[] range);
int count();
}
CategorieController
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.mycompany.controlleur;
import com.mycompany.dao.CategorieFacadeLocal;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.springframework.stereotype.*;
import org.springframework.ui.*;
import org.springframework.web.bind.annotation.*;
#Controller
#RequestMapping("categorie")
public class CategorieController {
CategorieFacadeLocal categorieFacade = lookupCategorieFacadeLocal();
#RequestMapping(method = RequestMethod.GET)
public String index(ModelMap modelmap){
modelmap.put("listeCategorie", categorieFacade.findAll());
return "index";
}
private CategorieFacadeLocal lookupCategorieFacadeLocal() {
try {
Context c = new InitialContext();
return (CategorieFacadeLocal) c.lookup("java:global/com.mycompany_ProjetCommerce-ear_ear_1.0-SNAPSHOT/com.mycompany_ProjetCommerce-ejb_ejb_1.0-SNAPSHOT/CategorieFacade!com.mycompany.dao.CategorieFacadeLocal");
} catch (NamingException ne) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "exception caught", ne);
throw new RuntimeException(ne);
}
}
}

How to POST nested entities with Spring Data REST

I'm building a Spring Data REST application and I'm having some problems when I try to POST it. The main entity has other two related entities nested.
There is a "questionary" object which has many answers and each one of these answers have many replies.
I generate a JSON like this from the front application to POST the questionary:
{
"user": "http://localhost:8080/users/1",
"status": 1,
"answers": [
{
"img": "urlOfImg",
"question": "http://localhost:8080/question/6",
"replies": [
{
"literal": "http://localhost:8080/literal/1",
"result": "6"
},
{
"literal": "http://localhost:8080/literal/1",
"result": "6"
}
]
},
{
"img": "urlOfImg",
"question": "http://localhost:8080/question/6",
"replies": [
{
"literal": "http://localhost:8080/literal/3",
"result": "10"
}
]
}
]
}
But when I try to post it, I get the follow error response:
{
"cause" : {
"cause" : {
"cause" : null,
"message" : "Template must not be null or empty!"
},
"message" : "Template must not be null or empty! (through reference chain: project.models.Questionary[\"answers\"])"
},
"message" : "Could not read JSON: Template must not be null or empty! (through reference chain: project.models.Questionary[\"answers\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Template must not be null or empty! (through reference chain: project.models.Questionary[\"answers\"])"
}
Edit:
I also add my repository:
#RepositoryRestResource(collectionResourceRel = "questionaries", path = "questionaries")
public interface InspeccionRepository extends JpaRepository<Inspeccion, Integer> {
#RestResource(rel="byUser", path="byUser")
public List<Questionary> findByUser (#Param("user") User user);
}
My Entity Questionary class is :
#Entity #Table(name="QUESTIONARY", schema="enco" )
public class Questionary implements Serializable {
private static final long serialVersionUID = 1L;
//----------------------------------------------------------------------
// ENTITY PRIMARY KEY ( BASED ON A SINGLE FIELD )
//----------------------------------------------------------------------
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEC_QUESTIONARY")
#SequenceGenerator(name = "SEC_QUESTIONARY", sequenceName = "ENCO.SEC_QUESTIONARY", allocationSize = 1)
#Column(name="IDQUES", nullable=false)
private Integer idques ;
//----------------------------------------------------------------------
// ENTITY DATA FIELDS
//----------------------------------------------------------------------
#Column(name="ESTATUS")
private Integer estatus ;
//----------------------------------------------------------------------
// ENTITY LINKS ( RELATIONSHIP )
//----------------------------------------------------------------------
#ManyToOne
#JoinColumn(name="IDUSER", referencedColumnName="IDUSER")
private User user;
#OneToMany(mappedBy="questionary", targetEntity=Answer.class)
private List<Answer> answers;
//----------------------------------------------------------------------
// CONSTRUCTOR(S)
//----------------------------------------------------------------------
public Questionary()
{
super();
}
//----------------------------------------------------------------------
// GETTERS & SETTERS FOR FIELDS
//----------------------------------------------------------------------
//--- DATABASE MAPPING : IDNSE ( NUMBER )
public void setIdnse( Integer idnse )
{
this.idnse = idnse;
}
public Integer getIdnse()
{
return this.idnse;
}
//--- DATABASE MAPPING : ESTADO ( NUMBER )
public void setEstatus Integer estatus )
{
this.estatus = estatus;
}
public Integer getEstatus()
{
return this.estatus;
}
//----------------------------------------------------------------------
// GETTERS & SETTERS FOR LINKS
//----------------------------------------------------------------------
public void setUser( Usuario user )
{
this.user = user;
}
public User getUser()
{
return this.user;
}
public void setAnswers( List<Respuesta> answers )
{
this.answers = answer;
}
public List<Answer> getAnswers()
{
return this.answers;
}
// Get Complete Object method public List<Answer>
getAnswerComplete() {
List<Answer> answers = this.answers;
return answers;
}
}
My Answer Entity:
#Entity #Table(name="ANSWER", schema="enco" ) public class Answer
implements Serializable {
private static final long serialVersionUID = 1L;
//----------------------------------------------------------------------
// ENTITY PRIMARY KEY ( BASED ON A SINGLE FIELD )
//----------------------------------------------------------------------
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEC_ANSWER")
#SequenceGenerator(name = "SEC_ANSWER", sequenceName = "ENCOADMIN.SEC_ANSWER", allocationSize = 1)
#Column(name="IDANS", nullable=false)
private Integer idans ;
//----------------------------------------------------------------------
// ENTITY DATA FIELDS
//----------------------------------------------------------------------
#Column(name="IMG", length=100)
private String img ;
//----------------------------------------------------------------------
// ENTITY LINKS ( RELATIONSHIP )
//----------------------------------------------------------------------
#ManyToOne
#JoinColumn(name="IDQUES", referencedColumnName="IDQUES")
private Questionary questionary ;
#OneToMany(mappedBy="answer", targetEntity=Reply.class)
private List<Reply> replies;
#ManyToOne
#JoinColumn(name="IDQUE", referencedColumnName="IDQUE")
private Question Question ;
//----------------------------------------------------------------------
// CONSTRUCTOR(S)
//----------------------------------------------------------------------
public Answer()
{
super();
}
//----------------------------------------------------------------------
// GETTER & SETTER FOR THE KEY FIELD
//----------------------------------------------------------------------
public void setIdans( Integer idans )
{
this.idans = idans ;
}
public Integer getIdans()
{
return this.idans;
}
//----------------------------------------------------------------------
// GETTERS & SETTERS FOR FIELDS
//----------------------------------------------------------------------
//--- DATABASE MAPPING : IMAGEN ( VARCHAR2 )
public void setImg( String img )
{
this.img = img;
}
public String getImg()
{
return this.img;
}
//----------------------------------------------------------------------
// GETTERS & SETTERS FOR LINKS
//----------------------------------------------------------------------
public void setQuestionary( Questionary questionary )
{
this.questionary = questionary;
}
public Questionary getQuestionary()
{
return this.questionary;
}
public void setReplies( List<Reply> contestaciones )
{
this.replies = replies;
}
public List<Reply> getReplies()
{
return this.replies;
}
public void setQuestion( Question question )
{
this.question = question;
}
public Question getQuestion()
{
return this.question;
}
}
And this is the error console:
Caused by: com.fasterxml.jackson.databind.JsonMappingException:
Template must not be null or empty! (through reference chain:
project.models.Questionary["answers"]) at
com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:232)
~[jackson-databind-2.3.3.jar:2.3.3] at *snip*
Try adding #RestResource(exported = false) on field answers in class Questionary.
According to me, this error occurs because the deserializer expects URIs to fetch the answers from, instead of having the answers nested in the JSON. Adding the annotation tells it to look in JSON instead.
I'm still seeing this error with 2.3.0.M1, but I finally found a workaround.
The basic issue is this: If you post the url of the embedded entity in the JSON, it works. If you post the actual embedded entity JSON, it doesn't. It tries to deserialize the entity JSON into a URI, which of course fails.
It looks like the issue is with the two TypeConstrainedMappingJackson2HttpMessageConverter objects that spring data rest creates in its configuration (in RepositoryRestMvcConfiguration.defaultMessageConverters()).
I finally got around the issue by configuring the supported media types of the messageConverters so that it skips those two and hits the plain MappingJackson2HttpMessageConverter, which works fine with nested entities.
For example, if you extend RepositoryRestMvcConfiguration and add this method, then when you send a request with content-type of 'application/json', it will hit the plain MappingJackson2HttpMessageConverter instead of trying to deserialize into URIs:
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
((MappingJackson2HttpMessageConverter) messageConverters.get(0))
.setSupportedMediaTypes(asList(MediaTypes.HAL_JSON));
((MappingJackson2HttpMessageConverter) messageConverters.get(2))
.setSupportedMediaTypes(asList(MediaType.APPLICATION_JSON));
}
That configures the message converters produced by defaultMessageConverters() in RepositoryRestMvcConfiguration.
Keep in mind that the plain objectMapper can't handle URIs in the JSON - you'll still need to hit one of the two preconfigured message converters any time you pass URIs of embedded entities.
One issue with your JSON is that you are trying to deserialize a string as a question:
"question": "http://localhost:8080/question/6"
In your Answer object, Jackson is expecting an object for question. It appears that you are using URLs for IDs, so instead of a string you need to pass something like this for your question:
"question": {
"id": "http://localhost:8080/question/6"
}
Try to update "Spring Boot Data REST Starter" library. Worked for me.
With Spring Boot 2.7.2 it is achievable with the following config (accepts both links and entities in the request bodies):
package com.my.project.config;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.CreatorProperty;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.repository.support.RepositoryInvokerFactory;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.support.EntityLookup;
import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.data.rest.webmvc.mapping.LinkCollector;
import org.springframework.data.rest.webmvc.support.ExcerptProjector;
import org.springframework.data.util.StreamUtils;
import org.springframework.hateoas.server.mvc.RepresentationModelProcessorInvoker;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.util.ReflectionUtils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static com.fasterxml.jackson.core.JsonToken.START_OBJECT;
// Allows POST'ing nested objects and not only links
#Configuration
public class CustomRepositoryRestMvcConfiguration implements RepositoryRestConfigurer {
private final ApplicationContext context;
private final PersistentEntities entities;
private final RepositoryInvokerFactory invokerFactory;
private final Repositories repositories;
private final Associations associations;
private final ExcerptProjector projector;
private final ObjectProvider<RepresentationModelProcessorInvoker> modelInvoker;
private final LinkCollector linkCollector;
private final RepositoryRestConfiguration repositoryRestConfiguration;
public CustomRepositoryRestMvcConfiguration(
ApplicationContext context,
PersistentEntities entities,
#Lazy RepositoryInvokerFactory invokerFactory,
Repositories repositories,
#Lazy Associations associations,
#Lazy ExcerptProjector projector,
#Lazy ObjectProvider<RepresentationModelProcessorInvoker> modelInvoker,
#Lazy LinkCollector linkCollector,
#Lazy RepositoryRestConfiguration repositoryRestConfiguration) {
this.context = context;
this.entities = entities;
this.invokerFactory = invokerFactory;
this.repositories = repositories;
this.associations = associations;
this.projector = projector;
this.modelInvoker = modelInvoker;
this.linkCollector = linkCollector;
this.repositoryRestConfiguration = repositoryRestConfiguration;
}
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
objectMapper.registerModule(persistentEntityJackson2Module(linkCollector));
}
protected Module persistentEntityJackson2Module(LinkCollector linkCollector) {
List<EntityLookup<?>> lookups = new ArrayList<>();
lookups.addAll(repositoryRestConfiguration.getEntityLookups(repositories));
lookups.addAll((Collection) beansOfType(context, EntityLookup.class).get());
EmbeddedResourcesAssembler assembler = new EmbeddedResourcesAssembler(entities, associations, projector);
PersistentEntityJackson2Module.LookupObjectSerializer lookupObjectSerializer = new PersistentEntityJackson2Module.LookupObjectSerializer(PluginRegistry.of(lookups));
// AssociationUriResolvingDeserializerModifier delegates
return new NestedSupportPersistentEntityJackson2Module(associations,
entities,
new UriToEntityConverter(entities, invokerFactory, repositories),
linkCollector,
invokerFactory,
lookupObjectSerializer,
modelInvoker.getObject(),
assembler
);
}
public static class NestedSupportPersistentEntityJackson2Module extends PersistentEntityJackson2Module {
public NestedSupportPersistentEntityJackson2Module(Associations associations,
PersistentEntities entities,
UriToEntityConverter converter,
LinkCollector collector,
RepositoryInvokerFactory factory,
LookupObjectSerializer lookupObjectSerializer,
RepresentationModelProcessorInvoker invoker,
EmbeddedResourcesAssembler assembler) {
super(associations, entities, converter, collector, factory, lookupObjectSerializer, invoker, assembler);
}
#Override
public SimpleModule setDeserializerModifier(BeanDeserializerModifier mod) {
super.setDeserializerModifier(new NestedObjectSuppAssociationUriResolvingDeserializerModifier(
(PersistentEntityJackson2Module.AssociationUriResolvingDeserializerModifier) mod)
);
return this;
}
}
#RequiredArgsConstructor
public static class NestedObjectSuppAssociationUriResolvingDeserializerModifier extends BeanDeserializerModifier {
private final PersistentEntityJackson2Module.AssociationUriResolvingDeserializerModifier uriDelegate;
#SneakyThrows
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config,
BeanDescription beanDesc,
BeanDeserializerBuilder builder) {
// Pushes Uri* deserializer
uriDelegate.updateBuilder(config, beanDesc, builder);
// Replace Uri* deserializers with delegates
var customizer = new ValueInstantiatorCustomizer(builder.getValueInstantiator(), config);
var properties = builder.getProperties();
while (properties.hasNext()) {
var prop = properties.next();
if (!prop.hasValueDeserializer()) {
continue;
}
if (prop.getValueDeserializer() instanceof PersistentEntityJackson2Module.UriStringDeserializer) {
customizer.replacePropertyIfNeeded(
builder,
prop.withValueDeserializer(new ObjectOrUriStringDeserializer(
prop.getValueDeserializer().handledType(),
prop.getValueDeserializer(),
new LateDelegatingDeser(prop.getType())
))
);
}
if ((Object) prop.getValueDeserializer() instanceof CollectionDeserializer) {
var collDeser = (CollectionDeserializer) ((Object) prop.getValueDeserializer());
if (!(collDeser.getContentDeserializer() instanceof PersistentEntityJackson2Module.UriStringDeserializer)) {
continue;
}
customizer.replacePropertyIfNeeded(
builder,
prop.withValueDeserializer(
new CollectionDeserializer(
collDeser.getValueType(),
new ObjectOrUriStringDeserializer(
prop.getValueDeserializer().handledType(),
((CollectionDeserializer) (Object) prop.getValueDeserializer()).getContentDeserializer(),
new LateDelegatingDeser(prop.getType().getContentType())
),
null,
collDeser.getValueInstantiator()
)
)
);
}
}
return customizer.conclude(builder);
}
#Getter
#RequiredArgsConstructor
public static class LateDelegatingDeser extends JsonDeserializer<Object> {
private final JavaType type;
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
return ctxt.findNonContextualValueDeserializer(type).deserialize(p, ctxt);
}
}
}
public static class ObjectOrUriStringDeserializer extends StdDeserializer<Object> {
private final JsonDeserializer<Object> uriDelegate;
private final JsonDeserializer<Object> vanillaDelegate;
public ObjectOrUriStringDeserializer(Class<?> type, JsonDeserializer<Object> uriDelegate, JsonDeserializer<Object> vanillaDelegate) {
super(type);
this.uriDelegate = uriDelegate;
this.vanillaDelegate = vanillaDelegate;
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException {
if (START_OBJECT == jp.getCurrentToken()) {
return vanillaDelegate.deserialize(jp, ctxt);
}
return uriDelegate.deserialize(jp, ctxt);
}
}
// Copied from original ValueInstantiatorCustomizer
public static class ValueInstantiatorCustomizer {
private final SettableBeanProperty[] properties;
private final StdValueInstantiator instantiator;
ValueInstantiatorCustomizer(ValueInstantiator instantiator, DeserializationConfig config) {
this.instantiator = StdValueInstantiator.class.isInstance(instantiator) //
? StdValueInstantiator.class.cast(instantiator) //
: null;
this.properties = this.instantiator == null || this.instantiator.getFromObjectArguments(config) == null //
? new SettableBeanProperty[0] //
: this.instantiator.getFromObjectArguments(config).clone(); //
}
/**
* Replaces the logically same property with the given {#link SettableBeanProperty} on the given
* {#link BeanDeserializerBuilder}. In case we get a {#link CreatorProperty} we also register that one to be later
* exposed via the {#link ValueInstantiator} backing the {#link BeanDeserializerBuilder}.
*
* #param builder must not be {#literal null}.
* #param property must not be {#literal null}.
*/
void replacePropertyIfNeeded(BeanDeserializerBuilder builder, SettableBeanProperty property) {
builder.addOrReplaceProperty(property, false);
if (!CreatorProperty.class.isInstance(property)) {
return;
}
properties[((CreatorProperty) property).getCreatorIndex()] = property;
}
/**
* Concludes the setup of the given {#link BeanDeserializerBuilder} by reflectively registering the potentially
* customized {#link SettableBeanProperty} instances in the {#link ValueInstantiator} backing the builder.
*
* #param builder must not be {#literal null}.
* #return
*/
BeanDeserializerBuilder conclude(BeanDeserializerBuilder builder) {
if (instantiator == null) {
return builder;
}
Field field = ReflectionUtils.findField(StdValueInstantiator.class, "_constructorArguments");
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, instantiator, properties);
builder.setValueInstantiator(instantiator);
return builder;
}
}
private static <S> org.springframework.data.util.Lazy<List<S>> beansOfType(ApplicationContext context, Class<?> type) {
return org.springframework.data.util.Lazy.of(() -> (List<S>) context.getBeanProvider(type)
.orderedStream()
.collect(StreamUtils.toUnmodifiableList()));
}
}
It is ugly, but it works. Don't forget about cascades and proper setters for entities, i.e. one must have for OneToMany:
public class DeliveryOrder {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private Collection<Delivery> deliveries;
public void setDeliveries(Collection<Delivery> deliveries) {
if (null != deliveries) {
deliveries.forEach(delivery -> delivery.setOrder(this));
}
this.deliveries = deliveries;
}
}

Resources