diff --git a/spring-data-rest-webmvc/pom.xml b/spring-data-rest-webmvc/pom.xml
index da4c7f128..42ef27ddf 100644
--- a/spring-data-rest-webmvc/pom.xml
+++ b/spring-data-rest-webmvc/pom.xml
@@ -35,6 +35,13 @@
org.springframework
spring-webmvc
+
+
+ org.jmolecules.integrations
+ jmolecules-spring
+ ${jmolecules-integration}
+ true
+
javax.servlet
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/JMoleculesConfigurer.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/JMoleculesConfigurer.java
new file mode 100644
index 000000000..65efcc50a
--- /dev/null
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/JMoleculesConfigurer.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.rest.webmvc.config;
+
+import java.io.Serializable;
+import java.util.function.Supplier;
+
+import org.jmolecules.ddd.types.Entity;
+import org.jmolecules.ddd.types.Identifier;
+import org.jmolecules.spring.IdentifierToPrimitivesConverter;
+import org.jmolecules.spring.PrimitivesToIdentifierConverter;
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.support.ConfigurableConversionService;
+import org.springframework.data.mapping.PersistentEntity;
+import org.springframework.data.mapping.PersistentProperty;
+import org.springframework.data.mapping.context.PersistentEntities;
+import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
+import org.springframework.format.FormatterRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Configuration for JMolecules integration. Registers the following components:
+ *
+ * - {@link IdentifierToPrimitivesConverter} and {@link PrimitivesToIdentifierConverter} in case not already present
+ * in the Spring MVC {@link ConversionService}
+ * - the same converters on the {@link ConversionService} exposed to {@link RepositoryRestConfigurer}.
+ * - a {@link BackendIdConverter} using those converters
+ *
+ *
+ * @author Oliver Drotbohm
+ * @since 3.5
+ */
+@Configuration(proxyBeanMethods = false)
+class JMoleculesConfigurer implements WebMvcConfigurer, RepositoryRestConfigurer {
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#addFormatters(org.springframework.format.FormatterRegistry)
+ */
+ @Override
+ public void addFormatters(FormatterRegistry registry) {
+
+ ConversionService conversionService = (ConversionService) registry;
+ Supplier supplier = () -> conversionService;
+
+ if (!conversionService.canConvert(Identifier.class, String.class)) {
+ registry.addConverter(new IdentifierToPrimitivesConverter(supplier));
+ }
+
+ if (!conversionService.canConvert(String.class, Identifier.class)) {
+ registry.addConverter(new PrimitivesToIdentifierConverter(supplier));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer#configureConversionService(org.springframework.core.convert.support.ConfigurableConversionService)
+ */
+ @Override
+ public void configureConversionService(ConfigurableConversionService conversionService) {
+
+ Supplier supplier = () -> conversionService;
+
+ conversionService.addConverter(new PrimitivesToIdentifierConverter(supplier));
+ conversionService.addConverter(new IdentifierToPrimitivesConverter(supplier));
+ }
+
+ @Lazy
+ @Bean
+ BackendIdConverter jMoleculesEntitiesBackendIdConverter(PersistentEntities entities,
+ @Qualifier("mvcConversionService") ObjectFactory conversionService) {
+ return new JMoleculesBackendIdentifierConverter(entities, () -> conversionService.getObject());
+ }
+
+ /**
+ * A {@link BackendIdConverter} to convert from and to JMolecules' Identifier
+ *
+ * @author Oliver Drotbohm
+ */
+ private static final class JMoleculesBackendIdentifierConverter implements BackendIdConverter {
+
+ private static final TypeDescriptor STRING_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
+ private static final ResolvableType IDENTIFIER_TYPE = ResolvableType.forClass(Identifier.class);
+
+ private final PrimitivesToIdentifierConverter identifierConverter;
+ private final IdentifierToPrimitivesConverter primitivesConverter;
+ private final PersistentEntities entities;
+
+ /**
+ * Creates a new {@link JMoleculesBackendIdentifierConverter} for the given {@link PersistentEntities} and
+ * {@link ConversionService}.
+ *
+ * @param entities must not be {@literal null}.
+ * @param conversionService must not be {@literal null}.
+ */
+ public JMoleculesBackendIdentifierConverter(PersistentEntities entities,
+ Supplier extends ConversionService> conversionService) {
+
+ this.identifierConverter = new PrimitivesToIdentifierConverter(conversionService);
+ this.primitivesConverter = new IdentifierToPrimitivesConverter(conversionService);
+ this.entities = entities;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.webmvc.spi.BackendIdConverter#fromRequestId(java.lang.String, java.lang.Class)
+ */
+ @Override
+ public Serializable fromRequestId(String id, Class> entityType) {
+
+ PersistentEntity, ? extends PersistentProperty>> entity = entities.getRequiredPersistentEntity(entityType);
+ Class> idType = entity.getRequiredIdProperty().getType();
+
+ return (Serializable) identifierConverter.convert(id, TypeDescriptor.forObject(id),
+ TypeDescriptor.valueOf(idType));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.webmvc.spi.BackendIdConverter#toRequestId(java.io.Serializable, java.lang.Class)
+ */
+ @Override
+ public String toRequestId(Serializable id, Class> entityType) {
+ return (String) primitivesConverter.convert(id, TypeDescriptor.forObject(id), STRING_DESCRIPTOR);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
+ */
+ @Override
+ public boolean supports(Class> delimiter) {
+
+ if (!Entity.class.isAssignableFrom(delimiter)) {
+ return false;
+ }
+
+ return IDENTIFIER_TYPE.isAssignableFrom(ResolvableType.forClass(delimiter)
+ .as(Entity.class)
+ .getGeneric(1));
+ }
+ }
+}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RestControllerImportSelector.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RestControllerImportSelector.java
index 9f46e7976..801ce7812 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RestControllerImportSelector.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RestControllerImportSelector.java
@@ -34,6 +34,7 @@ import org.springframework.util.ClassUtils;
class RestControllerImportSelector implements ImportSelector {
private static final String HAL_EXPLORER_CONFIGURATION = "org.springframework.data.rest.webmvc.halexplorer.HalExplorerConfiguration";
+ private static final String JMOLECULES_SPRING = "org.jmolecules.spring.IdentifierToPrimitivesConverter";
/*
* (non-Javadoc)
@@ -45,10 +46,16 @@ class RestControllerImportSelector implements ImportSelector {
List configurations = new ArrayList<>();
configurations.add(RestControllerConfiguration.class.getName());
- if (ClassUtils.isPresent(HAL_EXPLORER_CONFIGURATION, importingClassMetadata.getClass().getClassLoader())) {
+ ClassLoader classLoader = importingClassMetadata.getClass().getClassLoader();
+
+ if (ClassUtils.isPresent(HAL_EXPLORER_CONFIGURATION, classLoader)) {
configurations.add(HAL_EXPLORER_CONFIGURATION);
}
+ if (ClassUtils.isPresent(JMOLECULES_SPRING, classLoader)) {
+ configurations.add(JMoleculesConfigurer.class.getName());
+ }
+
return configurations.toArray(new String[configurations.size()]);
}
}