Search code examples
javaspringorika

Orika - map Page<T> to Page<TDto>


I have a problem in mapping Page object to Page object. I have the following Mapper class:

package com.agileplayers.agilemanager.core.mapper;

import com.agileplayers.agilemanager.core.domain.Account;
import com.agileplayers.agilemanager.core.domain.Entry;
import com.agileplayers.agilemanager.core.dto.AccountDto;
import com.agileplayers.agilemanager.core.dto.EntryDto;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.TypeBuilder;
import org.springframework.data.domain.PageImpl;
import org.springframework.stereotype.Component;


@Component
public class Mapper {
    final static MapperFactory MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();

    public MapperFacade getMapperFacade(){
        registerMappers();
        return MAPPER_FACTORY.getMapperFacade();
    }

    private void registerMappers(){
        MAPPER_FACTORY.classMap(Entry.class, EntryDto.class)
                .byDefault()
                .register();

        MAPPER_FACTORY.classMap(Account.class, AccountDto.class)
                .byDefault()
                .register();
        MAPPER_FACTORY.classMap(
                new TypeBuilder<PageImpl<Entry>>() {}.build(),
                new TypeBuilder<PageImpl<EntryDto>>() {}.build())
                .byDefault()
                .register();

    }
}

which is used here:

@Autowired
    private EntryRepository entryRepository;

    private void start() {
        Mapper mapper = new Mapper();
        Page<Entry> entryPage = entryRepository.findAll(new PageRequest(0, 2));

        Type<Page<Entry>> pageEntryType = new TypeBuilder<Page<Entry>>() {
        }.build();
        Type<Page<EntryDto>> pageEntryDtoType = new TypeBuilder<Page<EntryDto>>() {
        }.build();

        Page<EntryDto> entryDtoPage = mapper.getMapperFacade().map(entryPage, pageEntryType, pageEntryDtoType);

unfortunately, I am receiving the following:

Exception in thread "main" ma.glasnost.orika.MappingException: While attempting the following mapping:
sourceClass = class org.springframework.data.domain.PageImpl
sourceType = org.springframework.data.domain.Page<Entry>
destinationType = org.springframework.data.domain.Page<EntryDto>
resolvedStrategy = InstantiateAndUseCustomMapperStrategy<PageImpl<Entry>, PageImpl<EntryDto>> {customMapper: GeneratedMapper<PageImpl<Entry>, PageImpl<EntryDto>> {usedConverters: [], usedMappers: [], usedMapperFacades: [DefaultBoundMapperFacade<Sort, Sort>], usedTypes: [Object] }, unenhancer: ma.glasnost.orika.unenhance.BaseUnenhancer@4b5aa48b, objectFactory: ma.glasnost.orika.generated.PageImpl_PageImpl_ObjectFactory59678627946495970211838179$3@2dd2ff87}
Error occurred: java.lang.UnsupportedOperationException
-----begin dump of current state-----------------------------
Registered object factories: 3 (approximate size: 9,541.1 kB)
  [AccountDto] : {Account=DefaultConstructorObjectFactory<AccountDto>}
  [PageImpl<EntryDto>] : {PageImpl<Entry>=ma.glasnost.orika.generated.PageImpl_PageImpl_ObjectFactory59678627946495970211838179$3@2dd2ff87}
  [EntryDto] : {Entry=DefaultConstructorObjectFactory<EntryDto>}
-------------------------------------------------------------
Registered mappers: 3 (approximate size: 9,794.2 kB)
  [0] : GeneratedMapper<PageImpl<Entry>, PageImpl<EntryDto>> {usedConverters: [], usedMappers: [], usedMapperFacades: [DefaultBoundMapperFacade<Sort, Sort>], usedTypes: [Object] }
  [1] : GeneratedMapper<Entry, EntryDto> {usedConverters: [builtin:CloneableConverter(Copy by cloning:Date, Calendar, XMLGregorianCalendar)], usedMappers: [], usedMapperFacades: [DefaultBoundMapperFacade<Account, AccountDto>], usedTypes: [Date] }
  [2] : GeneratedMapper<Account, AccountDto> {usedConverters: [builtin:CloneableConverter(Copy by cloning:Date, Calendar, XMLGregorianCalendar)], usedMappers: [], usedMapperFacades: [], usedTypes: [Date, Entry, EntryDto] }
-------------------------------------------------------------
Registered concrete types: 6 (approximate size: 346.9 kB)
  [interface java.util.Collection] : ArrayList<Object>
  [interface java.util.Map] : LinkedHashMap<Object, Object>
  [interface java.util.Map$Entry] : MapEntry<Object, Object>
  [Page<Entry>] : PageImpl<EntryDto>
  [interface java.util.List] : ArrayList<Object>
  [interface java.util.Set] : LinkedHashSet<Object>
-------------------------------------------------------------
Resolved strategies: 4 (approximate size: 9,794.6 kB)
{source: PageImpl, dest: Page<EntryDto>, in-place:false}: InstantiateAndUseCustomMapperStrategy<PageImpl<Entry>, PageImpl<EntryDto>> {customMapper: GeneratedMapper<PageImpl<Entry>, PageImpl<EntryDto>> {usedConverters: [], usedMappers: [], usedMapperFacades: [DefaultBoundMapperFacade<Sort, Sort>], usedTypes: [Object] }, unenhancer: ma.glasnost.orika.unenhance.BaseUnenhancer@4b5aa48b, objectFactory: ma.glasnost.orika.generated.PageImpl_PageImpl_ObjectFactory59678627946495970211838179$3@2dd2ff87}
{source: Account, dest: AccountDto, in-place:false}: InstantiateAndUseCustomMapperStrategy<Account, AccountDto> {customMapper: GeneratedMapper<Account, AccountDto> {usedConverters: [builtin:CloneableConverter(Copy by cloning:Date, Calendar, XMLGregorianCalendar)], usedMappers: [], usedMapperFacades: [], usedTypes: [Date, Entry, EntryDto] }, unenhancer: ma.glasnost.orika.unenhance.BaseUnenhancer@4b5aa48b, objectFactory: DefaultConstructorObjectFactory<AccountDto>}
{source: Entry, dest: Object, in-place:false}: InstantiateAndUseCustomMapperStrategy<Entry, EntryDto> {customMapper: GeneratedMapper<Entry, EntryDto> {usedConverters: [builtin:CloneableConverter(Copy by cloning:Date, Calendar, XMLGregorianCalendar)], usedMappers: [], usedMapperFacades: [DefaultBoundMapperFacade<Account, AccountDto>], usedTypes: [Date] }, unenhancer: ma.glasnost.orika.unenhance.BaseUnenhancer@4b5aa48b, objectFactory: DefaultConstructorObjectFactory<EntryDto>}
{source: Entry, dest: EntryDto, in-place:false}: InstantiateAndUseCustomMapperStrategy<Entry, EntryDto> {customMapper: GeneratedMapper<Entry, EntryDto> {usedConverters: [builtin:CloneableConverter(Copy by cloning:Date, Calendar, XMLGregorianCalendar)], usedMappers: [], usedMapperFacades: [DefaultBoundMapperFacade<Account, AccountDto>], usedTypes: [Date] }, unenhancer: ma.glasnost.orika.unenhance.BaseUnenhancer@4b5aa48b, objectFactory: DefaultConstructorObjectFactory<EntryDto>}
-------------------------------------------------------------
Unenhance strategy: ma.glasnost.orika.unenhance.BaseUnenhancer@4b5aa48b
-----end dump of current state-------------------------------
    at ma.glasnost.orika.impl.ExceptionUtility.newMappingException(ExceptionUtility.java:55)
    at ma.glasnost.orika.impl.MapperFacadeImpl.map(MapperFacadeImpl.java:280)
    at ma.glasnost.orika.impl.MapperFacadeImpl.map(MapperFacadeImpl.java:243)
    at ma.glasnost.orika.impl.MapperFacadeImpl.map(MapperFacadeImpl.java:133)
    at com.agileplayers.agilemanager.AgileManagerApplication.start(AgileManagerApplication.java:49)
    at com.agileplayers.agilemanager.AgileManagerApplication.main(AgileManagerApplication.java:34)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.UnsupportedOperationException
    at java.util.Collections$UnmodifiableCollection.clear(Collections.java:1074)
    at ma.glasnost.orika.generated.Orika_PageImpl_PageImpl_Mapper5970055563717$0.mapAtoB(Orika_PageImpl_PageImpl_Mapper5970055563717$0.java)
    at ma.glasnost.orika.impl.mapping.strategy.UseCustomMapperStrategy.map(UseCustomMapperStrategy.java:67)
    at ma.glasnost.orika.impl.MapperFacadeImpl.map(MapperFacadeImpl.java:266)
    ... 9 more

There is no problem in mapping Entry to EntryDto or List to List. I assume the problem might be with parameterless constructor in PageImpl class but it is only my suspicion. Please advise, what I am doing wrong.


Solution

  • If you are using Spring Data Commons 1.10 or newer the Page object has a map method. Just use that to your advantage.

    For the next sample I assume you are actually using that.

    private void start() {
        final Mapper mapper = new Mapper();
        Page<Entry> entryPage = entryRepository.findAll(new PageRequest(0, 2));
        Page<EntryDto> entryDtoPage = entryPage.map(
            new Converter<Entry, EntryDto>() {
                public EntryDto convert(Entry source) {
                    mapper.getMapperFacade().map(source, EntryDto);
                }
        });
    

    No need to provide any other code trying to map/hack/reconstruct the actual Page. When on JDK8 you can probably replace it with a nice lamda making the code even smaller.