Equivalent of Jackson's #JsonUnwrapped in Jsonb - jsonb

I tried to implement the equivalent of Jacksons's #JsonUnwrapped in Jsonb (using Yasson) with this:
#Retention(RetentionPolicy.RUNTIME)
#JsonbTypeSerializer(UnwrappedJsonbSerializer.class)
public #interface JsonbUnwrapped {}
public class UnwrappedJsonbSerializer implements JsonbSerializer<Object> {
#Override
public void serialize(Object object, JsonGenerator generator, SerializationContext context) {
context.serialize(object, new UnwrappedJsonGenerator(generator));
}
}
public class UnwrappedJsonGenerator extends JsonGeneratorWrapper {
private int level;
public UnwrappedJsonGenerator(JsonGenerator delegate) {
super(delegate);
}
#Override
public JsonGenerator writeStartObject(String name) {
return level++ == 0 ? this : super.writeStartObject(name);
}
#Override
public JsonGenerator writeStartArray(String name) {
return level++ == 0 ? this : super.writeStartArray(name);
}
#Override
public JsonGenerator writeEnd() {
return --level == 0 ? this : super.writeEnd();
}
}
public class Person {
#JsonbUnwrapped
public Name getName() {
return new Name();
}
public static class Name {
public String getFirstName() {
return "John";
}
public String getLastName() {
return "Doe";
}
}
}
JsonbBuilder.create().toJson(new Person())
But this raises an exception javax.json.bind.JsonbException: Recursive reference has been found in class class Person$Name because my UnwrappedJsonbSerializer calls SerializationContext.serialize() with the same object that was initially passed.
Is there any other way to achieve that without resorting to custom serializers for Person or Name ?

Related

How to register hibernate custom multiple EventListeners

My scenario is need yo track entity property changes. I have used Hibernate PostUpdateEventListener interface to achieve that.
Following is my generic event listener class.
public abstract class EventListener<DOMAIN extends BaseModel> implements PostUpdateEventListener {
public abstract LogSupport getService();
public abstract BaseModel getLogDomain(DOMAIN domain);
#SuppressWarnings("unchecked")
private DOMAIN getDomain(BaseModel model) {
return (DOMAIN) model;
}
public void postUpdate(PostUpdateEvent event, BaseModel model) {
getService().createUpdateLog(getDomain(model), getPostUpdateEventNotes(event));
}
private String getPostUpdateEventNotes(PostUpdateEvent event) {
StringBuilder sb = new StringBuilder();
for (int p : event.getDirtyProperties()) {
sb.append("\t");
sb.append(event.getPersister().getEntityMetamodel().getProperties()[p].getName());
sb.append(" (Old value: ")
.append(event.getOldState()[p])
.append(", New value: ")
.append(event.getState()[p])
.append(")\n");
}
System.out.println(sb.toString());
return sb.toString();
}
}
And this is my custom entity listener.
#Component
public class AssetEventListener extends EventListener<Asset> {
private static final long serialVersionUID = -6076678526514705909L;
#Autowired
#Qualifier("assetLogService")
private LogSupport logSupport;
#Override
public LogSupport getService() {
AutowireHelper.autowire(this, logSupport);
return logSupport;
}
#PostPersist
public void onPostInsert(PostInsertEvent event) {
if (event.getEntity() instanceof BaseModel){
super.postPersist( event, (BaseModel) event.getEntity() );
}
}
#Override
public void onPostUpdate(PostUpdateEvent event) {
if (event.getEntity() instanceof BaseModel){
super.postUpdate( event, (BaseModel) event.getEntity() );
}
}
#Override
public BaseModel getLogDomain(Asset domain) {
return domain;
}
#Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return false;
}
}
And I called it from #EntityListeners
#Entity
#Table(name = "tbl_asset")
#EntityListeners({ AssetEventListener.class })
public class Asset extends BaseModel {
}
Listener not call when update the entity. Any help would be greatly appreciated.
Thanks,

#Embedded Audit was working with spring boot 1.4 but now not working after upgrade to spring boot 2.1.x

public interface EAuditable {
public static interface Update{
public EUpdateInfo getUpdateInfo();
public void setUpdateInfo(EUpdateInfo updateInfo);
}
public static interface Create{
public ECreateInfo getCreateInfo();
public void setCreateInfo(ECreateInfo createInfo);
}
public static interface UpdateDate{
public ZonedDateTime getLastUpdatedOn();
public void setLastUpdatedOn(ZonedDateTime date);
}
public static interface UpdateUser{
public String getLastUpdatedBy();
public void setLastUpdatedBy(String user);
}
public static interface CreateDate{
public ZonedDateTime getCreatedOn();
public void setCreatedOn(ZonedDateTime date);
}
public static interface CreateUser{
public String getCreatedBy();
public void setCreatedBy(String user);
}
}
#Entity
#Table(name = "TRACKABLE_ITEM")
#Inheritance(strategy = InheritanceType.JOINED)
public class ETrackableItem extends AbstractIdTenantPersistable implements EAuditable.Create, EAuditable.Update {
#Embedded
private ECreateInfo createInfo;
#Embedded
private EUpdateInfo updateInfo;
}
#MappedSuperclass
//JPA: For Eclipse Link, use #AdditionaliCritera instead of #Filter and #FilterDef
#FilterDef(name="multiTenant", parameters=#ParamDef( name="tenant_id", type="string" ) )
#Filter(name="multiTenant", condition=":tenant_id = TENANT_ID")
public abstract class AbstractIdTenantPersistable extends AbstractIdPersistable {
#Column(name = "TENANT_ID", length = 12)
protected String tenantId;
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
protected AbstractIdTenantPersistable(){}
protected AbstractIdTenantPersistable(UUID id) {
super(id);
}
protected AbstractIdTenantPersistable(UUID id, String tenantId) {
super(id);
this.tenantId = tenantId;
}
}
#MappedSuperclass
#EntityListeners(value = { PersistableEntityListener.class })
#TypeDef(name = "json", typeClass = JsonUserType.class)
public abstract class AbstractPersistable {
public enum ConstraintType{UniqueName, Unique, Others};
/**
* Returns the constraint name to its type mapping.
* #param name
* #return
*/
public ConstraintType getConstraintType(String name){
return ConstraintType.Others;
}
}
public class PersistableEntityListener {
#PrePersist
public void prePersist(AbstractPersistable e) {
if (e instanceof EAuditable.Create){
EAuditable.Create create = (EAuditable.Create) e;
create.setCreateInfo(new ECreateInfo(ZonedDateTime.now(ZoneId.of("UTC")), getUserName()));
}
if (e instanceof EAuditable.CreateDate){
EAuditable.CreateDate createDate = (EAuditable.CreateDate) e;
createDate.setCreatedOn(ZonedDateTime.now(ZoneId.of("UTC")));
}
if (e instanceof EAuditable.CreateUser){
EAuditable.CreateUser createUser = (EAuditable.CreateUser) e;
createUser.setCreatedBy(getUserName());
}
if (e instanceof AbstractIdTenantPersistable){
((AbstractIdTenantPersistable)e).setTenantId(getTenantId());
}
preUpdate(e);
}
#PreUpdate
public void preUpdate(AbstractPersistable e) {
if (e instanceof EAuditable.Update){
EAuditable.Update update = (EAuditable.Update) e;
update.setUpdateInfo(new EUpdateInfo(ZonedDateTime.now(ZoneId.of("UTC")), getUserName()));
}
if (e instanceof EAuditable.UpdateDate){
EAuditable.UpdateDate updateDate = (EAuditable.UpdateDate) e;
updateDate.setLastUpdatedOn(ZonedDateTime.now(ZoneId.of("UTC")));
}
if (e instanceof EAuditable.UpdateUser){
EAuditable.UpdateUser updateUser = (EAuditable.UpdateUser) e;
updateUser.setLastUpdatedBy(getUserName());
}
}
private String getUserName() {
//checkContext();
if (isUserSet()){
return ContextHolder.get().getAuthenticatedContext().getUserName();
}
return "-";
}
private String getTenantId() {
//checkContext();
if (isTenantSet()){
return ContextHolder.get().getAuthenticatedContext().getTenantId();
}
return "-";
}
private boolean isUserSet(){
return ContextHolder.get() != null &&
ContextHolder.get().getAuthenticatedContext() != null &&
ContextHolder.get().getAuthenticatedContext().getUserName() != null;
}
private boolean isTenantSet(){
return ContextHolder.get() != null &&
ContextHolder.get().getAuthenticatedContext() != null &&
ContextHolder.get().getAuthenticatedContext().getTenantId() != null;
}
private boolean isContextSet(){
return isUserSet() && isTenantSet();
}
private void checkContext(){
BeanHolder.asserts().isTrue(
isContextSet(),`enter code here`
DomainException.class, "DOMAIN.CONTEXT_NOT_SET");
}
}
Below the code which was working fine for auto populate the audit info with the spring-data-jpa used with spring boot 1.4.x but it is not working when we upgraded to spring boot 2.1.x
Can any one please help if we have different way of handing for the same.
I need to follow this kind of #Embedded way of doing as here we are doing it by composition except inheritance.
Thanks in advance.

Register DynamicParameterizedType global

How can i register a global available DynamicParameterizedType in hibernate?
I wrote the following type:
public class QuantityType extends AbstractSingleColumnStandardBasicType<Quantity<?>> implements DynamicParameterizedType {
public static final QuantityType INSTANCE = new QuantityType();
public QuantityType() {
super(DoubleTypeDescriptor.INSTANCE, new QuantityJavaDescriptor(AbstractUnit.ONE));
}
#Override
public String getName() {
return QuantityType.class.getSimpleName();
}
#Override
public void setParameterValues(Properties parameters) {
ParameterType reader = (ParameterType) parameters.get(PARAMETER_TYPE);
if (reader == null) throw new RuntimeException("Not Implemented");
Unit<?> resolvedUnit = resolveUnit(reader);
setJavaTypeDescriptor(new QuantityJavaDescriptor(resolvedUnit));
}
private Unit<?> resolveUnit(ParameterType reader) {...}
}
and registered it with a service registration in hibernate:
public class QuantityTypeRegistration implements TypeContributor {
#Override
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
typeContributions.contributeType(QuantityType.INSTANCE);
}
}
If i use the type in an entity, the wrap/unwrap method of the JavaTypeDescriptor gets called,
but instead of the parameterized JavaTypeDescriptor, the default JavaTypeDescriptor gets called. For some reason the setParameterValues method was not called.
Code: https://github.com/raynigon/unit-api/tree/master/jpa-starter/src/main/java/com/raynigon/unit_api/jpa

Converter works for RequestParameter but not for RequestBody field

I have the following converter:
#Component
public class CountryEnumConverter implements Converter<String, CountryEnum> {
#Override
public CountryEnum convert(String country) {
CountryEnum countryEnum = CountryEnum.getBySign(country);
if (countryEnum == null) {
throw new IllegalArgumentException(country + " - Country is not supported!");
}
return countryEnum;
}
}
Registered it is invoked when used for RequestParam
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestParam CountryEnum country) {
....
}
But this converter is never invoked when used for field in the RequstBody:
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestBody MyBody myBody) {
....
}
public class MyBody {
#NotNull
private CountryEnum country;
public MyBody() {
}
public CountryEnum getCountry() {
return country;
}
public void setCountry(CountryEnum country) {
this.country = country;
}
}
Your existing org.springframework.core.convert.converter.Converter instance will only work with data submitted as form encoded data. With #RequestBody you are sending JSON data which will be deserialized using using the Jackson library.
You can then create an instance of com.fasterxml.jackson.databind.util.StdConverter<IN, OUT>
public class StringToCountryTypeConverter extends StdConverter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
and then apply this on the target property:
public class MyBody {
#NotNull
#JsonDeserialize(converter = StringToCountryTypeConverter.class)
private CountryEnum country;
}
Given the similarity of the 2 interfaces I would expect that you could create one class to handle both scenarios:
public class StringToCountryTypeConverter extends StdConverter<String, CountryType>
implements org.springframework.core.convert.converter.Converter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
I found out that if I add the following code to my CountryEnum will do the trick.
#JsonCreator
public static CountryEnum fromString(String value) {
CountryEnumConverter converter = new CountryEnumConverter();
return converter.convert(value);
}

Spring Boot - Apache Derby duplicating IDs of a ListArray objects

This little project follows a basic MVC pattern, i'm using spring boot and apache derby as an embedded data base.
1) When adding a hardcoded object list inside service class, they all share the same id. Is there an explanation for this behavior ?
This shows the problem (Don't mind the 'kkk' objects, i've solved that part already)
Screen1
So this is the object account i'm working with :
#Entity
public class Account {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String owner;
private double budget;
private double budgetInvest;
private double budgetFonction;
public Account() {
}
public Account(String owner, double budget, double budgetInvest, double budgetFonction
) {
this.owner=owner;
this.budget = budget;
this.budgetInvest = budgetInvest;
this.budgetFonction = budgetFonction;
}
public Account (String owner, double budget) {
this.owner = owner;
this.budget=budget;
}
public Account (String owner) {
this.owner=owner;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public double getBudget() {
return budget;
}
public void setBudget(double budget) {
this.budget = budget;
}
public double getBudgetInvest() {
return budgetInvest;
}
public void setBudgetInvest(double budgetInvest) {
this.budgetInvest = budgetInvest;
}
public double getBudgetFonction() {
return budgetFonction;
}
public void setBudgetFonction(double budgetFonction) {
this.budgetFonction = budgetFonction;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
These are the lines responsible for displaying the objects inside the view :
<tr th:each="account : ${accounts}">
<td th:text="${account.id}">id</td>
<td><a href="#" th:text="${account.owner}">Title
...</a></td>
<td th:text="${account.budget}">Text ...</td>
</tr>
Here is the controller :
#Controller
public class AccountController {
#Autowired
private AccountService accountService;
#RequestMapping(value="/", method=RequestMethod.GET)
public String index() {
return "index";
}
#RequestMapping(value="/accountAdd", method=RequestMethod.GET)
public String addAccount(Model model) {
model.addAttribute("account", new Account());
return "accountAdd";
}
#RequestMapping(value="/accountAdd", method=RequestMethod.POST)
public String postAccount(#ModelAttribute Account account) {
accountService.addAccount(account);
return "redirect:listAccount";
}
#RequestMapping(value="/listAccount", method=RequestMethod.GET)
public String listAccount(Model model) {
System.out.println(accountService.getAllAccounts());
model.addAttribute("accounts",accountService.getAllAccounts());
return "listAccount";
}
}
And finally the service class :
#Service
public class AccountService {
#Autowired
private AccountRepository accountRepository;
public List<Account> getAllAccounts(){
List<Account>accounts = new ArrayList<>(Arrays.asList(
new Account("Maths Department",1000000,400000,600000),
new Account("Physics Department",7000000,200000,500000),
new Account("Science Department",3000000,700000,1000000)
));
accountRepository.findAll().forEach(accounts::add);
return accounts;
}
public Account getAccount(long id) {
return accountRepository.findById(id).orElse(null);
}
public void addAccount(Account account) {
accountRepository.save(account);
}
public void updateAccount(long id, Account account) {
accountRepository.save(account);
}
public void deleteAccount(long id) {
accountRepository.deleteById(id);
}
}
Ok, so while i haven't yet found the exact answer as to why it affects the same id for every object in a static list.
I found an elegant workaround to not only solve the issue but also enhance the structure of the code.
Instead of doing whatever barbaric initialization I was trying to perform, It's way better to do this inside the main class :
#SpringBootApplication
public class PayfeeApplication {
#Autowired
private AccountRepository accountRepository;
public static void main(String[] args) {
SpringApplication.run(PayfeeApplication.class, args);
}
#Bean
InitializingBean sendDatabase() {
return () -> {
accountRepository.save(new Account("Maths Department",1000000,400000,600000));
accountRepository.save(new Account("Physics Department",7000000,200000,500000));
accountRepository.save(new Account("Science Department",3000000,700000,1000000));
};
}
}

Resources