From 3c99c1ba8b0209c61ba61bab36a869d522e8766c Mon Sep 17 00:00:00 2001 From: Bennett Date: Sun, 29 Mar 2026 21:04:33 +0300 Subject: [PATCH 1/3] release: Fix error serializing data --- .../flextuma/core/config/JacksonConfig.java | 7 +- .../contact/services/ContactService.java | 74 +++++++++++++++++++ .../core/config/JacksonConfigTest.java | 19 +++++ 3 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/flexcodelabs/flextuma/core/config/JacksonConfig.java b/src/main/java/com/flexcodelabs/flextuma/core/config/JacksonConfig.java index 1be9ec8..94e9177 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/config/JacksonConfig.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/config/JacksonConfig.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -15,12 +16,16 @@ public class JacksonConfig { @Bean @Primary public ObjectMapper objectMapper() { + SimpleModule lazyLoadingSafeModule = new SimpleModule() + .setSerializerModifier(new LazyLoadingSafeBeanSerializerModifier()); + return JsonMapper.builder() .serializationInclusion(JsonInclude.Include.NON_NULL) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .addModule(lazyLoadingSafeModule) .findAndAddModules() .build(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/flexcodelabs/flextuma/modules/contact/services/ContactService.java b/src/main/java/com/flexcodelabs/flextuma/modules/contact/services/ContactService.java index d787355..9254835 100644 --- a/src/main/java/com/flexcodelabs/flextuma/modules/contact/services/ContactService.java +++ b/src/main/java/com/flexcodelabs/flextuma/modules/contact/services/ContactService.java @@ -1,13 +1,20 @@ package com.flexcodelabs.flextuma.modules.contact.services; +import java.util.List; import java.util.UUID; +import org.hibernate.Hibernate; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.flexcodelabs.flextuma.core.dtos.Pagination; +import com.flexcodelabs.flextuma.core.entities.auth.Role; +import com.flexcodelabs.flextuma.core.entities.auth.User; import com.flexcodelabs.flextuma.core.entities.contact.Contact; +import com.flexcodelabs.flextuma.core.entities.metadata.AbstractMetadataEntity; import com.flexcodelabs.flextuma.core.repositories.ContactRepository; import com.flexcodelabs.flextuma.core.services.BaseService; @@ -69,6 +76,46 @@ protected String getTableName() { return "contact"; } + @Override + @Transactional(readOnly = true) + public Pagination findAllPaginated(Pageable pageable, List filter, String fields, String rootJoin) { + Pagination pagination = super.findAllPaginated(pageable, filter, fields, rootJoin); + pagination.getData().forEach(this::initializeForSerialization); + return pagination; + } + + @Override + @Transactional(readOnly = true) + public List findAll(String fields, List filter, String rootJoin) { + List contacts = super.findAll(fields, filter, rootJoin); + contacts.forEach(this::initializeForSerialization); + return contacts; + } + + @Override + @Transactional(readOnly = true) + public java.util.Optional findById(UUID id, String fields) { + java.util.Optional contact = super.findById(id, fields); + contact.ifPresent(this::initializeForSerialization); + return contact; + } + + @Override + @Transactional + public Contact save(Contact entity) { + Contact saved = super.save(entity); + initializeForSerialization(saved); + return saved; + } + + @Override + @Transactional + public Contact update(UUID id, Contact entity) { + Contact updated = super.update(id, entity); + initializeForSerialization(updated); + return updated; + } + @Override protected void validateDelete(Contact entity) { // Clear relationships before deletion to avoid foreign key constraints @@ -101,4 +148,31 @@ public java.util.Map delete(UUID id) { return java.util.Map.of("message", getEntitySingular() + " deleted successfully"); } + + private void initializeForSerialization(Contact contact) { + Hibernate.initialize(contact.getLists()); + Hibernate.initialize(contact.getTags()); + initializeUser(contact.getCreatedBy()); + initializeUser(contact.getUpdatedBy()); + + contact.getLists().forEach(this::initializeMetadataEntity); + contact.getTags().forEach(this::initializeMetadataEntity); + } + + private void initializeMetadataEntity(AbstractMetadataEntity entity) { + initializeUser(entity.getCreatedBy()); + initializeUser(entity.getUpdatedBy()); + } + + private void initializeUser(User user) { + if (user == null) { + return; + } + + Hibernate.initialize(user); + Hibernate.initialize(user.getRoles()); + for (Role role : user.getRoles()) { + Hibernate.initialize(role.getPrivileges()); + } + } } diff --git a/src/test/java/com/flexcodelabs/flextuma/core/config/JacksonConfigTest.java b/src/test/java/com/flexcodelabs/flextuma/core/config/JacksonConfigTest.java index eb5d17c..fe5fca3 100644 --- a/src/test/java/com/flexcodelabs/flextuma/core/config/JacksonConfigTest.java +++ b/src/test/java/com/flexcodelabs/flextuma/core/config/JacksonConfigTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import org.hibernate.LazyInitializationException; import org.junit.jupiter.api.Test; import java.time.LocalDateTime; @@ -68,4 +69,22 @@ void objectMapper_shouldSerializeEmptyBeanWithoutError() throws JsonProcessingEx String json = objectMapper.writeValueAsString(bean); assertEquals("{}", json); } + + static class LazyBean { + public String getName() { + return "test"; + } + + public Object getLazyRelation() { + throw new LazyInitializationException("no session"); + } + } + + @Test + void objectMapper_shouldSkipLazyFieldsThatCannotBeInitialized() throws JsonProcessingException { + String json = objectMapper.writeValueAsString(new LazyBean()); + + assertTrue(json.contains("\"name\":\"test\"")); + assertFalse(json.contains("lazyRelation")); + } } From 2baf53c87bf1f2389479e178372ae95539f4b6d0 Mon Sep 17 00:00:00 2001 From: Bennett Date: Sun, 29 Mar 2026 21:05:38 +0300 Subject: [PATCH 2/3] release: Add custom generic serializer --- ...LazyLoadingSafeBeanSerializerModifier.java | 62 ++++++++++++++++ .../contact/services/ContactService.java | 74 ------------------- 2 files changed, 62 insertions(+), 74 deletions(-) create mode 100644 src/main/java/com/flexcodelabs/flextuma/core/config/LazyLoadingSafeBeanSerializerModifier.java diff --git a/src/main/java/com/flexcodelabs/flextuma/core/config/LazyLoadingSafeBeanSerializerModifier.java b/src/main/java/com/flexcodelabs/flextuma/core/config/LazyLoadingSafeBeanSerializerModifier.java new file mode 100644 index 0000000..a25d055 --- /dev/null +++ b/src/main/java/com/flexcodelabs/flextuma/core/config/LazyLoadingSafeBeanSerializerModifier.java @@ -0,0 +1,62 @@ +package com.flexcodelabs.flextuma.core.config; + +import java.util.List; + +import org.hibernate.LazyInitializationException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; + +public class LazyLoadingSafeBeanSerializerModifier extends BeanSerializerModifier { + + @Override + public List changeProperties(SerializationConfig config, BeanDescription beanDesc, + List beanProperties) { + return beanProperties.stream() + .map(LazyLoadingSafePropertyWriter::new) + .map(BeanPropertyWriter.class::cast) + .toList(); + } + + private static final class LazyLoadingSafePropertyWriter extends BeanPropertyWriter { + + private LazyLoadingSafePropertyWriter(BeanPropertyWriter base) { + super(base); + } + + @Override + public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception { + try { + super.serializeAsField(bean, gen, prov); + } catch (Exception ex) { + if (!isLazyLoadingFailure(ex)) { + throw ex; + } + + if (!gen.canOmitFields()) { + super.serializeAsOmittedField(bean, gen, prov); + } + } + } + + private boolean isLazyLoadingFailure(Throwable throwable) { + Throwable current = throwable; + while (current != null) { + if (current instanceof LazyInitializationException) { + return true; + } + if (current instanceof JsonMappingException jsonMappingException) { + current = jsonMappingException.getCause(); + continue; + } + current = current.getCause(); + } + return false; + } + } +} diff --git a/src/main/java/com/flexcodelabs/flextuma/modules/contact/services/ContactService.java b/src/main/java/com/flexcodelabs/flextuma/modules/contact/services/ContactService.java index 9254835..d787355 100644 --- a/src/main/java/com/flexcodelabs/flextuma/modules/contact/services/ContactService.java +++ b/src/main/java/com/flexcodelabs/flextuma/modules/contact/services/ContactService.java @@ -1,20 +1,13 @@ package com.flexcodelabs.flextuma.modules.contact.services; -import java.util.List; import java.util.UUID; -import org.hibernate.Hibernate; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.flexcodelabs.flextuma.core.dtos.Pagination; -import com.flexcodelabs.flextuma.core.entities.auth.Role; -import com.flexcodelabs.flextuma.core.entities.auth.User; import com.flexcodelabs.flextuma.core.entities.contact.Contact; -import com.flexcodelabs.flextuma.core.entities.metadata.AbstractMetadataEntity; import com.flexcodelabs.flextuma.core.repositories.ContactRepository; import com.flexcodelabs.flextuma.core.services.BaseService; @@ -76,46 +69,6 @@ protected String getTableName() { return "contact"; } - @Override - @Transactional(readOnly = true) - public Pagination findAllPaginated(Pageable pageable, List filter, String fields, String rootJoin) { - Pagination pagination = super.findAllPaginated(pageable, filter, fields, rootJoin); - pagination.getData().forEach(this::initializeForSerialization); - return pagination; - } - - @Override - @Transactional(readOnly = true) - public List findAll(String fields, List filter, String rootJoin) { - List contacts = super.findAll(fields, filter, rootJoin); - contacts.forEach(this::initializeForSerialization); - return contacts; - } - - @Override - @Transactional(readOnly = true) - public java.util.Optional findById(UUID id, String fields) { - java.util.Optional contact = super.findById(id, fields); - contact.ifPresent(this::initializeForSerialization); - return contact; - } - - @Override - @Transactional - public Contact save(Contact entity) { - Contact saved = super.save(entity); - initializeForSerialization(saved); - return saved; - } - - @Override - @Transactional - public Contact update(UUID id, Contact entity) { - Contact updated = super.update(id, entity); - initializeForSerialization(updated); - return updated; - } - @Override protected void validateDelete(Contact entity) { // Clear relationships before deletion to avoid foreign key constraints @@ -148,31 +101,4 @@ public java.util.Map delete(UUID id) { return java.util.Map.of("message", getEntitySingular() + " deleted successfully"); } - - private void initializeForSerialization(Contact contact) { - Hibernate.initialize(contact.getLists()); - Hibernate.initialize(contact.getTags()); - initializeUser(contact.getCreatedBy()); - initializeUser(contact.getUpdatedBy()); - - contact.getLists().forEach(this::initializeMetadataEntity); - contact.getTags().forEach(this::initializeMetadataEntity); - } - - private void initializeMetadataEntity(AbstractMetadataEntity entity) { - initializeUser(entity.getCreatedBy()); - initializeUser(entity.getUpdatedBy()); - } - - private void initializeUser(User user) { - if (user == null) { - return; - } - - Hibernate.initialize(user); - Hibernate.initialize(user.getRoles()); - for (Role role : user.getRoles()) { - Hibernate.initialize(role.getPrivileges()); - } - } } From 231bcff1c50d4e4719fa0eb604f4a4039a1947a6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 29 Mar 2026 18:06:10 +0000 Subject: [PATCH 3/3] Release v0.0.30 [skip ci] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b533db0..0c6286a 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'com.flexcodelabs' -version = '0.0.29' +version = '0.0.30' description = 'Flextuma App' java {