@Bean
  public Mapper mapper() {
    EditorMapper mapper = new EditorMapper();
    // http://stackoverflow.com/a/32309223/1203690
    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

    ConverterFactory converterFactory = mapperFactory.getConverterFactory();

    converterFactory.registerConverter(new PassThroughConverter(LocalDate.class));

    converterFactory.registerConverter(new PassThroughConverter(LocalDateTime.class));
    mapper.setMapperFacade(mapperFactory.getMapperFacade());

    return mapper;
  }
 public void registerSourceConverters(
     MapperFactory mapperFactory, ClassMapBuilder classMapBuilder) {
   ConverterFactory converterFactory = mapperFactory.getConverterFactory();
   converterFactory.registerConverter(
       "sourceOrcidConverter", new SourceOrcidConverter(orcidUrlManager));
   converterFactory.registerConverter(
       "sourceClientIdConverter", new SourceClientIdConverter(orcidUrlManager));
   converterFactory.registerConverter(
       "sourceNameConverter", new SourceNameConverter(sourceNameCacheManager));
   classMapBuilder
       .fieldMap("source.sourceOrcid", "sourceId")
       .converter("sourceOrcidConverter")
       .add();
   classMapBuilder
       .fieldMap("source.sourceClientId", "clientSourceId")
       .converter("sourceClientIdConverter")
       .add();
   classMapBuilder
       .fieldMap("source.sourceName", "elementSourceId")
       .converter("sourceNameConverter")
       .add();
 }
  public MapperFacade getPeerReviewMapperFacade() {
    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

    ConverterFactory converterFactory = mapperFactory.getConverterFactory();
    converterFactory.registerConverter(
        "workExternalIdentifiersConverterId", new WorkExternalIDsConverter());
    converterFactory.registerConverter(
        "workExternalIdentifierConverterId", new PeerReviewWorkExternalIDConverter());
    // do same as work

    ClassMapBuilder<PeerReview, PeerReviewEntity> classMap =
        mapperFactory.classMap(PeerReview.class, PeerReviewEntity.class);
    addV2CommonFields(classMap);
    registerSourceConverters(mapperFactory, classMap);
    classMap.field("url.value", "url");
    classMap.field("organization.name", "org.name");
    classMap.field("organization.address.city", "org.city");
    classMap.field("organization.address.region", "org.region");
    classMap.field("organization.address.country", "org.country");
    classMap.field(
        "organization.disambiguatedOrganization.disambiguatedOrganizationIdentifier",
        "org.orgDisambiguated.sourceId");
    classMap.field(
        "organization.disambiguatedOrganization.disambiguationSource",
        "org.orgDisambiguated.sourceType");
    classMap.field("groupId", "groupId");
    classMap.field("subjectType", "subjectType");
    classMap.field("subjectUrl.value", "subjectUrl");
    classMap.field("subjectName.title.content", "subjectName");
    classMap.field("subjectName.translatedTitle.content", "subjectTranslatedName");
    classMap.field("subjectName.translatedTitle.languageCode", "subjectTranslatedNameLanguageCode");
    classMap.field("subjectContainerName.content", "subjectContainerName");
    classMap
        .fieldMap("externalIdentifiers", "externalIdentifiersJson")
        .converter("workExternalIdentifiersConverterId")
        .add();
    classMap
        .fieldMap("subjectExternalIdentifier", "subjectExternalIdentifiersJson")
        .converter("workExternalIdentifierConverterId")
        .add();

    classMap.register();

    ClassMapBuilder<PeerReviewSummary, PeerReviewEntity> peerReviewSummaryClassMap =
        mapperFactory.classMap(PeerReviewSummary.class, PeerReviewEntity.class);
    addV2CommonFields(peerReviewSummaryClassMap);
    registerSourceConverters(mapperFactory, peerReviewSummaryClassMap);
    peerReviewSummaryClassMap
        .fieldMap("externalIdentifiers", "externalIdentifiersJson")
        .converter("workExternalIdentifiersConverterId")
        .add();
    peerReviewSummaryClassMap.field("organization.name", "org.name");
    peerReviewSummaryClassMap.field("organization.address.city", "org.city");
    peerReviewSummaryClassMap.field("organization.address.region", "org.region");
    peerReviewSummaryClassMap.field("organization.address.country", "org.country");
    peerReviewSummaryClassMap.field(
        "organization.disambiguatedOrganization.disambiguatedOrganizationIdentifier",
        "org.orgDisambiguated.sourceId");
    peerReviewSummaryClassMap.field(
        "organization.disambiguatedOrganization.disambiguationSource",
        "org.orgDisambiguated.sourceType");
    peerReviewSummaryClassMap.register();

    mapperFactory
        .classMap(FuzzyDate.class, CompletionDateEntity.class)
        .field("year.value", "year")
        .field("month.value", "month")
        .field("day.value", "day")
        .register();

    return mapperFactory.getMapperFacade();
  }
  public MapperFacade getFundingMapperFacade() {
    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
    ConverterFactory converterFactory = mapperFactory.getConverterFactory();
    converterFactory.registerConverter(
        "fundingExternalIdentifiersConverterId", new FundingExternalIDsConverter());
    converterFactory.registerConverter(
        "fundingContributorsConverterId", new JsonOrikaConverter<FundingContributors>());

    ClassMapBuilder<Funding, ProfileFundingEntity> fundingClassMap =
        mapperFactory.classMap(Funding.class, ProfileFundingEntity.class);
    addV2CommonFields(fundingClassMap);
    registerSourceConverters(mapperFactory, fundingClassMap);
    fundingClassMap.field("type", "type");
    fundingClassMap.field("organizationDefinedType.content", "organizationDefinedType");
    fundingClassMap.field("title.title.content", "title");
    fundingClassMap.field("title.translatedTitle.content", "translatedTitle");
    fundingClassMap.field("title.translatedTitle.languageCode", "translatedTitleLanguageCode");
    fundingClassMap.field("description", "description");
    fundingClassMap.field("amount.content", "numericAmount");
    fundingClassMap.field("amount.currencyCode", "currencyCode");
    fundingClassMap.field("url.value", "url");
    fundingClassMap.fieldBToA("org.name", "organization.name");
    fundingClassMap.fieldBToA("org.city", "organization.address.city");
    fundingClassMap.fieldBToA("org.region", "organization.address.region");
    fundingClassMap.fieldBToA("org.country", "organization.address.country");
    fundingClassMap.fieldBToA(
        "org.orgDisambiguated.sourceId",
        "organization.disambiguatedOrganization.disambiguatedOrganizationIdentifier");
    fundingClassMap.fieldBToA(
        "org.orgDisambiguated.sourceType",
        "organization.disambiguatedOrganization.disambiguationSource");
    fundingClassMap
        .fieldMap("externalIdentifiers", "externalIdentifiersJson")
        .converter("fundingExternalIdentifiersConverterId")
        .add();
    fundingClassMap
        .fieldMap("contributors", "contributorsJson")
        .converter("fundingContributorsConverterId")
        .add();
    fundingClassMap.register();

    ClassMapBuilder<FundingSummary, ProfileFundingEntity> fundingSummaryClassMap =
        mapperFactory.classMap(FundingSummary.class, ProfileFundingEntity.class);
    addV2CommonFields(fundingSummaryClassMap);
    registerSourceConverters(mapperFactory, fundingSummaryClassMap);
    fundingSummaryClassMap.field("type", "type");
    fundingSummaryClassMap.field("title.title.content", "title");
    fundingSummaryClassMap.field("title.translatedTitle.content", "translatedTitle");
    fundingSummaryClassMap.field(
        "title.translatedTitle.languageCode", "translatedTitleLanguageCode");
    fundingSummaryClassMap
        .fieldMap("externalIdentifiers", "externalIdentifiersJson")
        .converter("fundingExternalIdentifiersConverterId")
        .add();

    fundingSummaryClassMap.fieldBToA("org.name", "organization.name");
    fundingSummaryClassMap.fieldBToA("org.city", "organization.address.city");
    fundingSummaryClassMap.fieldBToA("org.region", "organization.address.region");
    fundingSummaryClassMap.fieldBToA("org.country", "organization.address.country");
    fundingSummaryClassMap.fieldBToA(
        "org.orgDisambiguated.sourceId",
        "organization.disambiguatedOrganization.disambiguatedOrganizationIdentifier");
    fundingSummaryClassMap.fieldBToA(
        "org.orgDisambiguated.sourceType",
        "organization.disambiguatedOrganization.disambiguationSource");

    fundingSummaryClassMap.register();

    mapperFactory
        .classMap(FuzzyDate.class, StartDateEntity.class)
        .field("year.value", "year")
        .field("month.value", "month")
        .field("day.value", "day")
        .register();
    mapperFactory
        .classMap(FuzzyDate.class, EndDateEntity.class)
        .field("year.value", "year")
        .field("month.value", "month")
        .field("day.value", "day")
        .register();
    return mapperFactory.getMapperFacade();
  }
  public MapperFacade getWorkMapperFacade() {
    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

    ConverterFactory converterFactory = mapperFactory.getConverterFactory();
    converterFactory.registerConverter(
        "workExternalIdentifiersConverterId", new WorkExternalIDsConverter());
    converterFactory.registerConverter(
        "workContributorsConverterId", new JsonOrikaConverter<WorkContributors>());

    ClassMapBuilder<Work, WorkEntity> workClassMap =
        mapperFactory.classMap(Work.class, WorkEntity.class);
    workClassMap.byDefault();
    workClassMap.field("putCode", "id");
    addV2DateFields(workClassMap);
    registerSourceConverters(mapperFactory, workClassMap);
    workClassMap.field("journalTitle.content", "journalTitle");
    workClassMap.field("workTitle.title.content", "title");
    workClassMap.field("workTitle.translatedTitle.content", "translatedTitle");
    workClassMap.field("workTitle.translatedTitle.languageCode", "translatedTitleLanguageCode");
    workClassMap.field("workTitle.subtitle.content", "subtitle");
    workClassMap.field("shortDescription", "description");
    workClassMap.field("workCitation.workCitationType", "citationType");
    workClassMap.field("workCitation.citation", "citation");
    workClassMap.field("workType", "workType");
    workClassMap.field("publicationDate", "publicationDate");
    workClassMap
        .fieldMap("workExternalIdentifiers", "externalIdentifiersJson")
        .converter("workExternalIdentifiersConverterId")
        .add();
    workClassMap.field("url.value", "workUrl");
    workClassMap
        .fieldMap("workContributors", "contributorsJson")
        .converter("workContributorsConverterId")
        .add();
    workClassMap.field("languageCode", "languageCode");
    workClassMap.field("country.value", "iso2Country");
    workClassMap.register();

    ClassMapBuilder<WorkSummary, WorkEntity> workSummaryClassMap =
        mapperFactory.classMap(WorkSummary.class, WorkEntity.class);
    registerSourceConverters(mapperFactory, workSummaryClassMap);
    workSummaryClassMap.field("putCode", "id");
    workSummaryClassMap.field("title.title.content", "title");
    workSummaryClassMap.field("title.translatedTitle.content", "translatedTitle");
    workSummaryClassMap.field("title.translatedTitle.languageCode", "translatedTitleLanguageCode");
    workSummaryClassMap.field("type", "workType");
    workSummaryClassMap.field("publicationDate", "publicationDate");
    workSummaryClassMap
        .fieldMap("externalIdentifiers", "externalIdentifiersJson")
        .converter("workExternalIdentifiersConverterId")
        .add();
    workSummaryClassMap.byDefault();
    workSummaryClassMap.register();

    ClassMapBuilder<WorkSummary, MinimizedWorkEntity> workSummaryMinimizedClassMap =
        mapperFactory.classMap(WorkSummary.class, MinimizedWorkEntity.class);
    addV2CommonFields(workSummaryMinimizedClassMap);
    registerSourceConverters(mapperFactory, workSummaryMinimizedClassMap);
    workSummaryMinimizedClassMap.field("title.title.content", "title");
    workSummaryMinimizedClassMap.field("title.translatedTitle.content", "translatedTitle");
    workSummaryMinimizedClassMap.field(
        "title.translatedTitle.languageCode", "translatedTitleLanguageCode");
    workSummaryMinimizedClassMap.field("type", "workType");
    workSummaryMinimizedClassMap.field("publicationDate.year.value", "publicationYear");
    workSummaryMinimizedClassMap.field("publicationDate.month.value", "publicationMonth");
    workSummaryMinimizedClassMap.field("publicationDate.day.value", "publicationDay");
    workSummaryMinimizedClassMap
        .fieldMap("externalIdentifiers", "externalIdentifiersJson")
        .converter("workExternalIdentifiersConverterId")
        .add();
    workSummaryMinimizedClassMap.byDefault();
    workSummaryMinimizedClassMap.register();

    ClassMapBuilder<Work, MinimizedWorkEntity> minimizedWorkClassMap =
        mapperFactory.classMap(Work.class, MinimizedWorkEntity.class);
    minimizedWorkClassMap.byDefault();
    registerSourceConverters(mapperFactory, minimizedWorkClassMap);
    minimizedWorkClassMap.field("putCode", "id");
    minimizedWorkClassMap.field("journalTitle.content", "journalTitle");
    minimizedWorkClassMap.field("workTitle.title.content", "title");
    minimizedWorkClassMap.field("workTitle.translatedTitle.content", "translatedTitle");
    minimizedWorkClassMap.field(
        "workTitle.translatedTitle.languageCode", "translatedTitleLanguageCode");
    minimizedWorkClassMap.field("workTitle.subtitle.content", "subtitle");
    minimizedWorkClassMap.field("shortDescription", "description");
    minimizedWorkClassMap.field("workType", "workType");
    minimizedWorkClassMap.field("publicationDate.year.value", "publicationYear");
    minimizedWorkClassMap.field("publicationDate.month.value", "publicationMonth");
    minimizedWorkClassMap.field("publicationDate.day.value", "publicationDay");
    minimizedWorkClassMap
        .fieldMap("workExternalIdentifiers", "externalIdentifiersJson")
        .converter("workExternalIdentifiersConverterId")
        .add();
    minimizedWorkClassMap.field("url.value", "workUrl");
    minimizedWorkClassMap.register();

    mapperFactory
        .classMap(PublicationDate.class, PublicationDateEntity.class)
        .field("year.value", "year")
        .field("month.value", "month")
        .field("day.value", "day")
        .register();

    return mapperFactory.getMapperFacade();
  }
  @Override
  public MapperFacade getObject() throws Exception {
    MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

    // Register converters
    ConverterFactory converterFactory = mapperFactory.getConverterFactory();
    converterFactory.registerConverter(
        "singleWorkExternalIdentifierFromJsonConverter",
        new SingleWorkExternalIdentifierFromJsonConverter());
    converterFactory.registerConverter(
        "externalIdentifierIdConverter", new ExternalIdentifierTypeConverter());

    // Register factories
    mapperFactory.registerObjectFactory(
        new WorkEntityFactory(workDao),
        TypeFactory.<NotificationWorkEntity>valueOf(NotificationWorkEntity.class),
        TypeFactory.<Item>valueOf(Item.class));

    // Custom notification
    ClassMapBuilder<NotificationCustom, NotificationCustomEntity> notificationCustomClassMap =
        mapperFactory.classMap(NotificationCustom.class, NotificationCustomEntity.class);
    registerSourceConverters(mapperFactory, notificationCustomClassMap);
    mapCommonFields(notificationCustomClassMap).register();

    // Permission notification
    ClassMapBuilder<NotificationPermission, NotificationAddItemsEntity>
        notificationPermissionClassMap =
            mapperFactory.classMap(NotificationPermission.class, NotificationAddItemsEntity.class);
    registerSourceConverters(mapperFactory, notificationPermissionClassMap);
    mapCommonFields(
            notificationPermissionClassMap
                .field("authorizationUrl.uri", "authorizationUrl")
                .field("items.items", "notificationItems")
                .customize(
                    new CustomMapper<NotificationPermission, NotificationAddItemsEntity>() {
                      @Override
                      public void mapAtoB(
                          NotificationPermission notification,
                          NotificationAddItemsEntity entity,
                          MappingContext context) {
                        if (StringUtils.isBlank(entity.getAuthorizationUrl())) {
                          String authUrl =
                              orcidUrlManager.getBaseUrl()
                                  + notification.getAuthorizationUrl().getPath();
                          // validate
                          validateAndConvertToURI(authUrl);
                          entity.setAuthorizationUrl(authUrl);
                        }
                      }

                      @Override
                      public void mapBtoA(
                          NotificationAddItemsEntity entity,
                          NotificationPermission notification,
                          MappingContext context) {
                        AuthorizationUrl authUrl = notification.getAuthorizationUrl();
                        if (authUrl != null) {
                          authUrl.setPath(extractFullPath(authUrl.getUri()));
                          authUrl.setHost(orcidUrlManager.getBaseHost());
                        }
                      }
                    }))
        .register();

    // Institutional sign in notification
    ClassMapBuilder<NotificationInstitutionalConnection, NotificationInstitutionalConnectionEntity>
        institutionalConnectionNotificationClassMap =
            mapperFactory.classMap(
                NotificationInstitutionalConnection.class,
                NotificationInstitutionalConnectionEntity.class);
    registerSourceConverters(mapperFactory, institutionalConnectionNotificationClassMap);
    mapCommonFields(
            institutionalConnectionNotificationClassMap
                .field("authorizationUrl.uri", "authorizationUrl")
                .customize(
                    new CustomMapper<
                        NotificationInstitutionalConnection,
                        NotificationInstitutionalConnectionEntity>() {
                      @Override
                      public void mapAtoB(
                          NotificationInstitutionalConnection notification,
                          NotificationInstitutionalConnectionEntity entity,
                          MappingContext context) {
                        if (StringUtils.isBlank(entity.getAuthorizationUrl())) {
                          String authUrl =
                              orcidUrlManager.getBaseUrl()
                                  + notification.getAuthorizationUrl().getPath();
                          // validate
                          validateAndConvertToURI(authUrl);
                          entity.setAuthorizationUrl(authUrl);
                        }
                      }

                      @Override
                      public void mapBtoA(
                          NotificationInstitutionalConnectionEntity entity,
                          NotificationInstitutionalConnection notification,
                          MappingContext context) {
                        AuthorizationUrl authUrl = notification.getAuthorizationUrl();
                        if (authUrl != null) {
                          authUrl.setPath(extractFullPath(authUrl.getUri()));
                          authUrl.setHost(orcidUrlManager.getBaseHost());
                        }
                        String providerId = entity.getAuthenticationProviderId();
                        if (StringUtils.isNotBlank(providerId)) {
                          String idpName =
                              identityProviderManager.retrieveIdentitifyProviderName(providerId);
                          notification.setIdpName(idpName);
                        } else {
                          notification.setIdpName(LAST_RESORT_IDENTITY_PROVIDER_NAME);
                        }
                      }
                    }))
        .register();

    // Amend notification
    ClassMapBuilder<NotificationAmended, NotificationAmendedEntity> amendNotificationClassMap =
        mapperFactory.classMap(NotificationAmended.class, NotificationAmendedEntity.class);
    registerSourceConverters(mapperFactory, amendNotificationClassMap);
    mapCommonFields(amendNotificationClassMap).register();
    mapperFactory
        .classMap(NotificationItemEntity.class, Item.class)
        .fieldMap("externalIdType", "externalIdentifier.type")
        .converter("externalIdentifierIdConverter")
        .add()
        .field("externalIdValue", "externalIdentifier.value")
        .byDefault()
        .register();

    return mapperFactory.getMapperFacade();
  }