From d6cd713741f03833367cbebd54ae8b24c242b9bc Mon Sep 17 00:00:00 2001 From: Mark Prins <1165786+mprins@users.noreply.github.com> Date: Mon, 19 Jan 2026 17:40:54 +0100 Subject: [PATCH 1/3] PMW-55: json sessie serialisatie aangepaste klassen gekopieerd + Jackson 3 code voor converters --- build/ci/docker-compose.yml | 6 +- pom.xml | 8 +- .../JdbcSessionConfiguration.java | 135 +++++++++++++++--- .../api/configuration/PopulateTestData.java | 6 + .../api/security/TailormapOidcUser.java | 18 ++- .../api/security/TailormapOidcUserMixin.java | 36 +++++ .../api/security/TailormapUserDetails.java | 5 +- .../security/TailormapUserDetailsImpl.java | 69 +++++++-- .../TailormapUserDetailsImplMixin.java | 44 ++++++ src/main/resources/application.properties | 6 +- 10 files changed, 284 insertions(+), 49 deletions(-) create mode 100644 src/main/java/org/tailormap/api/security/TailormapOidcUserMixin.java create mode 100644 src/main/java/org/tailormap/api/security/TailormapUserDetailsImplMixin.java diff --git a/build/ci/docker-compose.yml b/build/ci/docker-compose.yml index 52d02a36..cc1d5ca2 100644 --- a/build/ci/docker-compose.yml +++ b/build/ci/docker-compose.yml @@ -4,18 +4,19 @@ name: 'planmonitor-wonen-ci' volumes: - postgis-db: + planmonitor-wonen-data: services: db: image: postgis/postgis:18-3.6-alpine environment: + TZ: Europe/Amsterdam POSTGRES_USER: planmonitor-wonen POSTGRES_PASSWORD: planmonitor-wonen POSTGRES_DB: planmonitor-wonen volumes: - - postgis-db:/var/lib/postgresql/data + - planmonitor-wonen-data:/var/lib/postgresql/data - ./initdb:/docker-entrypoint-initdb.d ports: - "127.0.0.1:5432:5432" @@ -30,6 +31,7 @@ services: tailormap: image: ghcr.io/tailormap/tailormap:snapshot environment: + - TZ=Europe/Amsterdam - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/tailormap - SPRING_PROFILES_ACTIVE=populate-testdata - ADMIN_HASHED_PASSWORD diff --git a/pom.xml b/pom.xml index 4ff31eed..32353b72 100644 --- a/pom.xml +++ b/pom.xml @@ -194,6 +194,10 @@ SPDX-License-Identifier: MIT org.springframework.boot spring-boot-starter-aspectj + + org.springframework.boot + spring-boot-starter-json + org.springframework.boot spring-boot-starter-security @@ -245,10 +249,6 @@ SPDX-License-Identifier: MIT org.apache.commons commons-lang3 - - org.springframework.boot - spring-boot-starter-json - org.geotools gt-main diff --git a/src/main/java/nl/b3p/planmonitorwonen/api/configuration/JdbcSessionConfiguration.java b/src/main/java/nl/b3p/planmonitorwonen/api/configuration/JdbcSessionConfiguration.java index 4e0bf395..b7b6c5da 100644 --- a/src/main/java/nl/b3p/planmonitorwonen/api/configuration/JdbcSessionConfiguration.java +++ b/src/main/java/nl/b3p/planmonitorwonen/api/configuration/JdbcSessionConfiguration.java @@ -6,30 +6,46 @@ package nl.b3p.planmonitorwonen.api.configuration; -import java.io.InputStream; -import java.io.ObjectInputStream; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; import javax.sql.DataSource; +import org.jspecify.annotations.NonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.core.serializer.Deserializer; -import org.springframework.core.serializer.support.DeserializingConverter; -import org.springframework.core.serializer.support.SerializingConverter; import org.springframework.jdbc.core.simple.JdbcClient; import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.security.jackson.SecurityJacksonModules; +import org.springframework.session.config.SessionRepositoryCustomizer; +import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource; import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; import org.springframework.transaction.PlatformTransactionManager; +import tools.jackson.core.JacksonException; +import tools.jackson.core.StreamReadFeature; +import tools.jackson.databind.DefaultTyping; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator; @Configuration @EnableJdbcHttpSession @Profile("!test") -public class JdbcSessionConfiguration { +public class JdbcSessionConfiguration implements BeanClassLoaderAware { + private static final Logger logger = + LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + @Value("${spring.datasource.url}") private String dataSourceUrl; @@ -48,6 +64,27 @@ public class JdbcSessionConfiguration { @Value("${tailormap.datasource.password}") private String sessionDataSourcePassword; + private static final String CREATE_SESSION_ATTRIBUTE_QUERY = + """ +INSERT INTO %TABLE_NAME%_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) +VALUES (?, ?, convert_from(?, 'UTF8')::jsonb) +"""; + + private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = + """ +UPDATE %TABLE_NAME%_ATTRIBUTES +SET ATTRIBUTE_BYTES = encode(?, 'escape')::jsonb +WHERE SESSION_PRIMARY_ID = ? +AND ATTRIBUTE_NAME = ? +"""; + + private ClassLoader classLoader; + + @Override + public void setBeanClassLoader(@NonNull ClassLoader classLoader) { + this.classLoader = classLoader; + } + @Bean public DataSource dataSource() { DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); @@ -67,6 +104,14 @@ public JdbcClient tailormapJdbcClient(@Qualifier("tailormapDataSource") DataSour return JdbcClient.create(data); } + @Bean + SessionRepositoryCustomizer customizer() { + return (sessionRepository) -> { + sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY); + sessionRepository.setUpdateSessionAttributeQuery(UPDATE_SESSION_ATTRIBUTE_QUERY); + }; + } + @Bean(name = {"springSessionDataSource", "tailormapDataSource"}) @SpringSessionDataSource public DataSource sessionDataSource() { @@ -85,20 +130,72 @@ public PlatformTransactionManager springSessionTransactionOperations( @Bean("springSessionConversionService") public ConversionService springSessionConversionService() { - GenericConversionService converter = new GenericConversionService(); - converter.addConverter(Object.class, byte[].class, new SerializingConverter()); - converter.addConverter(byte[].class, Object.class, new DeserializingConverter(new CustomDeserializer())); - return converter; - } + BasicPolymorphicTypeValidator.Builder builder = BasicPolymorphicTypeValidator.builder() + .allowIfSubType("org.tailormap.api.security.") + .allowIfSubType("org.springframework.security.") + .allowIfSubType("java.util.") + .allowIfSubType(java.lang.Number.class) + .allowIfSubType("java.time.") + .allowIfBaseType(Object.class); + + JsonMapper mapper = JsonMapper.builder() + .configure( + StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION, + (logger.isDebugEnabled() || logger.isTraceEnabled())) + .configure(SerializationFeature.INDENT_OUTPUT, (logger.isDebugEnabled() || logger.isTraceEnabled())) + .addMixIn( + org.tailormap.api.security.TailormapUserDetailsImpl.class, + org.tailormap.api.security.TailormapUserDetailsImplMixin.class) + .addMixIn( + org.tailormap.api.security.TailormapOidcUser.class, + org.tailormap.api.security.TailormapOidcUserMixin.class) + .addModules(SecurityJacksonModules.getModules(this.classLoader, builder)) + .activateDefaultTyping(builder.build(), DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY) + .build(); - static class CustomDeserializer implements Deserializer { - @Override - public Object deserialize(InputStream inputStream) { - try (ObjectInputStream ois = new ObjectInputStream(inputStream)) { - return ois.readObject(); - } catch (Exception ignored) { - return null; + final GenericConversionService converter = new GenericConversionService(); + // Object -> byte[] (serialize to JSON bytes) + converter.addConverter(Object.class, byte[].class, source -> { + try { + logger.debug("Serializing Spring Session: {}", source); + return mapper.writerFor(Object.class).writeValueAsBytes(source); + } catch (JacksonException e) { + logger.error("Error serializing Spring Session object: {}", source, e); + throw new ConversionFailedException( + TypeDescriptor.forObject(source), TypeDescriptor.valueOf(byte[].class), source, e); } - } + }); + // byte[] -> Object (deserialize from JSON bytes) + converter.addConverter(byte[].class, Object.class, source -> { + try { + logger.debug( + "Deserializing Spring Session from bytes, length: {} ({})", + source.length, + new String(source, StandardCharsets.UTF_8)); + return mapper.readValue(source, Object.class); + } catch (JacksonException e) { + String preview; + try { + String content = new String(source, StandardCharsets.UTF_8); + int maxLength = 256; + if (logger.isDebugEnabled() || logger.isTraceEnabled()) { + preview = content; + } else { + preview = content.length() > maxLength ? content.substring(0, maxLength) + "..." : content; + } + } catch (Exception ex) { + preview = ""; + } + logger.error( + "Error deserializing Spring Session from bytes, length: {}, preview: {}", + source.length, + preview, + e); + throw new ConversionFailedException( + TypeDescriptor.valueOf(byte[].class), TypeDescriptor.valueOf(Object.class), source, e); + } + }); + + return converter; } } diff --git a/src/main/java/nl/b3p/planmonitorwonen/api/configuration/PopulateTestData.java b/src/main/java/nl/b3p/planmonitorwonen/api/configuration/PopulateTestData.java index 3f2eaa49..a6dc5b89 100644 --- a/src/main/java/nl/b3p/planmonitorwonen/api/configuration/PopulateTestData.java +++ b/src/main/java/nl/b3p/planmonitorwonen/api/configuration/PopulateTestData.java @@ -131,6 +131,12 @@ public void init() throws ParseException { private void populateGemeentes() throws IOException { String sql = ImportGemeentesApplication.getGemeentesSql(bestuurlijkeGebiedenWfs, gemeentesTypename); + // Intentionally clear the gemeente table before (re)importing test/demo data. + // This configuration is only active when the `planmonitor-wonen-api.populate-testdata` + // property is set to `true` and is not meant to be enabled in production environments. + // Ensure that this remains disabled in production and that schema constraints allow + // truncating and repopulating the gemeente table in this way. + this.jdbcClient.sql("delete from gemeente").update(); this.jdbcClient.sql(sql).update(); } } diff --git a/src/main/java/org/tailormap/api/security/TailormapOidcUser.java b/src/main/java/org/tailormap/api/security/TailormapOidcUser.java index 2d23a753..3f35e57d 100644 --- a/src/main/java/org/tailormap/api/security/TailormapOidcUser.java +++ b/src/main/java/org/tailormap/api/security/TailormapOidcUser.java @@ -7,9 +7,10 @@ package org.tailormap.api.security; import java.io.Serial; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; +import org.jspecify.annotations.NonNull; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.OidcUserInfo; @@ -19,7 +20,7 @@ public class TailormapOidcUser extends DefaultOidcUser implements TailormapUserD @Serial private static final long serialVersionUID = 1L; - private final Collection additionalGroupProperties; + private final Collection additionalGroupProperties = new ArrayList<>(); private final String oidcRegistrationName; @@ -32,17 +33,14 @@ public TailormapOidcUser( Collection additionalGroupProperties) { super(authorities, idToken, userInfo, nameAttributeKey); this.oidcRegistrationName = oidcRegistrationName; - this.additionalGroupProperties = Collections.unmodifiableCollection(additionalGroupProperties); - } - - @Override - public Collection getAdditionalProperties() { - return List.of(); + if (additionalGroupProperties != null) { + this.additionalGroupProperties.addAll(additionalGroupProperties); + } } @Override public Collection getAdditionalGroupProperties() { - return additionalGroupProperties; + return Collections.unmodifiableCollection(additionalGroupProperties); } @Override @@ -51,7 +49,7 @@ public String getPassword() { } @Override - public String getUsername() { + @NonNull public String getUsername() { return super.getName(); } diff --git a/src/main/java/org/tailormap/api/security/TailormapOidcUserMixin.java b/src/main/java/org/tailormap/api/security/TailormapOidcUserMixin.java new file mode 100644 index 00000000..15bad24a --- /dev/null +++ b/src/main/java/org/tailormap/api/security/TailormapOidcUserMixin.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 B3Partners B.V. + * + * SPDX-License-Identifier: MIT + */ +package org.tailormap.api.security; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import java.util.Collection; +import java.util.Map; +import org.springframework.security.core.GrantedAuthority; + +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.ANY, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class TailormapOidcUserMixin { + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public TailormapOidcUserMixin( + @SuppressWarnings("unused") @JsonProperty("claims") Map claims, + @SuppressWarnings("unused") @JsonProperty("authorities") Collection authorities, + @SuppressWarnings("unused") @JsonProperty("attributes") Map attributes, + @SuppressWarnings("unused") @JsonProperty("nameAttributeKey") String nameAttributeKey, + @SuppressWarnings("unused") @JsonProperty("oidcRegistrationName") String oidcRegistrationName, + @SuppressWarnings("unused") @JsonProperty("additionalGroupProperties") + Collection additionalGroupProperties) { + // mixin constructor only for Jackson; no implementation + } +} diff --git a/src/main/java/org/tailormap/api/security/TailormapUserDetails.java b/src/main/java/org/tailormap/api/security/TailormapUserDetails.java index 6b419df2..ce16fa28 100644 --- a/src/main/java/org/tailormap/api/security/TailormapUserDetails.java +++ b/src/main/java/org/tailormap/api/security/TailormapUserDetails.java @@ -8,12 +8,15 @@ import java.io.Serializable; import java.util.Collection; +import java.util.Collections; import java.util.stream.Stream; import org.springframework.security.core.userdetails.UserDetails; public interface TailormapUserDetails extends Serializable, UserDetails { - Collection getAdditionalProperties(); + default Collection getAdditionalProperties() { + return Collections.emptyList(); + } Collection getAdditionalGroupProperties(); diff --git a/src/main/java/org/tailormap/api/security/TailormapUserDetailsImpl.java b/src/main/java/org/tailormap/api/security/TailormapUserDetailsImpl.java index 0db39e66..0303601d 100644 --- a/src/main/java/org/tailormap/api/security/TailormapUserDetailsImpl.java +++ b/src/main/java/org/tailormap/api/security/TailormapUserDetailsImpl.java @@ -5,30 +5,77 @@ */ package org.tailormap.api.security; +import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serial; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import org.jspecify.annotations.NonNull; import org.springframework.security.core.GrantedAuthority; -class TailormapUserDetailsImpl implements TailormapUserDetails { +/** + * Internal {@link TailormapUserDetails} implementation. + * + *

Do not use this class directly; always depend on the {@link TailormapUserDetails} interface instead. This class is + * {@code public} only to support JSON/session deserialization (e.g. by Jackson / Spring Session). Changing its + * visibility back to package-private would break that deserialization. + */ +public class TailormapUserDetailsImpl implements TailormapUserDetails { @Serial private static final long serialVersionUID = 1L; - private Collection authorities; - private String username; - private String password; - private ZonedDateTime validUntil; - private boolean enabled; - private String organisation; + private final Collection authorities; + private final String username; + private final String password; + private final ZonedDateTime validUntil; + private final boolean enabled; + private final String organisation; private final Collection additionalProperties = new ArrayList<>(); private final Collection additionalGroupProperties = new ArrayList<>(); + /** + * Constructor for Jackson deserialization. + * + * @param authorities the authorities + * @param username the username + * @param password the password + * @param validUntil the valid until date + * @param enabled whether the user is enabled + * @param organisation the organisation + * @param additionalProperties the additional properties + * @param additionalGroupProperties the additional group properties + */ + @SuppressWarnings("unused") + TailormapUserDetailsImpl( + @JsonProperty("authorities") Collection authorities, + @JsonProperty("username") String username, + @JsonProperty("password") String password, + @JsonProperty("validUntil") ZonedDateTime validUntil, + @JsonProperty("enabled") boolean enabled, + @JsonProperty("organisation") String organisation, + @JsonProperty("additionalProperties") Collection additionalProperties, + @JsonProperty("additionalGroupProperties") + Collection additionalGroupProperties) { + this.authorities = authorities; + this.username = username; + this.password = password; + this.validUntil = validUntil; + this.enabled = enabled; + this.organisation = organisation; + if (additionalProperties != null) { + this.additionalProperties.addAll(additionalProperties); + } + if (additionalGroupProperties != null) { + this.additionalGroupProperties.addAll(additionalGroupProperties); + } + } + @Override - public Collection getAuthorities() { + @NonNull public Collection getAuthorities() { return authorities; } @@ -38,7 +85,7 @@ public String getPassword() { } @Override - public String getUsername() { + @NonNull public String getUsername() { return username; } @@ -59,11 +106,11 @@ public String getOrganisation() { @Override public Collection getAdditionalProperties() { - return additionalProperties; + return Collections.unmodifiableCollection(additionalProperties); } @Override public Collection getAdditionalGroupProperties() { - return additionalGroupProperties; + return Collections.unmodifiableCollection(additionalGroupProperties); } } diff --git a/src/main/java/org/tailormap/api/security/TailormapUserDetailsImplMixin.java b/src/main/java/org/tailormap/api/security/TailormapUserDetailsImplMixin.java new file mode 100644 index 00000000..cc3a186a --- /dev/null +++ b/src/main/java/org/tailormap/api/security/TailormapUserDetailsImplMixin.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 B3Partners B.V. + * + * SPDX-License-Identifier: MIT + */ +package org.tailormap.api.security; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import java.time.ZonedDateTime; +import java.util.Collection; +import org.springframework.security.core.GrantedAuthority; + +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.ANY, + getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class TailormapUserDetailsImplMixin { + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public TailormapUserDetailsImplMixin( + @SuppressWarnings("unused") @JsonProperty("authorities") Collection authorities, + @SuppressWarnings("unused") @JsonProperty("username") String username, + @SuppressWarnings("unused") @JsonProperty("password") String password, + @SuppressWarnings("unused") @JsonProperty("validUntil") ZonedDateTime validUntil, + @SuppressWarnings("unused") @JsonProperty("enabled") boolean enabled, + @SuppressWarnings("unused") @JsonProperty("organisation") String organisation, + @SuppressWarnings("unused") @JsonProperty("additionalProperties") + Collection additionalProperties, + @SuppressWarnings("unused") @JsonProperty("additionalGroupProperties") + Collection additionalGroupProperties) { + // mixin constructor only for Jackson; no implementation + } + + /** prevents serializing the password. */ + @JsonIgnore + public abstract String getPassword(); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4423f5ef..32d8d8af 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -36,7 +36,7 @@ spring.datasource.username=planmonitor-wonen spring.datasource.password=planmonitor-wonen # Actuator -management.endpoints.enabled-by-default=true +management.endpoints.access.default=read_only management.endpoints.web.base-path=/api/planmonitorwonen/actuator management.endpoints.web.exposure.include=info,health,prometheus,loggers,logfile,mappings @@ -63,5 +63,7 @@ logging.level.org.springframework.boot.autoconfigure=INFO logging.level.org.springframework.test.context=INFO logging.level.org.springframework.security.web.authentication=DEBUG -logging.level.nl.b3p.planmonitorwonen.api=DEBUG +logging.group.planmonitorwonen=nl.b3p.planmonitorwonen.api +logging.group.auth=org.tailormap.api.security,nl.b3p.planmonitorwonen.api.configuration.JdbcSessionConfiguration +logging.group.geotools=org.geotools From ced324bf81a661473606d7c7aebf89385118f5c6 Mon Sep 17 00:00:00 2001 From: Mark Prins <1165786+mprins@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:56:46 +0100 Subject: [PATCH 2/3] Disable Object to json serialisation for now; it is not used in teh application because sessions are read-only --- .../JdbcSessionConfiguration.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/nl/b3p/planmonitorwonen/api/configuration/JdbcSessionConfiguration.java b/src/main/java/nl/b3p/planmonitorwonen/api/configuration/JdbcSessionConfiguration.java index b7b6c5da..37dd75e0 100644 --- a/src/main/java/nl/b3p/planmonitorwonen/api/configuration/JdbcSessionConfiguration.java +++ b/src/main/java/nl/b3p/planmonitorwonen/api/configuration/JdbcSessionConfiguration.java @@ -155,16 +155,18 @@ public ConversionService springSessionConversionService() { final GenericConversionService converter = new GenericConversionService(); // Object -> byte[] (serialize to JSON bytes) - converter.addConverter(Object.class, byte[].class, source -> { - try { - logger.debug("Serializing Spring Session: {}", source); - return mapper.writerFor(Object.class).writeValueAsBytes(source); - } catch (JacksonException e) { - logger.error("Error serializing Spring Session object: {}", source, e); - throw new ConversionFailedException( - TypeDescriptor.forObject(source), TypeDescriptor.valueOf(byte[].class), source, e); - } - }); + // this is not actually done in the application because sessions are + // read-only, so this is commented out but left here for possible future use + // converter.addConverter(Object.class, byte[].class, source -> { + // try { + // logger.debug("Serializing Spring Session: {}", source); + // return mapper.writerFor(Object.class).writeValueAsBytes(source); + // } catch (JacksonException e) { + // logger.error("Error serializing Spring Session object: {}", source, e); + // throw new ConversionFailedException( + // TypeDescriptor.forObject(source), TypeDescriptor.valueOf(byte[].class), source, e); + // } + // }); // byte[] -> Object (deserialize from JSON bytes) converter.addConverter(byte[].class, Object.class, source -> { try { From 8ed8e65a512b4ab6069f7554e79e68834152c140 Mon Sep 17 00:00:00 2001 From: Mark Prins <1165786+mprins@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:57:46 +0100 Subject: [PATCH 3/3] PMW-60: update versienummer naar volgende minor --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 32353b72..7e706848 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ SPDX-License-Identifier: MIT nl.b3p.pmw planmonitor-wonen-api - 1.2.6-SNAPSHOT + 1.3.0-SNAPSHOT jar Planmonitor Wonen API Planmonitor Wonen API provides the backend for Planmonitor Wonen