Tried to create a configuration class which depends on another bean class using #DependsOn annotation and later found that #DependsOn only work along with #Bean and #Component and not with #Configuration.
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
private static final String URL_MAPPINGS = "/*";
private static final Class<?>[] classes = new Class[]{Application.class, Config1.class};
public static void main(String[] args) {
SpringApplication.run(classes, args);
}
...
}
#Configuration
public class MongoConfig {
#Value("${db.name}")
private String dbName;
#Value("${db.url}")
private String url;
#Override
public MongoClient mongo() {
ConnectionString connectionString = new ConnectionString("mongodb:"+url);
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongo(), dbName);
}
}
#Import({ MongoConfig.class, Config2.class})
#Configuration
public class Config1 {
public BeanA beanA(MongoTemplate mongoTemplate, Environment environment) {
return new BeanA(mongoTemplate);
}
}
#Configuration
#ComponentScan("com.test")
//this should be processed after BeanA is created, similar to #DependsOn
public class Config2 {
...
}
public class BeanA {
private MongoTemplate mongoTemplate;
public BeanA(MongoTemplate mongoTemplate, Environment environment) {
this.mongoTemplate = mongoTemplate;
this.applicationPropertySource = new ApplicationPropertySource(SOURCE_NAME);
MutablePropertySources mutablePropertySources = ((StandardEnvironment) environment).getPropertySources();
mutablePropertySources.addFirst(applicationPropertySource);
getExistingProperties().forEach((key, val) -> applicationPropertySource.set(key, val));
}
private Map<String, String> getExistingProperties() {
//assume values are obtained from DB
}
public static class ApplicationPropertySource extends PropertiesPropertySource {
public ApplicationPropertySource(String name) {
super(name, new HashMap());
}
public Object set(String key, String value) {
return getSource().put(key, value);
}
}
I like to achieve this to avoid adding #DependsOn annotation on all the beans in the project, is this even possible?
Spring version is v1.5.12.RELEASE
Related
I have a spring-boot application.
I have entity:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Document(COLLECTION_NAME)
public class PersonEntity {
public static final String COLLECTION_NAME = "person_info";
private static final String PERSON_NAME = "person_name";
#Id
private PersonId id;
#Field(name = PERSON_NAME)
private String personName;
#Indexed(name = "ttl_index", expireAfterSeconds=20)
private LocalDateTime date;
}
I have a repository interface:
public interface PersonRepository {
void saveWithTtl(PersonEntity entity);
}
The repository implementation:
#Slf4j
#Repository
public class PersonRepositoryImpl implements PersonRepository {
private final int expireAfterSeconds;
private final ReactiveMongoTemplate mongoTemplate;
public PersonRepositoryImpl(#Value("${ttl.index}") int expireAfterSeconds,
ReactiveMongoTemplate mongoTemplate) {
this.expireAfterSeconds = expireAfterSeconds;
this.mongoTemplate = mongoTemplate;
}
#Override
public void saveWithTtl(PersonEntity entity) {
mongoTemplate.indexOps(PersonEntity.class)
.ensureIndex(new Index().on(PersonEntity.CREATED_AT, ASC)
.expire(expireAfterSeconds)).subscribe(result -> log.info("Ttl index has been created: {}", result));
mongoTemplate.save(entity).subscribe(result -> log.info("Entity has been saved: {}", result));
}
}
And, finally, I have test that does not work:
#DataMongoTest
#Testcontainers
public class PersonRepositoryIT {
#Autowired
private ReactiveMongoTemplate mongoTemplate;
#Autowired
private PersonRepository repository;
#Container
private static MongoDbContainer mongoDbContainer = new MongoDbContainer();
#AfterEach
void cleanUp() {
repository.deleteAll();
}
#DynamicPropertySource
static void registerMongoProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", mongoDbContainer::getReplicaSetUrl);
}
#Test
public void shouldCreateAndDeleteRecordsAfterDelay_whenSaveWithTtl_givenDefinedTll() {
//given
PersonEntity givenEntity = PersonEntity.builder().createdAt(LocalDateTime.now())
.personName("Joe")
.id(PERSON_ID).build();
//when
repository.saveWithTtl(givenEntity);
//then
StepVerifier.create(mongoTemplate.estimatedCount(PersonEntity.COLLECTION_NAME))
.expectNext(1L)
.verifyComplete();
}
}
On expectNext it fails coz it returns 0 and not 1.
mongoTemplate.estimatedCount returns 0
When I test the repository from Postman (repo is calling inside service), it creates the document in MongoDB wil ttl index, as expected.
In test fonfig I have set the ${ttl.index} to 20.
What am I doing wrong?
I don't know if it is to late, but I had the same problem today.
I Found your question looking for an answer for my problem hahahaha.
This snipped worked for me:
#Container
public static MongoDBContainer container = new MongoDBContainer(DockerImageName.parse("mongo:6"));
#DynamicPropertySource
static void mongoDbProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", container::getReplicaSetUrl);
}
#Bean
public ReactiveMongoTemplate reactiveMongoTemplate() throws Exception {
container.start();
ConnectionString connectionString = new ConnectionString(container.getReplicaSetUrl());
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
MongoClient mongoClient = MongoClients.create(mongoClientSettings);
return new ReactiveMongoTemplate(mongoClient,"test");
}
Apparently ReactiveMongoTemplate is not being injected by default, then I created my own Bean an it worked
Note: I already look at and tried some approaches on SO e.g. How to test Spring's declarative caching support on Spring Data repositories?, but as most of them old, I cannot make them work properly and I need a solution with the latest library versions. So, I would be appreciated if you have a look at the question and help.
#Service
#EnableCaching
#RequiredArgsConstructor
public class DemoServiceImpl implements DemoService {
private static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
private final LabelTranslatableRepository translatableRepository;
private final LanguageService languageService;
#Override
public LabelDTO findByUuid(UUID uuid) {
final Label label = labelRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
final List<LabelTranslatable> translatableList = translatableRepository.findAllByEntityUuid(uuid);
return new LabelDTO(Pair.of(label.getUuid(), label.getKey()), translatableList);
}
}
I created the following Unit Test to test caching for the nethod above:
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
#ExtendWith(MockitoExtension.class)
class TextLabelServiceImpl_deneme_Test {
#Autowired
private CacheManager cacheManager;
#InjectMocks
private LabelService labelService;
#Mock
private LabelRepository labelRepository;
#Mock
private LabelTranslatableRepository translatableRepository;
#Test
void test_Cache() {
UUID uuid = UUID.randomUUID();
final TextLabel textLabel = new TextLabel();
textLabel.setId(1);
textLabel.setKey("key1");
TextLabelTranslatable textLabelTranslatable = new TextLabelTranslatable();
textLabelTranslatable.setEntityUuid(uuid);
textLabelTranslatable.setLanguage(SupportedLanguage.fr);
textLabelTranslatable.setValue("value1");
final List<TextLabelTranslatable> translatableList = new ArrayList<>();
translatableList.add(textLabelTranslatable);
when(labelRepository.findByUuid(uuid)).thenReturn(Optional.of(textLabel));
when(translatableRepository.findAllByEntityUuid(uuid)).thenReturn(translatableList);
TextLabelDTO result1 = labelService.findByUuid(uuid);
TextLabelDTO result2 = labelService.findByUuid(uuid);
assertEquals(result1, result2);
Mockito.verify(translatableRepository, Mockito.times(1)).findAllByEntityUuid(uuid);
}
I am not sure if there is a missing part in my test, but at the last line (Mockito.verify()), it returns 2 instead of 1 that means caching not works. But it is working properly and there is a problem in my test I think. How should I complete the unit test to check the caching properly?
You need to annotate the service class method with #Cacheable. Try to follow the code in this tutorial. The following test code works as expected
#Import({CacheConfig.class, DemoServiceImpl.class})
#ExtendWith(SpringExtension.class)
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
class DemoServiceImplTest {
#MockBean
private LabelRepository labelRepository;
#Autowired
private DemoServiceImpl demoService;
#Autowired
private CacheManager cacheManager;
#TestConfiguration
static class EmbeddedRedisConfiguration {
private final RedisServer redisServer;
public EmbeddedRedisConfiguration() {
this.redisServer = new RedisServer();
}
#PostConstruct
public void startRedis() {
redisServer.start();
}
#PreDestroy
public void stopRedis() {
this.redisServer.stop();
}
}
#Test
void givenRedisCaching_whenFindItemById_thenItemReturnedFromCache() {
UUID id = UUID.randomUUID();
Label aLabel = new Label(id, "label");
Mockito.when(labelRepository.findById(id)).thenReturn(Optional.of(aLabel));
Label labelCacheMiss = demoService.findByUuid(id);
Label labelCacheHit = demoService.findByUuid(id);
Mockito.verify(labelRepository, Mockito.times(1)).findById(id);
}
}
With this service class code:
#Service
#RequiredArgsConstructor
#EnableCaching
public class DemoServiceImpl {
public static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
#Cacheable(value = CACHE_NAME)
public Label findByUuid(UUID uuid) {
return labelRepository.findById(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
}
}
And this CacheConfig:
#Configuration
public class CacheConfig {
#Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration(DemoServiceImpl.CACHE_NAME,
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)));
}
#Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
}
}
This question already has answers here:
How to autowire #ConfigurationProperties into #Configuration?
(2 answers)
Closed 3 years ago.
Java 8 and Spring Boot 1.5.8 here. I have the following application.properties file:
logging:
config: 'logback.groovy'
myapp:
hystrixTimeoutMillis: 500
jwt:
expiry: 86400000
secret: 12345
machineId: 12345
spring:
cache:
type: none
Which maps to the following #ConfigurationProperties POJO:
#ConfigurationProperties(prefix = "myapp")
public class MyAppConfig {
private Jwt jwt;
private Long hystrixTimeoutMillis;
private String machineId;
public Jwt getJwt() {
return jwt;
}
public void setJwt(Jwt jwt) {
this.jwt = jwt;
}
public Long getHystrixTimeoutMillis() {
return hystrixTimeoutMillis;
}
public void setHystrixTimeoutMillis(Long hystrixTimeoutMillis) {
this.hystrixTimeoutMillis = hystrixTimeoutMillis;
}
public String getMachineId() {
return machineId;
}
public void setMachineId(String machineId) {
this.machineId = machineId;
}
public static class Jwt {
private Long expiry;
private String secret;
public Long getExpiry() {
return expiry;
}
public void setExpiry(Long expiry) {
this.expiry = expiry;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
}
}
And I have the following #Configuration (injector) class:
#Configuration
public class MyAppInjector implements ApplicationContextAware {
private Logger log = LoggerFactory.getLogger(this.getClass());
private ApplicationContext applicationContext;
#Autowired
private MyAppConfig myAppConfig;
#Bean
public AuthService authService(MyAppConfig myAppConfig) {
return new JwtAuthService(myAppConfig);
}
}
And the following JwtAuthService class:
public class JwtAuthService implements AuthService {
private static final String BEARER_TOKEN_NAME = "Bearer";
private Logger log = LoggerFactory.getLogger(this.getClass());
private MyAppConfig myAppConfig;
#Autowired
public JwtAuthService(MyAppConfig myAppConfig) {
this.myAppConfig = myAppConfig;
}
#Override
public boolean isValidAuthToken(String authToken) {
return true;
}
}
At startup I get the following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field myAppConfig in com.example.myapp.spring.MyAppInjector required a bean of type 'com.example.myapp.spring.MyAppConfig' that could not be found.
Action:
Consider defining a bean of type 'com.example.myapp.spring.MyAppConfig' in your configuration.
Why am I getting this error? Where am I injecting/configuring things incorrectly?
You are not declaring MyAppConfig as a bean anywhere in your example, #ConfigurationProperties doesn't make annotated class a bean. You can do it as part of MyAppInjector configuration:
#Configuration
public class MyAppInjector {
#Bean
public AuthService authService() {
return new JwtAuthService(myAppConfig());
}
#Bean
public MyAppConfig myAppConfig() {
return new MyAppConfig();
}
}
Class with #ConfigurationProperties should also be bean. You need to annotate it as #Component or manually register in #Configuration class with #Bean annotation (instead of trying to autowire it there)
I'm creating a Spring REST app using Spring Boot 2, Spring Data REST, Spring HATEOAS.
I created this controller:
#Api(tags = "City Entity")
#RepositoryRestController
#RequestMapping(path = "/api/v1")
#PreAuthorize("isAuthenticated()")
public class CityController {
#Autowired
private LocalValidatorFactoryBean validator;
#Autowired
private PagedBeanResourceAssembler<City> pagedBeanResourceAssembler;
#Autowired
private CityService cityService;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(validator);
}
#GetMapping(path = "/cities/search/autocomplete")
public ResponseEntity<?> autocomplete(#RequestParam(name = "city") String city, #RequestParam(name = "country", required = false) String country, Pageable pageable, Locale locale) {
return new ResponseEntity<>(pagedBeanResourceAssembler.toResource(cityService.autocomplete(city, country, pageable)), HttpStatus.OK);
}
}
The service method is:
#Transactional(readOnly = true)
public Page<City> autocomplete(String text, String country, Pageable pageable) {
//my logic
return elasticSearchManager.search(ElasticSearchUtil.getIndexName(City.class), null, City.class, filters, null, pageable);
}
As you can see City bean is not stored in the DB. In fact the bean is:
public class City implements Persistable<Long> {
private Long id;
#NotBlank
private String name;
private String district;
private String region;
private String zipCode;
#NotNull
#Size(min = 2, max = 2)
private String country;
}
and finally this is my PagedBeanResourceAssembler:
#Component
public class PagedBeanResourceAssembler<T> implements ResourceAssembler<Page<T>, PagedResources<T>> {
#Autowired
private EntityLinks entityLinks;
#Override
public PagedResources<T> toResource(Page<T> page) {
PagedResources<T> pagedResources = new PagedResources<T>(page.getContent(), asPageMetadata(page));
return pagedResources;
}
private PagedResources.PageMetadata asPageMetadata(Page<?> page) {
Assert.notNull(page, "Page must not be null!");
return new PagedResources.PageMetadata(page.getSize(), page.getNumber(), page.getTotalElements(), page.getTotalPages());
}
}
When I make a http call I see a WARNING message in the console:
08/02/2019 11:09:35,526 WARN http-nio-8082-exec-1 RepositoryRestMvcConfiguration$ResourceSupportHttpMessageConverter:205 - Failed to evaluate Jackson serialization for type [class org.springframework.hateoas.PagedResources]: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.<init>()
08/02/2019 11:09:35,527 WARN http-nio-8082-exec-1 MappingJackson2HttpMessageConverter:205 - Failed to evaluate Jackson serialization for type [class org.springframework.hateoas.PagedResources]: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.<init>()
Not using a PagedResources the error goes away. I don't understand where I'm doing something wrong. I got that HalResourcesSerializer has not a default constructor, but I don't use it directly and I don't understand either why with Entity persisted in the db a controller such this works fine.
How can I fix this problem continuing to use a PagedResource?
======== UPDATE ==========
I add my configuration to give a more detailed view:
CustomConfiguration:
#Configuration
#EnableRetry
#EnableTransactionManagement
#EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
public class CustomConfiguration {
public static CustomConfiguration INSTANCE;
#PostConstruct
public void init() {
INSTANCE = this;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public static SpringSecurityAuditorAware springSecurityAuditorAware() {
return new SpringSecurityAuditorAware();
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/i18n/messages");
// messageSource.setDefaultEncoding("UTF-8");
// set to true only for debugging
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
#Bean
public MessageSourceAccessor messageSourceAccessor() {
return new MessageSourceAccessor(messageSource());
}
/**
* Enable Spring bean validation https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation
*
* #return
*/
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource());
return factoryBean;
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
/**
* Utility class from Google to work with phone numbers {#link https://github.com/googlei18n/libphonenumber}
*
* #return
*/
#Bean
public PhoneNumberUtil phoneNumberUtil() {
return PhoneNumberUtil.getInstance();
}
/**
* To enable SpEL expressions
*
* #return
*/
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
/**
* Define the specific storage manager to use (disk, S3, etc)
*
* #return
*/
#Bean
public StorageManager storageManager() {
return new S3StorageManager();
}
/**
* GRACEFUL SHUTDOWN
*/
#Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
#Bean
public ConfigurableServletWebServerFactory webServerFactory(final GracefulShutdown gracefulShutdown) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(gracefulShutdown);
return factory;
}
}
GlobalRepositoryRestConfigurer:
#Configuration
public class GlobalRepositoryRestConfigurer implements RepositoryRestConfigurer {
private Logger log = LogManager.getLogger();
#Autowired(required = false)
private Jackson2ObjectMapperBuilder objectMapperBuilder;
#Autowired
private Validator validator;
#Value("${cors.mapping}")
private String corsMapping;
#Value("#{'${cors.allowed.headers}'.split(',')}")
private String[] corsAllowedHeaders;
#Value("#{'${cors.exposed.headers}'.split(',')}")
private String[] corsExposedHeaders;
#Value("#{'${cors.allowed.methods}'.split(',')}")
private String[] corsAllowedMethod;
#Value("#{'${cors.allowed.origins}'.split(',')}")
private String[] corsAllowedOrigins;
#Value("${cors.max.age}")
private int corsMaxAge;
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getCorsRegistry().addMapping(corsMapping).exposedHeaders(corsExposedHeaders).allowedOrigins(corsAllowedOrigins)
.allowedHeaders(corsAllowedHeaders).allowedMethods(corsAllowedMethod).maxAge(corsMaxAge);
}
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
}
/**
* ValidationException serialiazer
*
* #return
*/
#Bean
public ValidationExceptionSerializer validationExceptionSerializer() {
return new ValidationExceptionSerializer();
}
#Bean
public CustomValidationExceptionSerializer customValidationExceptionSerializer() {
return new CustomValidationExceptionSerializer();
}
#Bean
public ConstraintViolationExceptionSerializer constraintViolationExceptionSerializer() {
return new ConstraintViolationExceptionSerializer();
}
/**
* Customize Object Mapper
*/
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
if (this.objectMapperBuilder != null) {
/**
* Custom serializer for ConstraintViolationException
* (https://jira.spring.io/browse/DATAREST-593)
*/
try {
SimpleModule constraintExceptionModule = new SimpleModule();
constraintExceptionModule.addSerializer(ConstraintViolationException.class, constraintViolationExceptionSerializer());
constraintExceptionModule.addSerializer(ValidationException.class, validationExceptionSerializer());
constraintExceptionModule.addSerializer(cloud.optix.server.exceptions.ValidationException.class, customValidationExceptionSerializer());
objectMapper.registerModule(constraintExceptionModule);
this.objectMapperBuilder.configure(objectMapper);
} catch (Exception e) {
log.error("", e);
}
}
}
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
#Override
public void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) {
}
/**
* Adding converter to donwload files in{#link org.springframework.web.bind.annotation.RestController}
*
* #param messageConverters
*/
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
// super.configureHttpMessageConverters(messageConverters);
messageConverters.add(new ResourceHttpMessageConverter());
}
}
WebMvcConfiguration:
#Configuration
// Enable entity links for Spring HATEOAS
#EnableHypermediaSupport(type = {HypermediaType.HAL})
public class WebMvcConfiguration implements WebMvcConfigurer {
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private TenantRestClient tenantRestClient;
#Value("${cors.mapping}")
private String corsMapping;
#Value("#{'${cors.allowed.headers}'.split(',')}")
private String[] corsAllowedHeaders;
#Value("#{'${cors.exposed.headers}'.split(',')}")
private String[] corsExposedHeaders;
#Value("#{'${cors.allowed.methods}'.split(',')}")
private String[] corsAllowedMethod;
#Value("#{'${cors.allowed.origins}'.split(',')}")
private String[] corsAllowedOrigins;
#Value("${cors.max.age}")
private int corsMaxAge;
#Autowired
public WebMvcConfiguration() {
}
#Bean
public LocaleResolver localeResolver() {
return new SmartLocaleResolver();
}
public class SmartLocaleResolver extends CookieLocaleResolver {
#Override
public Locale resolveLocale(HttpServletRequest request) {
String acceptLanguage = request.getHeader("Accept-Language");
if (acceptLanguage == null || acceptLanguage.trim().isEmpty()) {
return super.determineDefaultLocale(request);
}
return request.getLocale();
}
}
/**
* Custom exception in WEB MVC
*
* #return
*/
#Bean
public CustomErrorAttributes myCustomErrorAttributes() {
return new CustomErrorAttributes();
}
/**
* Global CORS security configuration
*
* #param registry
*/
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(corsMapping).exposedHeaders(corsExposedHeaders).allowedOrigins(corsAllowedOrigins).allowedHeaders(corsAllowedHeaders)
.allowedMethods(corsAllowedMethod).maxAge(corsMaxAge);
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TenantInterceptor());
}
}
Try commenting out this line in your configuration:
this.objectMapperBuilder.configure(objectMapper);
RepositoryRestConfigurer configures the objectMapper for itself quite well I assume.
If you need it for automatically adding more modules from your classpath, then add/configure those modules manually.
You will get same error when you use RepresentationModelAssembler<Object, PersistentEntityResource> resourceAssembler as parameter to your controller method. When you use PersistentEntityResourceAssembler resourceAssembler, spring will create right instance for you.
I suggest you to try to examine class hierarchy of PagedBeanResourceAssembler class and find some more specific class/implementation.
MongoDB, Spring Data, findAll() method error:
No converter found capable of converting from type [java.lang.String]
to type [java.time.LocalDateTime]
public class EntityName {
#Id
private String id;
private Map<LocalDateTime, Integer> statistic;
}
I am able to save entity, but not able to load it. Any quick fixes?
This solved the problem:
#Configuration
public class MongoConfiguration extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.database:test}")
private String database;
#Value("${spring.data.mongodb.host:localhost}:${spring.data.mongodb.port:27017}")
private String host;
#Autowired
private MappingMongoConverter mongoConverter;
// Converts . into a mongo friendly char
#PostConstruct
public void setUpMongoEscapeCharacterConversion() {
mongoConverter.setMapKeyDotReplacement("_");
}
#Override
protected String getDatabaseName() {
return database;
}
#Override
public Mongo mongo() throws Exception {
return new MongoClient(host);
}
#Bean
#Override
public CustomConversions customConversions() {
List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>();
converterList.add(new MongoLocalDateTimeFromStringConverter());
return new CustomConversions(converterList);
}
private static final class MongoLocalDateTimeFromStringConverter implements Converter<String, LocalDateTime> {
#Override
public LocalDateTime convert(String source) {
return source == null ? null : LocalDateTime.parse(source);
}
}
}
#Maksym's way has helped me as well. I had to adjust it a little bit for Spring Framework 5 and Spring Boot 2:
#Bean
#Primary
public MappingMongoConverter mongoConverter(
#Autowired MongoMappingContext mongoMappingContext,
#Autowired MongoDbFactory mainMongoFactory,
#Autowired MongoCustomConversions conversions
) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mainMongoFactory);
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
mongoConverter.setMapKeyDotReplacement("#");
mongoConverter.afterPropertiesSet();
mongoConverter.setCustomConversions(conversions);
return mongoConverter;
}
#Bean
public MongoMappingContext mongoMappingContext() {
MongoMappingContext context = new MongoMappingContext();
context.setSimpleTypeHolder(new SimpleTypeHolder(new HashSet<>(Arrays.asList(
DateTime.class,
LocalDateTime.class
)), MongoSimpleTypes.HOLDER));
return context;
}
#Bean
public MongoCustomConversions customConversions() {
List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>();
converterList.add(new MongoLocalDateTimeFromStringConverter());
converterList.add(new MongoDateTimeFromStringConverter());
return new MongoCustomConversions(converterList);
}
private static final class MongoLocalDateTimeFromStringConverter implements Converter<String, LocalDateTime> {
#Override
public LocalDateTime convert(String source) {
return source == null ? null : LocalDateTime.parse(source);
}
}
private static final class MongoDateTimeFromStringConverter implements Converter<String, DateTime> {
#Override
public DateTime convert(String source) {
return source == null ? null : DateTime.parse(source);
}
}
Ref - https://github.com/lordofthejars/nosql-unit#dataset-format
If you want to use ISODate function or any other javascript function
you should see how MongoDB Java Driver deals with it. For example in
case of ISODate:
In your json file use $date for conversion
"bornAt":{ "$date" : "2011-01-05T10:09:15.210Z"}