Rando Shtishi
Rando Shtishi

Reputation: 1512

Mongo Date Custom Converter not being called when save method of mongo repository is invoked

I am working with linux Debian 9. I have installed JDK 1.8. I am using maven version 3.6 and the version of springboot is 2.1. The mongodb version is 3.6.

Below is the model of class in java which i am trying to save in mongodb:

@org.springframework.data.mongodb.core.mapping.Document(collection = FileContentIndexQueue.ENTITY_COLLECTION_NAME)
@CompoundIndexes({
    @CompoundIndex(name = "state_timestamp", def = "{'state' : 1, 'timestamp': -1}")
})
@QuerySupertype
public class FileContentIndexQueue extends AbstractEntityNoLock {
        ...

    private ZonedDateTime timestamp;


    public FileContentIndexQueue() {
    }

    public FileContentIndexQueue(String fileId, String parentId, String childType, String indexName) {
        super();
        this.fileId = fileId;
        this.parentId = parentId;
        this.childType = childType;
        this.indexName = indexName;
        this.state = IndexingState.TODO;
        this.timestamp = ZonedDateTime.now();
    }

        ...

    public ZonedDateTime getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(ZonedDateTime timestamp) {
        this.timestamp = timestamp;
    }


}

I am using spring data mongodb and below is the repository class and the custom repository class with its implementations:

//Repository 
public interface FileContentIndexQueueRepositoryMongoElastic extends 
                MongoElasticRepository<FileContentIndexQueue, String>
                , FileContentIndexQueueRepositoryCustom 
{
}


//Custom Repository
public interface FileContentIndexQueueRepositoryCustom {
    void addFileWithContentToExtract(AbstractEntityNoLock entity, IFile file) throws CommonAllegatiException;
    void removeFileWithContentToExtract(AbstractEntityNoLock entity, IFile file) throws CommonAllegatiException;    
}

//Custom Repository Class Implementation
public class FileContentIndexQueueRepositoryCustomImpl implements FileContentIndexQueueRepositoryCustom {


    @Autowired
    @Lazy
    private FileContentIndexQueueRepositoryMongoElastic fileContentIndexQueueRepositoryMongoElastic;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public void addFileWithContentToExtract(AbstractEntityNoLock entity, IFile file) throws CommonAllegatiException {
        if(entity.getId() == null) {
            throw new CommonAllegatiException("Impossibile creare il FileContentIndexQueue da una entity senza id quindi non ancora salvata");
        }
        if(file == null) {
            throw new CommonAllegatiException("Impossibile creare il FileContentIndexQueue da un IFile null");
        }
        if(file.getFileId() == null) {
            throw new CommonAllegatiException("Impossibile creare il FileContentIndexQueue da un IFile senza id");
        }
        //da ricavare dalla entity
        String parentId = entity.getId();
        String indexName = elasticsearchTemplate.getPersistentEntityFor(entity.getClass()).getIndexName();
        String fileId = file.getFileId();

        FileContentIndexQueue fciq = new FileContentIndexQueue(fileId, parentId, CHILDTYPE, indexName);
        fileContentIndexQueueRepositoryMongoElastic.save(fciq); 
          //**after the save is the point where the error is generated**
    }

    @Override
    public void removeFileWithContentToExtract(AbstractEntityNoLock entity, IFile file) throws CommonAllegatiException {
              ...
    }   

}


All the classes above are part of library which i have downloaded with maven from the corporate repository and i cannot modify. The problem is that the FileContentIndexQueue.java model has an attribute timestamp which is type ZonedDateTime and mongo db does not support this type and spring data does not have built in converter and throws the error: org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class java.time.ZonedDateTime.

Also below it is the application properties file with properties that i have set for mongo db and elastic search:

 #MongoDB
spring.data.mongodb.uri=mongodb://localhost:27017/?safe=true&w=1
spring.data.mongodb.database=operaTestDb

 #Elasticsearch
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=localhost:9300
spring.data.mongoelastic.save-on-elastic=true

I have tried to created customer converters and register the converter to be called when a save method of mongo repository is invoked. Below it is the code of solution that i have implemented.

@Configuration
public class ConverterConfig  {

    @Autowired
    MongoDbFactory mongoDbFactory;


    @Bean
    public MongoTemplate mongoTemplate() throws UnknownHostException {
        MappingMongoConverter converter = new MappingMongoConverter(
                new DefaultDbRefResolver(mongoDbFactory), new MongoMappingContext());
        converter.setCustomConversions(customConversions());
        converter.afterPropertiesSet();
        return new MongoTemplate(mongoDbFactory, converter);
    }


    @Bean
    public MongoCustomConversions customConversions() {
        List<Converter<?, ?>> converters = new ArrayList<>();
        converters.add(DateToZonedDateTimeConverter.INSTANCE);
        converters.add(ZonedDateTimeToDateConverter.INSTANCE);
        return new MongoCustomConversions(converters);
    }

    enum DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {

        INSTANCE;

        @Override
        public ZonedDateTime convert(Date source) {
            return ZonedDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault());
        }
    }

    enum ZonedDateTimeToDateConverter implements Converter<ZonedDateTime, Date> {

        INSTANCE;

        @Override
        public Date convert(ZonedDateTime source) {
            return Date.from(source.toInstant());
        }
    }
}


The problem is even when i create the converters and register them , the error : org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class java.time.ZonedDateTime. still persists. I put a debug in converters but it doesn't reach there. It is like the converters are not registered at all. I would appreciate any suggestion on what should i do to debug or if you have another solution for this problem without using converters. Modifying the model attribute from ZonedDatetime to another date format is not option since i don't have access to that library.

Kind Regards, Rando.

Upvotes: 5

Views: 7829

Answers (2)

iamcrypticcoder
iamcrypticcoder

Reputation: 2889

It took me one hour to figure out in the latest version of spring data mongo, org.bson.Document should be used instead of com.mongodb.BasicDBObject. Here is an example:

@Component
@WritingConverter
public class UserModelConverter implements Converter<UserModel, Document> {

    @Override
    public Document convert(UserModel s) {
        Document obj = new Document();
        obj.put("firstName", "FirstName");
        obj.put("lastName", "LastName");

        obj.remove("_class");

        return obj;
    }
}

Upvotes: 2

Rando Shtishi
Rando Shtishi

Reputation: 1512

This problem was solved by doing the following modification in ConverterConfig class:

  1. removing the bean annotation from the method customConversions()
  2. removing the override annotation from convert methods
  3. Add @ReadingConverter annotation in DateToZonedDateTimeConverter enum
  4. Add @WritingConverter annotation in ZonedDateTimeToDateConverterenum

Below is the version of ConverterConfig class that worked for me. I hope it helps you not to lose time like I did.

@Configuration
public class ConverterConfig {
    @Autowired
    MongoDbFactory mongoDbFactory;
    @Bean
    public MongoTemplate mongoTemplate() throws UnknownHostException {
        MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory),
                new MongoMappingContext());
        converter.setCustomConversions(customConversions());
        converter.afterPropertiesSet();
        return new MongoTemplate(mongoDbFactory, converter);
    }
    public MongoCustomConversions customConversions() {
        List<Converter<?, ?>> converters = new ArrayList<>();
        converters.add(DateToZonedDateTimeConverter.INSTANCE);
        converters.add(ZonedDateTimeToDateConverter.INSTANCE);
        return new MongoCustomConversions(converters);
    }
    @ReadingConverter
    enum DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {
        INSTANCE;
        public ZonedDateTime convert(Date source) {
            return source == null ? null : ZonedDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault());
        }
    }
    @WritingConverter
    enum ZonedDateTimeToDateConverter implements Converter<ZonedDateTime, LocalDateTime> {
        INSTANCE;
        public LocalDateTime convert(ZonedDateTime source) {
            return source == null ? null : LocalDateTime.ofInstant(source.toInstant(), ZoneId.systemDefault());
        }
    }
}

Upvotes: 4

Related Questions