Support for jMolecules Identifier types.

We now properly handle jMolecules Identifier instances by integration with the corresponding Converter implementations provided by the jmolecules-spring integrations library.

Fixes GH-1982.
This commit is contained in:
Oliver Drotbohm
2021-02-24 17:58:39 +01:00
parent e9bc98a1e9
commit 65d4d71895
3 changed files with 178 additions and 1 deletions

View File

@@ -35,6 +35,13 @@
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.jmolecules.integrations</groupId>
<artifactId>jmolecules-spring</artifactId>
<version>${jmolecules-integration}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>

View File

@@ -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:
* <ul>
* <li>{@link IdentifierToPrimitivesConverter} and {@link PrimitivesToIdentifierConverter} in case not already present
* in the Spring MVC {@link ConversionService}</li>
* <li>the same converters on the {@link ConversionService} exposed to {@link RepositoryRestConfigurer}.</li>
* <li>a {@link BackendIdConverter} using those converters</li>
* </ol>
*
* @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<ConversionService> 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<ConversionService> supplier = () -> conversionService;
conversionService.addConverter(new PrimitivesToIdentifierConverter(supplier));
conversionService.addConverter(new IdentifierToPrimitivesConverter(supplier));
}
@Lazy
@Bean
BackendIdConverter jMoleculesEntitiesBackendIdConverter(PersistentEntities entities,
@Qualifier("mvcConversionService") ObjectFactory<ConversionService> 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));
}
}
}

View File

@@ -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<String> 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()]);
}
}