<repo_name>chriniko13/eresearch-repositorer-service<file_sep>/src/main/java/com/eresearch/repositorer/test/ElsevierAuthorIntegrationTest.java
package com.eresearch.repositorer.test;

import com.eresearch.repositorer.connector.ElsevierAuthorConsumerConnector;
import com.eresearch.repositorer.dto.elsevierauthor.request.AuthorFinderDto;
import com.eresearch.repositorer.dto.elsevierauthor.request.AuthorNameDto;
import com.eresearch.repositorer.dto.elsevierauthor.response.AuthorFinderImmediateResultDto;
import com.eresearch.repositorer.dto.repositorer.request.RepositorerFindDto;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;

@Component
public class ElsevierAuthorIntegrationTest {

    @Autowired
    private ElsevierAuthorConsumerConnector connector;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ModelMapper modelMapper;

    public void testIntegrationWithElsevierAuthor() throws RepositorerBusinessException, JsonProcessingException {

        //we will test also how modelmapper behaves here...

        RepositorerFindDto repositorerFindDto = RepositorerFindDto.builder()
                .firstname("Anastasios")
                .surname("Tsolakidis")
                .build();

        AuthorNameDto authorNameDto = modelMapper.map(repositorerFindDto, AuthorNameDto.class);
        AuthorFinderDto authorFinderDto = new AuthorFinderDto(authorNameDto);

        AuthorFinderImmediateResultDto authorImmediateResult =
                connector.extractInfoFromElsevierAuthorWithRetries(authorFinderDto, UUID.randomUUID().toString());

        String resultAsAString = objectMapper.writeValueAsString(authorImmediateResult);
        System.out.println("[ELSEVIER AUTHOR INTEGRATION RESULT] == " + resultAsAString);
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/service/ErrorReportServiceImpl.java
package com.eresearch.repositorer.service;

import com.eresearch.repositorer.domain.error.ErrorReport;
import com.eresearch.repositorer.dto.repositorer.request.ErrorReportFilenameDto;
import com.eresearch.repositorer.dto.repositorer.request.ErrorReportSearchDto;
import com.eresearch.repositorer.dto.repositorer.response.ErrorReportDeleteOperationResultDto;
import com.eresearch.repositorer.dto.repositorer.response.ErrorReportDeleteOperationStatus;
import com.eresearch.repositorer.dto.repositorer.response.RandomEntriesResponseDto;
import com.eresearch.repositorer.dto.repositorer.response.RetrievedErrorReportDto;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.exception.error.RepositorerError;
import com.eresearch.repositorer.repository.ErrorReportRepository;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.Clock;
import java.time.Instant;
import java.util.Collection;
import java.util.UUID;

@Log4j
@Service
public class ErrorReportServiceImpl implements ErrorReportService {

    private static final String SAMPLE_ENTRIES_STORED_SUCCESSFULLY_TO_DB = "Sample entries stored successfully to db.";

    @Autowired
    private ErrorReportRepository errorReportRepository;

    @Autowired
    private Clock clock;

    @Autowired
    private RandomStringGeneratorService randomStringGeneratorService;

    @Override
    public Collection<RetrievedErrorReportDto> find(ErrorReportSearchDto errorReportSearchDto, boolean fullFetch) {
        return errorReportRepository.find(fullFetch, errorReportSearchDto);
    }

    @Override
    public Collection<RetrievedErrorReportDto> findAll(boolean fullFetch) {
        return errorReportRepository.findAll(fullFetch);
    }

    @Override
    public RandomEntriesResponseDto addRandomEntriesForTesting() {

        for (int i = 1; i <= 20; i++) {

            ErrorReport errorReport = ErrorReport.builder()
                    .crashedComponentName(randomStringGeneratorService.randomString(3000))
                    .createdAt(Instant.now(clock))
                    .errorStacktrace(randomStringGeneratorService.randomString(3000))
                    .exceptionToString(randomStringGeneratorService.randomString(3000))
                    .failedMessage(randomStringGeneratorService.randomString(3000))
                    .id(UUID.randomUUID().toString())
                    .repositorerError(RepositorerError.UNIDENTIFIED_ERROR)
                    .build();

            errorReportRepository.store(errorReport);
        }

        return new RandomEntriesResponseDto(SAMPLE_ENTRIES_STORED_SUCCESSFULLY_TO_DB);
    }

    @Override
    public ErrorReportDeleteOperationResultDto deleteAll() {

        try {

            boolean result = errorReportRepository.deleteAll();
            return new ErrorReportDeleteOperationResultDto(result, ErrorReportDeleteOperationStatus.SUCCESS);

        } catch (RepositorerBusinessException e) {

            log.error("ErrorReportServiceImpl#deleteAll --- error occurred.", e);

            final ErrorReportDeleteOperationStatus statusToReturn;
            switch (e.getRepositorerError()) {

                case REPOSITORY_IS_EMPTY:
                    statusToReturn = ErrorReportDeleteOperationStatus.REPOSITORY_IS_EMPTY;
                    break;

                default: //Note: we should never go here....
                    statusToReturn = ErrorReportDeleteOperationStatus.APPLICATION_NOT_IN_CORRECT_STATE;
            }

            return new ErrorReportDeleteOperationResultDto(false, statusToReturn);

        }
    }

    @Override
    public ErrorReportDeleteOperationResultDto delete(ErrorReportFilenameDto errorReportFilenameDto) {
        try {

            String filename = errorReportFilenameDto.getFilename();
            boolean deleteOperationResult = errorReportRepository.delete(filename);

            return new ErrorReportDeleteOperationResultDto(deleteOperationResult, ErrorReportDeleteOperationStatus.SUCCESS);

        } catch (RepositorerBusinessException e) {

            log.error("ErrorReportServiceImpl#delete --- error occurred.", e);

            final ErrorReportDeleteOperationStatus statusToReturn;
            switch (e.getRepositorerError()) {

                case RECORD_DOES_NOT_EXIST:
                    statusToReturn = ErrorReportDeleteOperationStatus.RECORD_DOES_NOT_EXIST;
                    break;

                case NO_UNIQUE_RESULTS_EXIST_BASED_ON_PROVIDED_DISCRIMINATOR:
                    statusToReturn = ErrorReportDeleteOperationStatus.NO_UNIQUE_RESULTS_EXIST_BASED_ON_PROVIDED_DISCRIMINATOR;
                    break;

                default: //Note: we should never go here....
                    statusToReturn = ErrorReportDeleteOperationStatus.APPLICATION_NOT_IN_CORRECT_STATE;
            }

            return new ErrorReportDeleteOperationResultDto(false, statusToReturn);
        }
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/test/IntegrationTestsSuite.java
package com.eresearch.repositorer.test;

import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Log4j
@Component
public class IntegrationTestsSuite {

    @Autowired
    private AuthorMatcherIntegrationTest authorMatcherIntegrationTest;

    @Autowired
    private DblpConsumerIntegrationTest dblpConsumerIntegrationTest;

    @Autowired
    private ScienceDirectIntegrationTest scienceDirectIntegrationTest;

    @Autowired
    private ScopusIntegrationTest scopusIntegrationTest;

    @Autowired
    private ElsevierAuthorIntegrationTest elsevierAuthorIntegrationTest;

    public void runSuite() {

        //NOTE: add your scenarios or tests if needed...
        try {

            authorMatcherIntegrationTest.testIntegrationWithAuthorMatcher();
            dblpConsumerIntegrationTest.testDblpConsumerIntegration();
            scienceDirectIntegrationTest.testScienceDirectIntegration();
            scopusIntegrationTest.testScopusIntegration();
            elsevierAuthorIntegrationTest.testIntegrationWithElsevierAuthor();

        } catch (JsonProcessingException e) {
            log.error("IntegrationTestsSuite#runSuite --- error occurred.", e);
        }
    }

}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/results/elsevierauthor/ElsevierAuthorResultsTransformer.java
package com.eresearch.repositorer.transformer.results.elsevierauthor;

import com.eresearch.repositorer.dto.elsevierauthor.response.*;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.exception.error.RepositorerError;
import com.eresearch.repositorer.transformer.dto.ElsevierAuthorResultsTransformerDto;
import com.eresearch.repositorer.transformer.results.ResultsTransformer;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


@Log4j
@Component
public class ElsevierAuthorResultsTransformer implements ResultsTransformer {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public Message<?> transform(Message<?> message) {

        try {

            log.info("ElsevierAuthorResultsTransformer --> " + Thread.currentThread().getName());

            final AuthorFinderQueueResultDto authorFinderQueueResultDto = deserializeMessage(message);

            final List<String> elsevierAuthorIds = extractElsevierAuthorIds(authorFinderQueueResultDto);

            final String transactionId = authorFinderQueueResultDto.getTransactionId();

            return MessageBuilder
                    .withPayload(new ElsevierAuthorResultsTransformerDto(elsevierAuthorIds))
                    .setHeader(TRANSACTION_ID, transactionId)
                    .build();

        } catch (IOException error) {

            log.error("ElsevierAuthorResultsTransformer#deserializeMessage --- error occurred", error);

            throw new RepositorerBusinessException(RepositorerError.COULD_NOT_DESERIALIZE_MESSAGE,
                    RepositorerError.COULD_NOT_DESERIALIZE_MESSAGE.getMessage(),
                    error,
                    this.getClass().getName());
        }
    }

    private AuthorFinderQueueResultDto deserializeMessage(Message<?> message) throws IOException {

        final String resultAsString = (String) message.getPayload();
        return objectMapper.readValue(resultAsString, new TypeReference<AuthorFinderQueueResultDto>() {
        });
    }

    private List<String> extractElsevierAuthorIds(AuthorFinderQueueResultDto authorFinderQueueResultDto) {

        final List<String> elsevierAuthorIds = new ArrayList<>();

        AuthorFinderResultsDto authorFinderResultsDto = authorFinderQueueResultDto.getAuthorFinderResultsDto();

        if (!authorFinderResultsDto.getOperationResult()) {
            throw new RepositorerBusinessException(RepositorerError.COULD_NOT_PERFORM_OPERATION,
                    RepositorerError.COULD_NOT_PERFORM_OPERATION.getMessage(),
                    this.getClass().getName());
        }

        for (AuthorSearchViewResultsDto authorSearchViewResultsDto : authorFinderResultsDto.getResults()) {

            AuthorSearchViewDto authorSearchViewDto = authorSearchViewResultsDto.getAuthorSearchViewDto();

            if (noResultsToProcess(authorSearchViewDto)) continue;

            for (AuthorSearchViewEntry authorSearchViewEntry : authorSearchViewDto.getEntries()) {

                //this will contain info such as: "dc:identifier": "AUTHOR_ID:23007591800"
                String dcIdentifier = authorSearchViewEntry.getDcIdentifier();
                final String elsevierAuthorId = dcIdentifier.split(":")[1];

                elsevierAuthorIds.add(elsevierAuthorId);
            }
        }

        return elsevierAuthorIds;
    }

    private boolean noResultsToProcess(AuthorSearchViewDto authorSearchViewDto) {
        return "0".equals(authorSearchViewDto.getTotalResults())
                && "0".equals(authorSearchViewDto.getStartIndex())
                && "0".equals(authorSearchViewDto.getItemsPerPage())
                && authorSearchViewDto.getEntries() != null
                && authorSearchViewDto.getEntries().size() == 1
                && authorSearchViewDto.getEntries().iterator().next().getError().equals("Result set was empty");
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/dblp/response/generated/Dblp.java
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2017.02.27 at 11:20:24 PM EET 
//


package com.eresearch.repositorer.dto.dblp.response.generated;

import lombok.Getter;
import lombok.Setter;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.NormalizedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.List;

@Getter
@Setter

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "dblp")
public class Dblp {

    @XmlAttribute(name = "mdate")
    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
    protected String mdate;

    @XmlElement(name = "article", type = Article.class)
    private List<Article> articles;

    @XmlElement(name = "inproceedings", type = Inproceedings.class)
    private List<Inproceedings> inproceedings;

    @XmlElement(name = "proceedings", type = Proceedings.class)
    private List<Proceedings> proceedings;

    @XmlElement(name = "book", type = Book.class)
    private List<Book> books;

    @XmlElement(name = "incollection", type = Incollection.class)
    private List<Incollection> incollections;

    @XmlElement(name = "phdthesis", type = Phdthesis.class)
    private List<Phdthesis> phdthesis;

    @XmlElement(name = "mastersthesis", type = Mastersthesis.class)
    private List<Mastersthesis> mastersthesis;

    @XmlElement(name = "www", type = Www.class)
    private List<Www> www;

    @XmlElement(name = "person", type = Person.class)
    private List<Person> persons;

    @XmlElement(name = "data", type = Data.class)
    private List<Data> data;

}
<file_sep>/src/main/java/com/eresearch/repositorer/validator/Validator.java
package com.eresearch.repositorer.validator;

import com.eresearch.repositorer.exception.data.RepositorerValidationException;

public interface Validator<T> {

    void validate(T data) throws RepositorerValidationException;
}
<file_sep>/src/main/java/com/eresearch/repositorer/repository/NamesLookupRepository.java
package com.eresearch.repositorer.repository;


import com.eresearch.repositorer.domain.lookup.NameLookup;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;

public interface NamesLookupRepository extends MongoRepository<NameLookup, String> {

    NameLookup findNamesLookupByTransactionIdEquals(String transactionId);

    List<NameLookup> findAllByFirstnameEqualsAndInitialsEqualsAndSurnameEquals(String firstname, String initial, String surname);
}
<file_sep>/src/main/java/com/eresearch/repositorer/aggregator/release/ElsevierAuthorMessagesAwaitingReleaseStrategy.java
package com.eresearch.repositorer.aggregator.release;

import com.eresearch.repositorer.transformer.RepositorerFindDtoPopulator;
import org.springframework.integration.aggregator.ReleaseStrategy;
import org.springframework.integration.store.MessageGroup;
import org.springframework.stereotype.Component;

@Component
public class ElsevierAuthorMessagesAwaitingReleaseStrategy implements ReleaseStrategy {

    private static final Integer OPERATIONS_PER_EXTERNAL_SYSTEM = RepositorerFindDtoPopulator.NO_OF_NAME_VARIANTS;

    private static final Integer NO_OF_EXTERNAL_SYSTEMS = ExternalSystem
            .values()
            .length;

    private enum ExternalSystem {
        ELSEVIER_AUTHOR
    }

    @Override
    public boolean canRelease(MessageGroup group) {
        return group.size() == NO_OF_EXTERNAL_SYSTEMS * OPERATIONS_PER_EXTERNAL_SYSTEM;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/validator/RepositorerFindDtosValidator.java
package com.eresearch.repositorer.validator;

import com.eresearch.repositorer.dto.repositorer.request.RepositorerFindDto;
import com.eresearch.repositorer.dto.repositorer.request.RepositorerFindDtos;
import com.eresearch.repositorer.exception.data.RepositorerValidationException;
import com.eresearch.repositorer.exception.error.RepositorerError;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.stream.Stream;

/*
    Note: Only initials could be empty or null.
 */
@Component
@Log4j
public class RepositorerFindDtosValidator implements Validator<RepositorerFindDtos> {

    @Value("${no.records.to.process.threshold}")
    private String noOfRecordsToProcessThreshold;

    private int noOfRecordsToProcessThresholdInt;

    @PostConstruct
    public void init() {
        noOfRecordsToProcessThresholdInt = Integer.parseInt(noOfRecordsToProcessThreshold);
    }

    @Override
    public void validate(RepositorerFindDtos dtos) throws RepositorerValidationException {

        // first validation...
        if (dtos == null || dtos.getRepositorerFindDtos() == null || dtos.getRepositorerFindDtos().isEmpty()) {
            log.error("RepositorerFindDtosValidator#validate --- error occurred (first validation) --- repositorerFindDtos = " + dtos);
            throw new RepositorerValidationException(RepositorerError.INVALID_DATA_ERROR, RepositorerError.INVALID_DATA_ERROR.getMessage());
        }

        // second validation...
        if (dtos.getRepositorerFindDtos().size() > noOfRecordsToProcessThresholdInt) {
            log.error("RepositorerFindDtosValidator#validate --- error occurred (second validation) --- repositorerFindDtos = " + dtos);
            throw new RepositorerValidationException(
                    RepositorerError.LIMIT_FOR_NUMBER_OF_TOTAL_RECORDS_TO_PROCESS_EXCEEDED,
                    RepositorerError.LIMIT_FOR_NUMBER_OF_TOTAL_RECORDS_TO_PROCESS_EXCEEDED.getMessage());
        }

        // third validation...
        for (RepositorerFindDto repositorerFindDto : dtos.getRepositorerFindDtos()) {
            boolean validationError = Stream
                    .of(repositorerFindDto.getFirstname(), repositorerFindDto.getSurname())
                    .anyMatch(d -> d == null || "".equals(d));

            if (validationError) {
                log.error("RepositorerFindDtosValidator#validate --- error occurred (third validation) --- repositorerFindDtos = " + dtos);
                throw new RepositorerValidationException(RepositorerError.INVALID_DATA_ERROR, RepositorerError.INVALID_DATA_ERROR.getMessage());
            }
        }
    }
}
<file_sep>/src/main/resources/application-dev.properties
# Note add you development properties here.<file_sep>/src/main/java/com/eresearch/repositorer/transformer/RecordsApplyUniquenessTransformer.java
package com.eresearch.repositorer.transformer;

import com.eresearch.repositorer.domain.record.Entry;
import com.eresearch.repositorer.domain.record.Record;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

@Log4j
@Component
public class RecordsApplyUniquenessTransformer {

    private static final String TRANSACTION_ID = "Transaction-Id";

    @Value("${apply.uniqueness.on.record.title}")
    private String applyUniquenessOnRecordTitle;

    public Message<?> transform(Message<?> message) {

        log.info("RecordsApplyUniquenessTransformer --> " + Thread.currentThread().getName());

        Record record = (Record) message.getPayload();
        String transactionId = (String) message.getHeaders().get(TRANSACTION_ID);

        if (Boolean.valueOf(applyUniquenessOnRecordTitle)) {
            Collection<Entry> entries = record.getEntries();

            if (entries != null && !entries.isEmpty()) {
                Set<Entry> uniqueEntries = new LinkedHashSet<>(entries);
                record.setEntries(uniqueEntries);
            }
        }

        return MessageBuilder
                .withPayload(record)
                .setHeader(TRANSACTION_ID, transactionId)
                .build();
    }

}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/repositorer/response/RetrievedNameLookupDtos.java
package com.eresearch.repositorer.dto.repositorer.response;

import com.eresearch.repositorer.domain.lookup.NameLookup;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Collection;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class RetrievedNameLookupDtos {

    private Collection<NameLookup> nameLookups;
}
<file_sep>/src/main/java/com/eresearch/repositorer/service/NameLookupService.java
package com.eresearch.repositorer.service;

import com.eresearch.repositorer.domain.lookup.NameLookup;
import com.eresearch.repositorer.dto.repositorer.request.NameLookupSearchDto;
import com.eresearch.repositorer.dto.repositorer.response.NameLookupDeleteOperationResultDto;
import com.eresearch.repositorer.dto.repositorer.response.RandomEntriesResponseDto;

import java.util.Collection;

public interface NameLookupService {

    NameLookup find(NameLookupSearchDto nameLookupSearchDto);

    Collection<NameLookup> findAll();

    RandomEntriesResponseDto addRandomEntriesForTesting();

    NameLookupDeleteOperationResultDto deleteAll();

    NameLookupDeleteOperationResultDto delete(NameLookupSearchDto nameLookupSearchDto);
}
<file_sep>/src/main/java/com/eresearch/repositorer/repository/RecordRepository.java
package com.eresearch.repositorer.repository;

import com.eresearch.repositorer.domain.record.Author;
import com.eresearch.repositorer.domain.record.Record;
import com.eresearch.repositorer.dto.repositorer.response.RetrievedRecordDto;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.exception.error.RepositorerError;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.ByteStreams;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.gridfs.GridFSDBFile;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Repository;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.*;
import java.util.regex.Pattern;

@Log4j
@Repository
public class RecordRepository implements EresearchRepositorerRepository<Record, RetrievedRecordDto, Author> {

    private static final String FILENAME_PREFIX = "RECORD";

    private static final String RECORD_TYPE = "RECORD";

    private static final boolean APPLY_DISTINCTION_ON_SEARCH_RESULTS = true;

    private static final String CONTENT_TYPE = "application/octet-stream";

    private static final String NO_VALUE = "NoValue";

    private static final String REGEX_FOR_ALL = ".*";

    private final GridFsTemplate gridFsTemplate;
    private final Clock clock;
    private final Pattern findAllRecordsPattern;
    private final ObjectMapper objectMapper;

    @Autowired
    public RecordRepository(GridFsTemplate gridFsTemplate, Clock clock, ObjectMapper objectMapper) {
        this.gridFsTemplate = gridFsTemplate;
        this.clock = clock;
        this.objectMapper = objectMapper;

        findAllRecordsPattern = Pattern.compile(REGEX_FOR_ALL);
    }

    @Override
    public boolean deleteAll() {
        // first perform all necessary validations....
        List<GridFSDBFile> gridFSDBFiles = gridFsTemplate.find(Query.query(Criteria.where("filename").regex(FILENAME_PREFIX + findAllRecordsPattern)));
        if (gridFSDBFiles.size() == 0) {
            throw new RepositorerBusinessException(RepositorerError.REPOSITORY_IS_EMPTY,
                    RepositorerError.REPOSITORY_IS_EMPTY.getMessage());
        }

        // then perform delete operation...
        gridFsTemplate.delete(Query.query(Criteria.where("filename").regex(FILENAME_PREFIX + findAllRecordsPattern)));

        // and then perform all necessary validations again...
        return findAll(false).size() == 0;
    }

    @Override
    public boolean delete(String filename) {
        // first perform all necessary validations....
        List<GridFSDBFile> gridFSDBFiles = gridFsTemplate.find(Query.query(Criteria.where("filename").is(filename)));

        if (gridFSDBFiles.size() > 1) {
            throw new RepositorerBusinessException(RepositorerError.NO_UNIQUE_RESULTS_EXIST_BASED_ON_PROVIDED_DISCRIMINATOR,
                    RepositorerError.NO_UNIQUE_RESULTS_EXIST_BASED_ON_PROVIDED_DISCRIMINATOR.getMessage());
        }

        if (gridFSDBFiles.size() == 0) {
            throw new RepositorerBusinessException(RepositorerError.RECORD_DOES_NOT_EXIST,
                    RepositorerError.RECORD_DOES_NOT_EXIST.getMessage());
        }

        // then perform delete operation...
        gridFsTemplate.delete(Query.query(Criteria.where("filename").is(filename)));

        // and then perform all necessary validations again...
        gridFSDBFiles = gridFsTemplate.find(Query.query(Criteria.where("filename").is(filename)));
        return gridFSDBFiles.size() == 0;
    }

    @Override
    public Collection<RetrievedRecordDto> find(String filename, boolean fullFetch) {

        final Collection<RetrievedRecordDto> retrievedRecordDtos = new ArrayList<>();

        List<GridFSDBFile> gridFSDBFiles = gridFsTemplate.find(Query.query(Criteria.where("filename").is(filename)));

        populateRetrievedRecordsWithResults(retrievedRecordDtos, gridFSDBFiles, fullFetch);

        return retrievedRecordDtos;
    }

    @Override
    public Collection<RetrievedRecordDto> find(boolean fullFetch, Author mainAuthorName) {

        //------------- do main search --------------
        final Collection<RetrievedRecordDto> retrievedRecordDtos = APPLY_DISTINCTION_ON_SEARCH_RESULTS ? new HashSet<>() : new ArrayList<>();

        String filenameToSearchFor = createFilenameToSearchFor(mainAuthorName);

        final List<GridFSDBFile> retrievedGridFSDBFilesFromMainSearch
                = gridFsTemplate.find(Query.query(Criteria.where("filename").regex(filenameToSearchFor)));

        populateRetrievedRecordsWithResults(retrievedRecordDtos, retrievedGridFSDBFilesFromMainSearch, fullFetch);

        //--------- and then do additional search based on splitted metadata info ------------
        List<GridFSDBFile> retrievedGridFSDBFilesFromNameVariantsSearch = gridFsTemplate.find(
                Query.query(
                        Criteria.where("metadata.authorRecordName.firstname")
                                .regex(REGEX_FOR_ALL + mainAuthorName.getFirstname() + REGEX_FOR_ALL)
                                .and("metadata.authorRecordName.initials")
                                .regex(REGEX_FOR_ALL + mainAuthorName.getInitials() + REGEX_FOR_ALL)
                                .and("metadata.authorRecordName.lastname")
                                .regex(REGEX_FOR_ALL + mainAuthorName.getSurname() + REGEX_FOR_ALL)
                                .and("metadata.recordType")
                                .regex(RECORD_TYPE)
                )
        );

        populateRetrievedRecordsWithResults(retrievedRecordDtos, retrievedGridFSDBFilesFromNameVariantsSearch, fullFetch);

        return retrievedRecordDtos;
    }

    @Override
    public Collection<RetrievedRecordDto> findAll(boolean fullFetch) {
        final List<RetrievedRecordDto> retrievedRecordDtos = new ArrayList<>();

        List<GridFSDBFile> gridFSDBFiles = gridFsTemplate.find(Query.query(Criteria.where("filename").regex(FILENAME_PREFIX + findAllRecordsPattern)));

        populateRetrievedRecordsWithResults(retrievedRecordDtos, gridFSDBFiles, fullFetch);

        return retrievedRecordDtos;
    }

    @Override
    public void store(Record record) {

        //first serialize record...
        try {
            byte[] bytes = objectMapper.writeValueAsBytes(record);

            //then transform the serialized record into an input stream...
            try (InputStream is = new ByteArrayInputStream(bytes)) {

                //finally store the info...
                String fileName = createFilename(record) + "#" + getStringifiedDate(record);
                DBObject metaData = createMetadata(record);
                gridFsTemplate.store(is, fileName, CONTENT_TYPE, metaData);
            }

        } catch (Exception error) {
            log.error("RecordRepository#store --- error occurred.", error);
            throw new RepositorerBusinessException(RepositorerError.COULD_NOT_SERIALIZE_OBJECT,
                    RepositorerError.COULD_NOT_SERIALIZE_OBJECT.getMessage(),
                    error,
                    this.getClass().getName());
        }
    }

    private String createFilename(Record record) {
        return FILENAME_PREFIX
                + normalizeInfo(record.getFirstname())
                + "_"
                + normalizeInfo(record.getInitials())
                + "_"
                + normalizeInfo(record.getLastname());
    }

    private DBObject createMetadata(Record record) {
        DBObject metaData = new BasicDBObject();

        DBObject authorRecordName = new BasicDBObject();
        authorRecordName.put("firstname", record.getFirstname());
        authorRecordName.put("initials", record.getInitials());
        authorRecordName.put("lastname", record.getLastname());

        metaData.put("authorRecordName", authorRecordName);
        metaData.put("recordCreatedAt", getStringifiedDate(record));
        metaData.put("recordType", RECORD_TYPE);

        return metaData;
    }

    private String getStringifiedDate(Record record) {
        return LocalDateTime.ofInstant(record.getCreatedAt(), clock.getZone()).toString();
    }

    private String normalizeInfo(String info) {
        return Optional.ofNullable(info).filter(i -> !i.isEmpty()).orElse(NO_VALUE);
    }

    private String createFilenameToSearchFor(Author author) {
        return FILENAME_PREFIX
                + normalizeInfo(author.getFirstname()) + REGEX_FOR_ALL
                + "_"
                + normalizeInfo(author.getInitials()) + REGEX_FOR_ALL
                + "_"
                + normalizeInfo(author.getSurname()) + REGEX_FOR_ALL;
    }

    private void populateRetrievedRecordsWithResults(Collection<RetrievedRecordDto> retrievedRecordDtos, List<GridFSDBFile> gridFSDBFiles, boolean fullFetch) {

        for (GridFSDBFile gridFSDBFile : gridFSDBFiles) {

            String filename = gridFSDBFile.getFilename();

            RetrievedRecordDto retrievedRecordDto = new RetrievedRecordDto();
            retrievedRecordDto.setFilename(filename);

            if (fullFetch) {
                Record record = deserializeStoredRecord(gridFSDBFile.getInputStream());
                retrievedRecordDto.setRecord(record);
            }

            retrievedRecordDtos.add(retrievedRecordDto);
        }
    }

    private Record deserializeStoredRecord(InputStream inputStream) {
        try {

            byte[] bytes = ByteStreams.toByteArray(inputStream);
            return objectMapper.readValue(bytes, Record.class);

        } catch (Exception error) {

            log.error("RecordRepository#deserializeStoredRecord --- error occurred.", error);
            throw new RepositorerBusinessException(RepositorerError.COULD_NOT_DESERIALIZE_OBJECT,
                    RepositorerError.COULD_NOT_DESERIALIZE_OBJECT.getMessage(),
                    error,
                    this.getClass().getName());
        }
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/application/configuration/EventBusConfiguration.java
package com.eresearch.repositorer.application.configuration;

import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Configuration
public class EventBusConfiguration {

    @Bean
    public EventBus batchExtractorEventBus() {

        ThreadPoolExecutor batchExtractorThreadPool = new ThreadPoolExecutor(
                20, 80,
                5000L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(300, true),
                new ThreadFactoryBuilder().setNameFormat("batch-extractor-thread-%d").build(),
                new ThreadPoolExecutor.CallerRunsPolicy());

        return new AsyncEventBus("batchExtractorBus", batchExtractorThreadPool);
    }

}
<file_sep>/src/main/java/com/eresearch/repositorer/application/configuration/AppConfiguration.java
package com.eresearch.repositorer.application.configuration;

import com.eresearch.repositorer.deserializer.InstantDeserializer;
import com.eresearch.repositorer.deserializer.LocalDateDeserializer;
import com.eresearch.repositorer.deserializer.LocalTimeDeserializer;
import com.eresearch.repositorer.serializer.InstantSerializer;
import com.eresearch.repositorer.serializer.LocalDateSerializer;
import com.eresearch.repositorer.serializer.LocalTimeSerializer;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.handler.ssl.SslContextBuilder;
import net.jodah.failsafe.RetryPolicy;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.client.Netty4ClientHttpRequestFactory;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLException;
import java.time.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


@Configuration
public class AppConfiguration {

    @Bean
    @Qualifier("basicRetryPolicyForConnectors")
    public RetryPolicy basicRetryPolicyForConnectors() {
        return new RetryPolicy()
                .retryOn(RestClientException.class)
                .withMaxRetries(10)
                .withDelay(8, TimeUnit.SECONDS)
                .withJitter(7, TimeUnit.SECONDS);
    }

    @Bean(destroyMethod = "shutdownNow")
    @Qualifier("authorExtractionExecutor")
    public ExecutorService authorExtractionExecutor() {
        return new ThreadPoolExecutor(
                20, 80,
                5000L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(300, true),
                new ThreadFactoryBuilder().setNameFormat("author-extraction-thread-%d").build(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }

    @Bean(destroyMethod = "shutdownNow")
    @Qualifier("authorEntriesMatchingExecutor")
    public ExecutorService authorEntriesMatchingExecutor() {
        return new ThreadPoolExecutor(
                20, 80,
                5000L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(300, true),
                new ThreadFactoryBuilder().setNameFormat("author-entries-matching-thread-%d").build(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }

    @Bean(destroyMethod = "shutdownNow")
    @Qualifier("dblpEntriesProcessorsExecutor")
    public ExecutorService dblpEntriesProcessorsExecutor() {
        return new ThreadPoolExecutor(
                20, 80,
                5000L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(300, true),
                new ThreadFactoryBuilder().setNameFormat("dblp-entries-processing-thread-%d").build(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }

    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();

        JavaTimeModule javaTimeModule = new JavaTimeModule();

        javaTimeModule.addSerializer(Instant.class, new InstantSerializer());
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer());
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());

        javaTimeModule.addDeserializer(Instant.class, new InstantDeserializer());
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer());

        objectMapper.registerModule(javaTimeModule);

        objectMapper.findAndRegisterModules();
        objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); //for elsevier api.

        return objectMapper;
    }

    @Bean
    @Qualifier("repositorerRestTemplate")
    public RestTemplate restTemplate() throws SSLException {
        Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory();
        nettyFactory.setSslContext(SslContextBuilder.forClient().build());

        return new RestTemplate(nettyFactory);
    }

    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }

    @Value("${service.zone.id}")
    private ZoneId zoneId;

    @Bean
    public Clock clock() {
        return Clock.system(zoneId);
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/service/RandomStringGeneratorService.java
package com.eresearch.repositorer.service;

import org.springframework.stereotype.Service;

import java.security.SecureRandom;

@Service
public class RandomStringGeneratorService {

    private static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    private static SecureRandom rnd = new SecureRandom();

    public String randomString(int len) {
        final StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            sb.append(AB.charAt(rnd.nextInt(AB.length())));
        }
        return sb.toString();
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/results/dblp/processor/DblpDataProcessorBasic.java
package com.eresearch.repositorer.transformer.results.dblp.processor;

import com.eresearch.repositorer.domain.record.Entry;
import com.eresearch.repositorer.dto.dblp.response.DblpAuthor;
import com.eresearch.repositorer.dto.dblp.response.generated.Data;
import com.eresearch.repositorer.dto.dblp.response.generated.Dblp;
import com.eresearch.repositorer.transformer.results.dblp.processor.common.EntryCreator;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.LinkedList;
import java.util.List;

@Component
public class DblpDataProcessorBasic implements DblpProcessor {

    private final EntryCreator entryCreator;

    @Autowired
    public DblpDataProcessorBasic(EntryCreator entryCreator) {
        this.entryCreator = entryCreator;
    }

    @Override
    public void doProcessing(List<Entry> entries, Dblp dblp, DblpAuthor dblpAuthor) throws JsonProcessingException {

        List<Data> data = dblp.getData();

        if (data != null && !data.isEmpty()) {

            final List<Entry> collectedEntries = new LinkedList<>();

            for (Data datum : data) {

                entryCreator.create(dblpAuthor, collectedEntries, datum);
            }

            entries.addAll(collectedEntries);
        }
    }

    @Override
    public boolean allowProcessing() {
        return false;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/scopus/response/ScopusConsumerResultsDto.java
package com.eresearch.repositorer.dto.scopus.response;


import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ScopusConsumerResultsDto {

    @JsonProperty("search-results")
    private ScopusConsumerSearchViewDto scopusConsumerSearchViewDto;
}
<file_sep>/src/main/java/com/eresearch/repositorer/service/RecordServiceImpl.java
package com.eresearch.repositorer.service;

import com.eresearch.repositorer.domain.record.Author;
import com.eresearch.repositorer.dto.repositorer.request.RecordFilenameDto;
import com.eresearch.repositorer.dto.repositorer.request.RecordSearchDto;
import com.eresearch.repositorer.dto.repositorer.response.RecordDeleteOperationResultDto;
import com.eresearch.repositorer.dto.repositorer.response.RecordDeleteOperationStatus;
import com.eresearch.repositorer.dto.repositorer.response.RecordSearchResultDto;
import com.eresearch.repositorer.dto.repositorer.response.RetrievedRecordDto;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.repository.RecordRepository;
import lombok.extern.log4j.Log4j;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Log4j
public class RecordServiceImpl implements RecordService {

    private final RecordRepository recordRepository;
    private final ModelMapper modelMapper;

    @Autowired
    public RecordServiceImpl(RecordRepository recordRepository, ModelMapper modelMapper) {
        this.recordRepository = recordRepository;
        this.modelMapper = modelMapper;
    }

    @Override
    public RecordSearchResultDto find(RecordSearchDto recordSearchDto, boolean fullFetch) {

        Author author = modelMapper.map(recordSearchDto, Author.class);

        Collection<RetrievedRecordDto> retrievedRecordDtos = recordRepository.find(fullFetch, author);

        return new RecordSearchResultDto(retrievedRecordDtos);
    }

    @Override
    public RecordSearchResultDto findAll(boolean fullFetch) {
        Collection<RetrievedRecordDto> retrievedRecordDtos = recordRepository.findAll(fullFetch);

        List<RetrievedRecordDto> fromEarliestToLatestRecordDtos = retrievedRecordDtos
                .stream()
                .sorted(earliestToLatestRecordsComparator())
                .collect(Collectors.toList());

        return new RecordSearchResultDto(fromEarliestToLatestRecordDtos);
    }

    @Override
    public RecordDeleteOperationResultDto deleteAll() {
        try {

            boolean result = recordRepository.deleteAll();
            return new RecordDeleteOperationResultDto(result, RecordDeleteOperationStatus.SUCCESS);

        } catch (RepositorerBusinessException e) {

            final RecordDeleteOperationStatus statusToReturn;
            switch (e.getRepositorerError()) {

                case REPOSITORY_IS_EMPTY:
                    statusToReturn = RecordDeleteOperationStatus.REPOSITORY_IS_EMPTY;
                    break;

                default: //Note: we should never go here....
                    statusToReturn = RecordDeleteOperationStatus.APPLICATION_NOT_IN_CORRECT_STATE;
            }

            return new RecordDeleteOperationResultDto(false, statusToReturn);

        }
    }

    @Override
    public RecordDeleteOperationResultDto delete(RecordFilenameDto recordFilenameDto) {
        try {

            String filename = recordFilenameDto.getFilename();
            boolean deleteOperationResult = recordRepository.delete(filename);

            return new RecordDeleteOperationResultDto(deleteOperationResult, RecordDeleteOperationStatus.SUCCESS);

        } catch (RepositorerBusinessException e) {

            final RecordDeleteOperationStatus statusToReturn;
            switch (e.getRepositorerError()) {

                case RECORD_DOES_NOT_EXIST:
                    statusToReturn = RecordDeleteOperationStatus.RECORD_DOES_NOT_EXIST;
                    break;

                case NO_UNIQUE_RESULTS_EXIST_BASED_ON_PROVIDED_DISCRIMINATOR:
                    statusToReturn = RecordDeleteOperationStatus.NO_UNIQUE_RESULTS_EXIST_BASED_ON_PROVIDED_DISCRIMINATOR;
                    break;

                default: //Note: we should never go here....
                    statusToReturn = RecordDeleteOperationStatus.APPLICATION_NOT_IN_CORRECT_STATE;
            }

            return new RecordDeleteOperationResultDto(false, statusToReturn);
        }
    }

    @Override
    public RecordSearchResultDto find(RecordFilenameDto recordFilenameDto, boolean fullFetch) {
        Collection<RetrievedRecordDto> dtos = recordRepository.find(recordFilenameDto.getFilename(), fullFetch);
        return new RecordSearchResultDto(dtos);
    }

    private Comparator<RetrievedRecordDto> earliestToLatestRecordsComparator() {
        return (o1, o2) -> {
            //Note: filename contains info such as: Errikos_NoValue_Ventouras#2017-08-28T01:07:10.590
            String createdAtStr_o1 = o1.getFilename().split("#")[1];
            LocalDateTime localDateTime_o1 = LocalDateTime.parse(createdAtStr_o1);

            String createdAtStr_o2 = o2.getFilename().split("#")[1];
            LocalDateTime localDateTime_o2 = LocalDateTime.parse(createdAtStr_o2);

            return localDateTime_o1.compareTo(localDateTime_o2);
        };
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/gateway/AuthorExtractor.java
package com.eresearch.repositorer.gateway;

import com.eresearch.repositorer.dto.repositorer.request.RepositorerFindDto;
import org.springframework.integration.annotation.Gateway;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;

import java.util.concurrent.Future;

public interface AuthorExtractor {

    @Gateway
    Future<Void> extract(@Payload RepositorerFindDto repositorerFindDto,
                         @Header("Transaction-Id") String transactionId);
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/elsevierauthor/response/AuthorSearchViewAffiliationCurrent.java
package com.eresearch.repositorer.dto.elsevierauthor.response;

import com.fasterxml.jackson.annotation.JsonProperty;


public class AuthorSearchViewAffiliationCurrent {

    @JsonProperty("affiliation-url")
    private String url;

    @JsonProperty("affiliation-id")
    private String id;

    @JsonProperty("affiliation-name")
    private String name;

    @JsonProperty("affiliation-city")
    private String city;

    @JsonProperty("affiliation-country")
    private String country;

    public AuthorSearchViewAffiliationCurrent() {
    }

    public AuthorSearchViewAffiliationCurrent(String url, String id, String name, String city, String country) {
        this.url = url;
        this.id = id;
        this.name = name;
        this.city = city;
        this.country = country;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }
}
<file_sep>/docker-compose.yml
version: '3.3'

services:

  #### INFRASTRUCTURE ####
  activemq:
    image: webcenter/activemq:latest
    ports:
      - 8161:8161
      - 61616:61616
      - 61613:61613
    environment:
      ACTIVEMQ_NAME: amq
      ACTIVEMQ_REMOVE_DEFAULT_ACCOUNT: 'True'
      ACTIVEMQ_ADMIN_LOGIN: admin
      ACTIVEMQ_ADMIN_PASSWORD: <PASSWORD>
    volumes:
      - /container_data/activemq/data:/data/activemq
      - /container_data/activemq/log:/var/log/activemq

  mongo:
    image: mongo
    ports:
      - "27017:27017"
    volumes:
      - data-volume:/data/db



  #### PLATFORM SERVICES ####

  eresearch-scidir-consumer:
    image: chriniko/eresearch-scidir-consumer:1.0
    depends_on:
      - activemq
    ports:
      - 8080:8080

  eresearch-scopus-consumer:
    image: chriniko/eresearch-scopus-consumer:1.0
    depends_on:
      - activemq
    ports:
      - 8082:8082

  eresearch-dblp-consumer:
    image: chriniko/eresearch-dblp-consumer:1.0
    depends_on:
      - activemq
    ports:
      - 8884:8884

  eresearch-author-finder:
    image: chriniko/eresearch-author-finder:1.0
    depends_on:
      - activemq
    ports:
      - 8083:8083

  eresearch-author-matcher:
    image: chriniko/eresearch-author-matcher:1.0
    ports:
      - 8881:8881


volumes:
  data-volume:
  my-db:
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/results/scopus/ElsevierScopusResultsTransformer.java
package com.eresearch.repositorer.transformer.results.scopus;

import com.eresearch.repositorer.domain.record.Author;
import com.eresearch.repositorer.domain.record.Entry;
import com.eresearch.repositorer.domain.record.metadata.MetadataLabelsHolder;
import com.eresearch.repositorer.domain.record.metadata.Source;
import com.eresearch.repositorer.dto.scopus.response.*;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.exception.error.RepositorerError;
import com.eresearch.repositorer.transformer.dto.TransformedEntriesDto;
import com.eresearch.repositorer.transformer.results.ResultsTransformer;
import com.eresearch.repositorer.transformer.results.metadata.ProcessMetadataPredicate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Log4j
@Component
public class ElsevierScopusResultsTransformer implements ResultsTransformer {

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ProcessMetadataPredicate processMetadataPredicate;

    @Override
    public Message<?> transform(Message<?> message) {

        log.info("ElsevierScopusResultsTransformer --> " + Thread.currentThread().getName());

        final String transactionId;
        try {

            final ScopusFinderQueueResultDto scopusFinderQueueResultDto = deserializeMessage(message);

            final List<Entry> transformedEntries = doProcessing(scopusFinderQueueResultDto);

            transactionId = scopusFinderQueueResultDto.getTransactionId();

            return MessageBuilder
                    .withPayload(new TransformedEntriesDto(transformedEntries))
                    .setHeader(TRANSACTION_ID, transactionId)
                    .build();

        } catch (IOException error) {
            log.error("ElsevierScopusResultsTransformer#transform --- error occurred, error message: " + error.toString(), error);

            throw new RepositorerBusinessException(RepositorerError.COULD_NOT_DESERIALIZE_MESSAGE,
                    RepositorerError.COULD_NOT_DESERIALIZE_MESSAGE.getMessage(),
                    error,
                    this.getClass().getName());
        }
    }

    private ScopusFinderQueueResultDto deserializeMessage(Message<?> message) throws IOException {

        final String resultAsString = (String) message.getPayload();
        return objectMapper.readValue(resultAsString, new TypeReference<ScopusFinderQueueResultDto>() {
        });

    }

    private List<Entry> doProcessing(ScopusFinderQueueResultDto scopusFinderQueueResultDto) throws JsonProcessingException {

        final List<Entry> transformedEntries = new ArrayList<>();

        ElsevierScopusConsumerResultsDto elsevierScopusConsumerResultsDto = scopusFinderQueueResultDto.getScopusConsumerResultsDto();

        if (!elsevierScopusConsumerResultsDto.getOperationResult()) {
            throw new RepositorerBusinessException(RepositorerError.COULD_NOT_PERFORM_OPERATION,
                    RepositorerError.COULD_NOT_PERFORM_OPERATION.getMessage(),
                    this.getClass().getName());
        }

        for (ScopusConsumerResultsDto scopusConsumerResultsDto : elsevierScopusConsumerResultsDto.getResults()) {

            ScopusConsumerSearchViewDto scopusConsumerSearchViewDto = scopusConsumerResultsDto.getScopusConsumerSearchViewDto();

            if (noResultsToProcess(scopusConsumerSearchViewDto)) continue;

            for (ScopusSearchViewEntry entry : scopusConsumerSearchViewDto.getEntries()) {

                Entry entryToSave = createEntry(entry);
                transformedEntries.add(entryToSave);
            }
        }

        return transformedEntries;
    }

    private boolean noResultsToProcess(ScopusConsumerSearchViewDto scopusConsumerSearchViewDto) {
        return "0".equals(scopusConsumerSearchViewDto.getTotalResults())
                && "0".equals(scopusConsumerSearchViewDto.getStartIndex())
                && "0".equals(scopusConsumerSearchViewDto.getItemsPerPage())
                && scopusConsumerSearchViewDto.getEntries() != null
                && scopusConsumerSearchViewDto.getEntries().size() == 1
                && scopusConsumerSearchViewDto.getEntries().iterator().next().getError().equals("Result set was empty");
    }

    private Entry createEntry(ScopusSearchViewEntry entry) throws JsonProcessingException {

        final Map<String, String> metadata;
        if (processMetadataPredicate.doMetadataProcessing()) {
            metadata = metadataPopulation(entry);
        } else {
            metadata = null;
        }

        String dcTitle = entry.getDcTitle();

        Set<Author> entryAuthors = new LinkedHashSet<>();
        Collection<ScopusSearchAuthor> authors = entry.getAuthors();
        if (authors != null && !authors.isEmpty()) {

            entryAuthors = authors
                    .stream()
                    .map(author -> new Author(author.getGivenName(), author.getInitials(), author.getSurname()))
                    .collect(LinkedHashSet::new, Set::add, Set::addAll);
        }

        return Entry
                .builder()
                .title(dcTitle)
                .authors(entryAuthors)
                .metadata(metadata)
                .build();
    }

    private Map<String, String> metadataPopulation(ScopusSearchViewEntry entry) throws JsonProcessingException {

        Map<String, String> metadata = new LinkedHashMap<>();
        metadata.put(MetadataLabelsHolder.SOURCE.getLabelName(),
                Source.ELSEVIER_SCOPUS.getSourceName());

        Collection<ScopusSearchViewLink> links = entry.getLinks();
        if (links != null && !links.isEmpty()) {
            List<String> linksAsHrefs = links
                    .stream()
                    .map(ScopusSearchViewLink::getHref)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());

            String linksAsHrefsString = objectMapper.writeValueAsString(linksAsHrefs);
            metadata.put(MetadataLabelsHolder.ScopusLabels.LINKS.getLabelName(),
                    linksAsHrefsString);
        } else {
            metadata.put(MetadataLabelsHolder.ScopusLabels.LINKS.getLabelName(),
                    null);
        }

        String prismUrl = entry.getPrismUrl();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_URL.getLabelName(),
                prismUrl);

        String dcIdentifier = entry.getDcIdentifier();
        metadata.put(MetadataLabelsHolder.ScopusLabels.DC_IDENTIFIER.getLabelName(),
                dcIdentifier);

        String eid = entry.getEid();
        metadata.put(MetadataLabelsHolder.ScopusLabels.EID.getLabelName(),
                eid);

        String dcTitle = entry.getDcTitle();
        metadata.put(MetadataLabelsHolder.ScopusLabels.DC_TITLE.getLabelName(),
                dcTitle);

        String prismAggregationType = entry.getPrismAggregationType();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_AGGREGATION_TYPE.getLabelName(),
                prismAggregationType);

        String citedByCount = entry.getCitedByCount();
        metadata.put(MetadataLabelsHolder.ScopusLabels.CITED_BY_COUNT.getLabelName(),
                citedByCount);

        String prismPublicationName = entry.getPrismPublicationName();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_PUBLICATION_NAME.getLabelName(),
                prismPublicationName);

        List<ScopusSearchPrismIsbn> prismIsbns = entry.getPrismIsbns();
        if (prismIsbns != null && !prismIsbns.isEmpty()) {

            String prismIsbnsAsString = objectMapper.writeValueAsString(prismIsbns);
            metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_ISBNS.getLabelName(), prismIsbnsAsString);

        } else {
            metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_ISBNS.getLabelName(), null);
        }


        String prismIssn = entry.getPrismIssn();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_ISSN.getLabelName(),
                prismIssn);

        String prismEissn = entry.getPrismEissn();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_EISSN.getLabelName(),
                prismEissn);

        String prismVolume = entry.getPrismVolume();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_VOLUME.getLabelName(),
                prismVolume);

        String prismIssueIdentifier = entry.getPrismIssueIdentifier();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_ISSUE_IDENTIFIER.getLabelName(),
                prismIssueIdentifier);

        String prismPageRange = entry.getPrismPageRange();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_PAGE_RANGE.getLabelName(),
                prismPageRange);

        String prismCoverDate = entry.getPrismCoverDate();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_COVER_DATE.getLabelName(),
                prismCoverDate);

        String prismCoverDisplayDate = entry.getPrismCoverDisplayDate();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_COVER_DISPLAY_DATE.getLabelName(),
                prismCoverDisplayDate);

        String prismDoi = entry.getPrismDoi();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PRISM_DOI.getLabelName(),
                prismDoi);

        String pii = entry.getPii();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PII.getLabelName(),
                pii);

        String pubmedId = entry.getPubmedId();
        metadata.put(MetadataLabelsHolder.ScopusLabels.PUBMED_ID.getLabelName(),
                pubmedId);

        String orcId = entry.getOrcId();
        metadata.put(MetadataLabelsHolder.ScopusLabels.ORC_ID.getLabelName(),
                orcId);

        String dcCreator = entry.getDcCreator();
        metadata.put(MetadataLabelsHolder.ScopusLabels.DC_CREATOR.getLabelName(),
                dcCreator);

        Collection<ScopusSearchAffiliation> affiliations = entry.getAffiliations();
        if (affiliations != null && !affiliations.isEmpty()) {
            String affiliationsAsString = objectMapper.writeValueAsString(affiliations);
            metadata.put(MetadataLabelsHolder.ScopusLabels.AFFILIATIONS.getLabelName(),
                    affiliationsAsString);
        } else {
            metadata.put(MetadataLabelsHolder.ScopusLabels.AFFILIATIONS.getLabelName(),
                    null);
        }

        Collection<ScopusSearchAuthor> authors = entry.getAuthors();
        if (authors != null && !authors.isEmpty()) {
            String authorsAsString = objectMapper.writeValueAsString(authors);
            metadata.put(MetadataLabelsHolder.ScopusLabels.AUTHORS.getLabelName(),
                    authorsAsString);
        } else {
            metadata.put(MetadataLabelsHolder.ScopusLabels.AUTHORS.getLabelName(),
                    null);
        }

        ScopusSearchAuthorCount authorCount = entry.getAuthorCount();
        if (authorCount != null && authorCount.getAuthorCount() != null) {
            metadata.put(MetadataLabelsHolder.ScopusLabels.AUTHOR_COUNT.getLabelName(),
                    authorCount.getAuthorCount());
        } else {
            metadata.put(MetadataLabelsHolder.ScopusLabels.AUTHOR_COUNT.getLabelName(),
                    null);
        }

        String dcDescription = entry.getDcDescription();
        metadata.put(MetadataLabelsHolder.ScopusLabels.DC_DESCRIPTION.getLabelName(),
                dcDescription);

        String authorKeywords = entry.getAuthorKeywords();
        metadata.put(MetadataLabelsHolder.ScopusLabels.AUTHOR_KEYWORDS.getLabelName(),
                authorKeywords);

        String articleNumber = entry.getArticleNumber();
        metadata.put(MetadataLabelsHolder.ScopusLabels.ARTICLE_NUMBER.getLabelName(),
                articleNumber);

        String subtype = entry.getSubtype();
        metadata.put(MetadataLabelsHolder.ScopusLabels.SUBTYPE.getLabelName(),
                subtype);

        String subtypeDescription = entry.getSubtypeDescription();
        metadata.put(MetadataLabelsHolder.ScopusLabels.SUBTYPE_DESCRIPTION.getLabelName(),
                subtypeDescription);

        String sourceId = entry.getSourceId();
        metadata.put(MetadataLabelsHolder.ScopusLabels.SOURCE_ID.getLabelName(),
                sourceId);

        String fundingAgencyAcronym = entry.getFundingAgencyAcronym();
        metadata.put(MetadataLabelsHolder.ScopusLabels.FUNDING_AGENCY_ACRONYM.getLabelName(),
                fundingAgencyAcronym);

        String fundingAgencyIdentification = entry.getFundingAgencyIdentification();
        metadata.put(MetadataLabelsHolder.ScopusLabels.FUNDING_AGENCY_IDENTIFICATION.getLabelName(),
                fundingAgencyIdentification);

        String fundingAgencyName = entry.getFundingAgencyName();
        metadata.put(MetadataLabelsHolder.ScopusLabels.FUNDING_AGENCY_NAME.getLabelName(),
                fundingAgencyName);

        String message = entry.getMessage();
        metadata.put(MetadataLabelsHolder.ScopusLabels.MESSAGE.getLabelName(),
                message);

        String openAccess = entry.getOpenAccess();
        metadata.put(MetadataLabelsHolder.ScopusLabels.OPEN_ACCESS.getLabelName(), openAccess);

        String openAccessFlag = entry.getOpenAccessFlag();
        metadata.put(MetadataLabelsHolder.ScopusLabels.OPEN_ACCESS_FLAG.getLabelName(), openAccessFlag);

        return metadata;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/test/ScopusIntegrationTest.java
package com.eresearch.repositorer.test;

import com.eresearch.repositorer.connector.ElsevierScopusConsumerConnector;
import com.eresearch.repositorer.dto.scopus.request.ElsevierScopusConsumerDto;
import com.eresearch.repositorer.dto.scopus.response.ScopusFinderImmediateResultDto;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;

@Component
public class ScopusIntegrationTest {

    @Autowired
    private ElsevierScopusConsumerConnector connector;

    @Autowired
    private ObjectMapper objectMapper;

    public void testScopusIntegration() throws RepositorerBusinessException, JsonProcessingException {

        ScopusFinderImmediateResultDto scopusImmediateResult = connector.extractInfoFromScopusWithRetries(
                ElsevierScopusConsumerDto.builder().scopusAuthorIdentifierNumber("23007591800").build(),
                UUID.randomUUID().toString());

        String resultAsAString = objectMapper.writeValueAsString(scopusImmediateResult);
        System.out.println("[SCOPUS INTEGRATION RESULT] == " + resultAsAString);
    }

}
<file_sep>/src/main/java/com/eresearch/repositorer/service/DiscardedMessageService.java
package com.eresearch.repositorer.service;

import com.eresearch.repositorer.dto.repositorer.request.DiscardedMessageSearchDto;
import com.eresearch.repositorer.dto.repositorer.request.DiscaredMessageFilenameDto;
import com.eresearch.repositorer.dto.repositorer.response.DiscardedMessageDeleteOperationResultDto;
import com.eresearch.repositorer.dto.repositorer.response.RandomEntriesResponseDto;
import com.eresearch.repositorer.dto.repositorer.response.RetrievedDiscardedMessageDto;

import java.util.Collection;

public interface DiscardedMessageService {


    Collection<RetrievedDiscardedMessageDto> find(DiscardedMessageSearchDto discardedMessageSearchDto, boolean fullFetch);

    Collection<RetrievedDiscardedMessageDto> findAll(boolean fullFetch);

    DiscardedMessageDeleteOperationResultDto deleteAll();

    DiscardedMessageDeleteOperationResultDto delete(DiscaredMessageFilenameDto discaredMessageFilenameDto);

    RandomEntriesResponseDto addRandomEntriesForTesting();

}
<file_sep>/src/main/java/com/eresearch/repositorer/domain/externalsystem/DynamicExternalSystemMessagesAwaiting.java
package com.eresearch.repositorer.domain.externalsystem;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.Instant;

/**
 * This domain class holds external system integration info, such as for how many messages aggregator should wait (release strategy) etc.
 */

@Getter
@Setter

@Document(collection = "dynamic-external-systems-messages-awaiting")
public class DynamicExternalSystemMessagesAwaiting {

    @Id
    private String id;

    private String transactionId;
    private String externalSystemName;
    private Integer noOfMessagesAwaiting;

    private Instant createdAt;

    public DynamicExternalSystemMessagesAwaiting(String transactionId, String externalSystemName, Integer noOfMessagesAwaiting, Instant createdAt) {
        this.transactionId = transactionId;
        this.externalSystemName = externalSystemName;
        this.noOfMessagesAwaiting = noOfMessagesAwaiting;
        this.createdAt = createdAt;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/results/dblp/processor/common/CommonDblpSource.java
package com.eresearch.repositorer.transformer.results.dblp.processor.common;


import com.eresearch.repositorer.dto.dblp.response.generated.*;
import com.eresearch.repositorer.dto.dblp.response.generated.Number;

import java.util.List;

public interface CommonDblpSource {

    String getKey();

    String getMdate();

    String getPubltype();

    String getReviewid();

    String getRating();

    String getCdate();

    List<Author> getAuthors();

    List<Editor> getEditors();

    List<Title> getTitles();

    List<Booktitle> getBooktitles();

    List<Pages> getPages();

    List<Year> getYears();

    List<Address> getAddresses();

    List<Journal> getJournals();

    List<Volume> getVolumes();

    List<Number> getNumbers();

    List<Month> getMonths();

    List<Url> getUrls();

    List<Ee> getEes();

    List<Cdrom> getCdroms();

    List<Cite> getCites();

    List<Publisher> getPublishers();

    List<Note> getNotes();

    List<Crossref> getCrossrefs();

    List<Isbn> getIsbns();

    List<Series> getSeries();

    List<School> getSchools();

    List<Chapter> getChapters();

    List<Publnr> getPublnrs();
}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/results/dblp/processor/common/ObjectAcceptor.java
package com.eresearch.repositorer.transformer.results.dblp.processor.common;

import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.exception.error.RepositorerError;


public interface ObjectAcceptor {

    static CommonDblpSource isAcceptedObject(Object o) {
        if (o instanceof CommonDblpSource) {
            return (CommonDblpSource) o;
        }

        throw new RepositorerBusinessException(RepositorerError.APPLICATION_NOT_IN_CORRECT_STATE,
                RepositorerError.APPLICATION_NOT_IN_CORRECT_STATE.getMessage(),
                ObjectAcceptor.class.getName());
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/sciencedirect/response/SciDirImmediateResultDto.java
package com.eresearch.repositorer.dto.sciencedirect.response;

import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@ToString
@NoArgsConstructor
public class SciDirImmediateResultDto {

    private String message;
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/elsevierauthor/response/AuthorFinderImmediateResultDto.java
package com.eresearch.repositorer.dto.elsevierauthor.response;

import lombok.ToString;

@ToString
public class AuthorFinderImmediateResultDto {

    private String message;

    public AuthorFinderImmediateResultDto(String message) {
        this.message = message;
    }

    public AuthorFinderImmediateResultDto() {
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/sciencedirect/response/ScienceDirectSearchViewEntry.java
package com.eresearch.repositorer.dto.sciencedirect.response;


import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import java.util.Collection;

@EqualsAndHashCode(of = {"dcIdentifier", "dcTitle"})
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ScienceDirectSearchViewEntry {

    @JsonProperty("@force-array")
    private String forceArray;

    @JsonProperty("error")
    private String error;

    @JsonProperty("@_fa")
    private String fa;

    @JsonProperty("load-date")
    private String loadDate;

    @JsonProperty("link")
    private Collection<ScienceDirectSearchViewLink> links;

    @JsonProperty("prism:url")
    private String prismUrl;

    @JsonProperty("dc:identifier")
    private String dcIdentifier;

    @JsonProperty("openaccess")
    private String openAccess;

    @JsonProperty("openaccessFlag")
    private String openAccessFlag;

    @JsonProperty("dc:title")
    private String dcTitle;

    @JsonProperty("prism:publicationName")
    private String prismPublicationName;

    @JsonProperty("prism:isbn")
    private String prismIsbn;

    @JsonProperty("prism:issn")
    private String prismIssn;

    @JsonProperty("prism:volume")
    private String prismVolume;

    @JsonProperty("prism:issueIdentifier")
    private String prismIssueIdentifier;

    @JsonProperty("prism:issueName")
    private String prismIssueName;

    @JsonProperty("prism:edition")
    private String prismEdition;

    @JsonProperty("prism:startingPage")
    private String prismStartingPage;

    @JsonProperty("prism:endingPage")
    private String prismEndingPage;

    @JsonProperty("prism:coverDate")
    private String prismCoverDate;

    @JsonProperty("prism:coverDisplayDate")
    private String coverDisplayDate;

    @JsonProperty("dc:creator")
    private String dcCreator;

    @JsonProperty("authors")
    private ScienceDirectSearchViewEntryAuthors authors;

    @JsonProperty("prism:doi")
    private String prismDoi;

    @JsonProperty("pii")
    private String pii;

    @JsonProperty("pubType")
    private String pubType;

    @JsonProperty("prism:teaser")
    private String prismTeaser;

    @JsonProperty("dc:description")
    private String dcDescription;

    @JsonProperty("authkeywords")
    private String authKeywords;

    @JsonProperty("prism:aggregationType")
    private String prismAggregationType;

    @JsonProperty("prism:copyright")
    private String prismCopyright;

    @JsonProperty("scopus-id")
    private String scopusId;

    @JsonProperty("eid")
    private String eid;

    @JsonProperty("scopus-eid")
    private String scopusEid;

    @JsonProperty("pubmed-id")
    private String pubmedId;

    @JsonProperty("openaccessArticle")
    private String openAccessArticle;

    @JsonProperty("openArchiveArticle")
    private String openArchiveArticle;

    @JsonProperty("openaccessUserLicense")
    private String openAccessUserLicense;
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/scopus/request/ElsevierScopusConsumerDto.java
package com.eresearch.repositorer.dto.scopus.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class ElsevierScopusConsumerDto {

    @JsonProperty("au-id")
    private String scopusAuthorIdentifierNumber;

}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/repositorer/request/RepositorerFindDtos.java
package com.eresearch.repositorer.dto.repositorer.request;


import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import java.util.List;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class RepositorerFindDtos {

    @JsonProperty("names")
    private List<RepositorerFindDto> repositorerFindDtos;

}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/sciencedirect/response/ScienceDirectSearchViewEntryAuthor.java
package com.eresearch.repositorer.dto.sciencedirect.response;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class ScienceDirectSearchViewEntryAuthor {

    @JsonProperty("$")
    private String name;


    /*
        Note, in order to handle the following case:

        1)
        "authors": {
          "author": "<NAME>"
        }


        2)
        "authors": {
          "author": [
            {
              "$": "<NAME>"
            },
            {
              "$": "<NAME>"
            },
            {
              "$": "<NAME>"
            }
          ]
        }


     */
    @JsonCreator
    public ScienceDirectSearchViewEntryAuthor(String input) {
        if (input.contains("$")) {
            String s = input.split(":")[1];
            this.setName(s);
        } else {
            this.setName(input);
        }
    }

}
<file_sep>/init.sql


CREATE DATABASE IF NOT EXISTS scidir_consumer;

CREATE DATABASE IF NOT EXISTS scopus_consumer;

CREATE DATABASE IF NOT EXISTS dblp_consumer;

CREATE DATABASE IF NOT EXISTS author_matcher;

CREATE DATABASE IF NOT EXISTS author_finder_consumer;




<file_sep>/src/main/java/com/eresearch/repositorer/application/event/listener/BaseApplicationEventListener.java
package com.eresearch.repositorer.application.event.listener;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

import lombok.extern.log4j.Log4j;

/**
 * This is a basic application listener which logs all application events which
 * take place. It is more used for investigation & logging purposes.
 * 
 * Listens for every produced event.
 * 
 * @author chriniko
 *
 */
@Log4j
public class BaseApplicationEventListener implements ApplicationListener<ApplicationEvent> {

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		// Note: enrich this functionality based on your needs...
		log.info("#### > " + event.getClass().getCanonicalName());
	}

}
<file_sep>/src/main/java/com/eresearch/repositorer/activator/externalsystem/ScienceDirectConsumptionActivator.java
package com.eresearch.repositorer.activator.externalsystem;

import com.eresearch.repositorer.connector.ScienceDirectConsumerConnector;
import com.eresearch.repositorer.dto.repositorer.request.RepositorerFindDto;
import com.eresearch.repositorer.dto.sciencedirect.request.ElsevierScienceDirectConsumerDto;
import com.eresearch.repositorer.dto.sciencedirect.response.SciDirImmediateResultDto;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.transformer.dto.RepositorerFindDtos;
import lombok.extern.log4j.Log4j;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

@Log4j
@Component
public class ScienceDirectConsumptionActivator implements ConsumptionActivator {

    @Autowired
    private ScienceDirectConsumerConnector scienceDirectConsumerConnector;

    @Autowired
    private ModelMapper modelMapper;

    @Override
    public void send(final Message<?> message) {
        try {
            log.info("ScienceDirectConsumptionActivator --> " + Thread.currentThread().getName());

            final RepositorerFindDtos repositorerFindDtos = (RepositorerFindDtos) message.getPayload();
            final String transactionId = (String) message.getHeaders().get(TRANSACTION_ID);

            for (RepositorerFindDto repositorerFindDto : repositorerFindDtos.getDtos()) {

                final ElsevierScienceDirectConsumerDto scienceDirectConsumerDto
                        = modelMapper.map(repositorerFindDto, ElsevierScienceDirectConsumerDto.class);


                final SciDirImmediateResultDto immediateResult
                        = scienceDirectConsumerConnector.extractInfoFromScienceDirectWithRetries(scienceDirectConsumerDto, transactionId);

                log.info("ScienceDirectConsumptionEventSender#send --- immediateResult = " + immediateResult);


            }
        } catch (RepositorerBusinessException e) {

            log.error("ScienceDirectConsumptionEventSender#send --- error occurred.", e);
            throw e;
        }
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/dblp/response/DblpAuthor.java
package com.eresearch.repositorer.dto.dblp.response;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
import java.util.Objects;

@EqualsAndHashCode(of = {"authorName", "urlpt"})
@Getter
@Setter
@XmlAccessorType(XmlAccessType.FIELD)
public class DblpAuthor {

    @XmlValue
    private String authorName;

    @XmlAttribute(name = "urlpt")
    private String urlpt;

    public DblpAuthor(String value) {
        String[] splittedInfo = value.split("#");

        authorName = splittedInfo[0].split("=")[1];
        urlpt = splittedInfo[1].split("=")[1];
    }

    @Override
    public String toString() {
        return "authorName=" + authorName + "#urlpt=" + urlpt;
    }
}
<file_sep>/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.eresearch.repositorer</groupId>
    <artifactId>eresearch-repositorer</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <name>eresearch-repositorer</name>
    <description>This is the eresearch repositorer service.</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>

        <skipUTs>false</skipUTs>
        <skipITs>false</skipITs>
    </properties>

    <dependencies>

        <!-- Start of Spring Dependencies -->
        <dependency> <!-- is used -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- start of declaring dependencies for undertow server -->
        <dependency> <!-- is used -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-core</artifactId>
            <version>1.4.12.Final</version>
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-servlet</artifactId>
            <version>1.4.12.Final</version>
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-websockets-jsr</artifactId>
            <version>1.4.12.Final</version>
        </dependency>
        <!-- end of declaring dependencies for undertow server -->

        <dependency> <!-- is used -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency> <!-- is used (in future) -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-integration</artifactId>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-jms</artifactId>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        <!-- End of Spring Dependencies -->


        <!-- Start of different dependencies -->

        <!-- Start: in order to use spock framework -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.spockframework</groupId>
            <artifactId>spock-core</artifactId>
            <version>1.1-groovy-2.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.athaydes</groupId>
            <artifactId>spock-reports</artifactId>
            <version>1.3.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.7.5</version>
        </dependency>
        <!-- End: in order to use spock framework -->


        <dependency> <!-- is used -->
            <groupId>javax.jms</groupId>
            <artifactId>javax.jms-api</artifactId>
            <version>2.0.1</version>
            <scope>compile</scope>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-broker</artifactId>
            <version>5.14.3</version>
            <scope>compile</scope>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.7.Final</version>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>0.7.5</version>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>22.0</version>
        </dependency>

        <dependency> <!-- is used -->
            <groupId>net.jodah</groupId>
            <artifactId>failsafe</artifactId>
            <version>1.0.4</version>
        </dependency>

        <dependency>
            <groupId>com.github.tomakehurst</groupId>
            <artifactId>wiremock-jre8</artifactId>
            <version>2.22.0</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.awaitility</groupId>
            <artifactId>awaitility</artifactId>
            <version>3.1.6</version>
            <scope>test</scope>
        </dependency>
        <!-- End of different dependencies -->

    </dependencies>

    <build>
        <plugins>

            <!-- Boot Maven Plugin -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <classifier>boot</classifier>
                </configuration>
            </plugin>

            <!-- Groovy Maven Plugin -->
            <plugin>
                <groupId>org.codehaus.gmavenplus</groupId>
                <artifactId>gmavenplus-plugin</artifactId>
                <version>1.5</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>addTestSources</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <!-- Maven Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <!-- Maven Surefire Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
                <configuration>
                    <skipTests>${skipUTs}</skipTests>
                    <includes>
                        <include>**/*Test.java</include>
                        <include>**/*Spec.java</include>
                    </includes>
                </configuration>
            </plugin>

            <!-- Failsafe Plugin -->
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.21.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                        <configuration>
                            <skipTests>${skipITs}</skipTests>
                            <!-- Sets the VM argument line used when unit tests are run. -->
                            <!--<argLine>${failsafeArgLine}</argLine>-->
                            <includes>
                                <include>**/*IT</include>
                            </includes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

    <!-- Start in order to create html test reports for spock framework -->
    <repositories>
        <repository>
            <id>jcenter</id>
            <name>JCenter Repo</name>
            <url>http://jcenter.bintray.com</url>
        </repository>
    </repositories>


</project>
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/dto/ElsevierAuthorResultsTransformerDto.java
package com.eresearch.repositorer.transformer.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.Collection;

@Setter
@Getter
@AllArgsConstructor
@ToString
public class ElsevierAuthorResultsTransformerDto {
    private Collection<String> elsevierAuthorIds;
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/elsevierauthor/response/AuthorSearchViewLink.java
package com.eresearch.repositorer.dto.elsevierauthor.response;

import com.fasterxml.jackson.annotation.JsonProperty;

public class AuthorSearchViewLink {

    @JsonProperty("@_fa")
    private String fa;

    @JsonProperty("@href")
    private String href;

    @JsonProperty("@ref")
    private String ref;

    @JsonProperty("@type")
    private String type;

    public AuthorSearchViewLink() {
    }

    public AuthorSearchViewLink(String fa, String href, String ref, String type) {
        this.fa = fa;
        this.href = href;
        this.ref = ref;
        this.type = type;
    }

    public String getFa() {
        return fa;
    }

    public void setFa(String fa) {
        this.fa = fa;
    }

    public String getHref() {
        return href;
    }

    public void setHref(String href) {
        this.href = href;
    }

    public String getRef() {
        return ref;
    }

    public void setRef(String ref) {
        this.ref = ref;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/event/BatchExtractorWakeUpEvent.java
package com.eresearch.repositorer.event;

import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class BatchExtractorWakeUpEvent {

    private boolean continueWork;
}
<file_sep>/src/main/java/com/eresearch/repositorer/connector/DblpConsumerConnector.java
package com.eresearch.repositorer.connector;

import com.eresearch.repositorer.dto.dblp.request.DblpConsumerDto;
import com.eresearch.repositorer.dto.dblp.response.DblpImmediateResultDto;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.exception.error.RepositorerError;
import lombok.extern.log4j.Log4j;
import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Log4j
@Component
public class DblpConsumerConnector implements Connector {

    private static final String TRANSACTION_ID = "Transaction-Id";

    @Value("${dblp.consumer.url}")
    private String dblpConsumerUrl;

    @Autowired
    @Qualifier("repositorerRestTemplate")
    private RestTemplate restTemplate;

    @Autowired
    @Qualifier("basicRetryPolicyForConnectors")
    private RetryPolicy basicRetryPolicyForConnectors;

    private DblpImmediateResultDto extractInfoFromDblp(DblpConsumerDto dblpConsumerDto, String transactionId) {

        final String url = dblpConsumerUrl;

        final HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(TRANSACTION_ID, transactionId);

        final HttpEntity<DblpConsumerDto> httpEntity = new HttpEntity<>(dblpConsumerDto, httpHeaders);

        final ResponseEntity<DblpImmediateResultDto> responseEntity = restTemplate.exchange(
                url,
                HttpMethod.POST,
                httpEntity,
                DblpImmediateResultDto.class);

        return responseEntity.getBody();
    }

    public DblpImmediateResultDto extractInfoFromDblpWithRetries(DblpConsumerDto dblpConsumerDto, String transactionId) {

        return Failsafe
                .with(basicRetryPolicyForConnectors)
                .withFallback(() -> {
                    throw new RepositorerBusinessException(RepositorerError.CONNECTOR_CONNECTION_ERROR,
                            RepositorerError.CONNECTOR_CONNECTION_ERROR.getMessage(),
                            this.getClass().getName());
                })
                .onSuccess(s -> log.info("DblpConsumerConnector#extractInfoFromDblpWithRetries, completed successfully!"))
                .onFailure(error -> log.error("DblpConsumerConnector#extractInfoFromDblpWithRetries, failed!"))
                .onAbort(error -> log.error("DblpConsumerConnector#extractInfoFromDblpWithRetries, aborted!"))
                .get((context) -> {

                    long startTime = context.getStartTime().toMillis();
                    long elapsedTime = context.getElapsedTime().toMillis();
                    int executions = context.getExecutions();

                    String message = String.format("DblpConsumerConnector#extractInfoFromDblpWithRetries, retrying...with params: " +
                            "[executions=%s, startTime(ms)=%s, elapsedTime(ms)=%s, dblpConsumerDto=%s, transactionId=%s]", executions, startTime, elapsedTime, dblpConsumerDto, transactionId);

                    log.warn(message);

                    return extractInfoFromDblp(dblpConsumerDto, transactionId);
                });
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/application/event/listener/ApplicationReadyEventListener.java
package com.eresearch.repositorer.application.event.listener;

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

import lombok.extern.log4j.Log4j;

/**
 * This listener listens for the event which is produced when the application is
 * ready.
 * 
 * @author chriniko
 *
 */
@Log4j
public class ApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {

	@Override
	public void onApplicationEvent(ApplicationReadyEvent event) {
		// Note: add functionality according to your needs...
		log.info("~~~Application is Ready~~~");
	}

}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/results/metadata/ProcessMetadataPredicate.java
package com.eresearch.repositorer.transformer.results.metadata;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ProcessMetadataPredicate {

    @Value("${collect.metadata.info}")
    private String doMetadataProcessing;

    public boolean doMetadataProcessing() {
        return Boolean.valueOf(doMetadataProcessing);
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/AuthorEntriesMatchingTransformer.java
package com.eresearch.repositorer.transformer;

import com.eresearch.repositorer.connector.AuthorMatcherConnector;
import com.eresearch.repositorer.domain.record.Author;
import com.eresearch.repositorer.domain.record.Entry;
import com.eresearch.repositorer.domain.record.Record;
import com.eresearch.repositorer.dto.authormatcher.request.AuthorComparisonDto;
import com.eresearch.repositorer.dto.authormatcher.request.AuthorNameDto;
import com.eresearch.repositorer.dto.authormatcher.response.AuthorMatcherResultsDto;
import com.eresearch.repositorer.dto.authormatcher.response.StringMetricAlgorithm;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.service.CaptureRequestResponseService;
import com.eresearch.repositorer.transformer.dto.NameDto;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import lombok.extern.log4j.Log4j;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

@Log4j
@Component
public class AuthorEntriesMatchingTransformer {

    private static final String TRANSACTION_ID = "Transaction-Id";

    @Value("${author.entries.matching.string.metric.algorithm}")
    private String stringMetricAlgorithmToUse;

    private StringMetricAlgorithm stringMetricAlgorithmToUseEnum;

    @Value("${author.entries.matching.success.threshold}")
    private String successThreshold;

    private double successThresholdDouble;

    @Value("${apply.record.entries.filtering.with.author.matching}")
    private String applyRecordEntriesFilteringWithAuthorMatching;

    @Value("${author.entries.matching.multithread.approach}")
    private String isMultithreadApproach;

    @Value("${capture-service.enabled}")
    private boolean captureServiceEnabled;

    @Autowired
    private CaptureRequestResponseService captureRequestResponseService;

    @Autowired
    @Qualifier("authorEntriesMatchingExecutor")
    private ExecutorService authorEntriesMatchingExecutor;

    @Autowired
    private ModelMapper modelMapper;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private AuthorMatcherConnector authorMatcherConnector;

    @Value("${author.entries.matching.size.splitting.threshold}")
    private String authorEntriesMatchingSizeSplitting;

    private Integer authorEntriesMatchingSizeSplittingInteger;

    @Value("${author.entries.matching.splitting.list.size}")
    private String authorEntriesMatchingSplittingListSize;

    private Integer authorEntriesMatchingSplittingListSizeInteger;

    @PostConstruct
    public void init() {
        successThresholdDouble = Double.valueOf(successThreshold);

        stringMetricAlgorithmToUseEnum = StringMetricAlgorithm.valueOf(stringMetricAlgorithmToUse);

        authorEntriesMatchingSizeSplittingInteger = Integer.valueOf(authorEntriesMatchingSizeSplitting);

        authorEntriesMatchingSplittingListSizeInteger = Integer.valueOf(authorEntriesMatchingSplittingListSize);
    }

    public Message<?> transform(Message<?> message) {
        log.info("AuthorEntriesMatchingTransformer --> " + Thread.currentThread().getName());

        Record record = (Record) message.getPayload();
        String transactionId = (String) message.getHeaders().get(TRANSACTION_ID);

        if (Boolean.valueOf(applyRecordEntriesFilteringWithAuthorMatching)) {
            doAuthorMatching(record);
        }

        return MessageBuilder
                .withPayload(record)
                .setHeader(TRANSACTION_ID, transactionId)
                .build();
    }

    private void doAuthorMatching(Record record) {
        final List<NameDto> nameDtosToCompareAgainst = getNameDtos(record);

        if (record.getEntries() == null || record.getEntries().isEmpty()) {
            return;
        }

        Collection<Entry> entries = record.getEntries();

        final Collection<Entry> filteredEntries = getEntriesListToPopulate();

        if (Boolean.valueOf(isMultithreadApproach) && entries.size() >= authorEntriesMatchingSizeSplittingInteger) {

            List<List<Entry>> splittedEntries = Lists.partition(new ArrayList<>(entries), authorEntriesMatchingSplittingListSizeInteger);

            List<CompletableFuture<Void>> workers = splittedEntries
                    .stream()
                    .map(splittedEntry -> CompletableFuture.runAsync(
                            () -> singleThreadApproach(splittedEntry, nameDtosToCompareAgainst, filteredEntries),
                            authorEntriesMatchingExecutor
                            )
                    )
                    .collect(Collectors.toList());

            workers.forEach(CompletableFuture::join);

        } else {

            singleThreadApproach(entries, nameDtosToCompareAgainst, filteredEntries);

        }

        record.setEntries(filteredEntries);
    }

    private Collection<Entry> getEntriesListToPopulate() {
        return Boolean.valueOf(isMultithreadApproach) ? Collections.synchronizedList(new LinkedList<>()) : new LinkedList<>();
    }

    private void singleThreadApproach(Collection<Entry> entries, List<NameDto> nameDtosToCompareAgainst, Collection<Entry> filteredEntries) {
        boolean keepEntry;
        for (Entry entry : entries) {

            Set<Author> entryAuthors = entry.getAuthors();
            if (entryAuthors == null || entryAuthors.isEmpty()) {
                continue;
            }

            keepEntry = false;

            for (NameDto nameDtoToCompareAgainst : nameDtosToCompareAgainst) {
                for (Author author : entryAuthors) {
                    keepEntry = compare(keepEntry, nameDtoToCompareAgainst, author);
                }
            }

            if (keepEntry) {
                filteredEntries.add(entry);
            }
        }
    }

    private List<NameDto> getNameDtos(Record record) {
        final List<NameDto> nameDtosToCompareAgainst = new LinkedList<>();

        nameDtosToCompareAgainst.add(new NameDto(record.getFirstname(), record.getInitials(), record.getLastname()));

        if (record.getNameVariants() != null && !record.getNameVariants().isEmpty()) {
            nameDtosToCompareAgainst.addAll(record
                    .getNameVariants()
                    .stream()
                    .map(nameVariant -> modelMapper.map(nameVariant, NameDto.class))
                    .collect(Collectors.toCollection(LinkedList::new)));
        }

        return nameDtosToCompareAgainst;
    }

    private boolean compare(boolean keepEntry,
                            NameDto nameDtoToCompareAgainst /* Note: entry's author */,
                            Author author) {

        AuthorComparisonDto authorComparisonDto = new AuthorComparisonDto(
                modelMapper.map(nameDtoToCompareAgainst, AuthorNameDto.class),
                modelMapper.map(author, AuthorNameDto.class)
        );

        try {
            AuthorMatcherResultsDto authorMatcherResultsDto
                    = authorMatcherConnector.performAuthorMatchingWithRetries(authorComparisonDto);


            if (captureServiceEnabled) {
                try {
                    String filename = String.join("_",
                            Optional.ofNullable(author.getFirstname()).orElse("!"),
                            Optional.ofNullable(author.getInitials()).orElse("!"),
                            Optional.ofNullable(author.getSurname()).orElse("!"),
                            "$",
                            Optional.ofNullable(nameDtoToCompareAgainst.getFirstname()).orElse("!"),
                            Optional.ofNullable(nameDtoToCompareAgainst.getInitials()).orElse("!"),
                            Optional.ofNullable(nameDtoToCompareAgainst.getSurname()).orElse("!")
                    );

                    String requestContents = objectMapper.writeValueAsString(authorComparisonDto);
                    captureRequestResponseService.log(filename + "_REQUEST", requestContents, "json");


                    String responseContents = objectMapper.writeValueAsString(authorMatcherResultsDto);
                    captureRequestResponseService.log(filename + "_RESPONSE", responseContents, "json");

                } catch (JsonProcessingException error) {
                    log.error("error occurred during serialization of contents to sent them to capture service", error);
                }
            }


            Double comparisonResultFloor = authorMatcherResultsDto
                    .getResults()
                    .get(stringMetricAlgorithmToUseEnum)
                    .getComparisonResultFloor();

            if (comparisonResultFloor >= successThresholdDouble) {
                keepEntry = true;
            }

        } catch (RepositorerBusinessException e) {
            log.error("AuthorEntriesMatchingTransformer#transform --- error occurred.", e);
            keepEntry = true; //Note: if also retries failed and we are here, we keep it and move on.
        }

        return keepEntry;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/repositorer/request/RecordSearchDto.java
package com.eresearch.repositorer.dto.repositorer.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class RecordSearchDto {

    @JsonProperty("firstname")
    private String firstname;

    @JsonProperty("initials")
    private String initials;

    @JsonProperty("surname")
    private String surname;
}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/results/dblp/processor/DblpInproceedingsProcessorBasic.java
package com.eresearch.repositorer.transformer.results.dblp.processor;

import com.eresearch.repositorer.domain.record.Entry;
import com.eresearch.repositorer.dto.dblp.response.DblpAuthor;
import com.eresearch.repositorer.dto.dblp.response.generated.Dblp;
import com.eresearch.repositorer.dto.dblp.response.generated.Inproceedings;
import com.eresearch.repositorer.transformer.results.dblp.processor.common.EntryCreator;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.LinkedList;
import java.util.List;

@Component
public class DblpInproceedingsProcessorBasic implements DblpProcessor {

    private final EntryCreator entryCreator;

    @Autowired
    public DblpInproceedingsProcessorBasic(EntryCreator entryCreator) {
        this.entryCreator = entryCreator;
    }

    @Override
    public void doProcessing(List<Entry> entries, Dblp dblp, DblpAuthor dblpAuthor) throws JsonProcessingException {

        List<Inproceedings> inproceedings = dblp.getInproceedings();

        if (inproceedings != null && !inproceedings.isEmpty()) {

            final List<Entry> collectedEntries = new LinkedList<>();

            for (Inproceedings inproceeding : inproceedings) {

                entryCreator.create(dblpAuthor, collectedEntries, inproceeding);

            }

            entries.addAll(collectedEntries);
        }
    }

    @Override
    public boolean allowProcessing() {
        return true;
    }
}

<file_sep>/src/main/java/com/eresearch/repositorer/domain/retry/RetryEntry.java
package com.eresearch.repositorer.domain.retry;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.Instant;


/**
 * This domain class holds info for failed author extraction attempt(s), in order to perform retries on this/them.
 */

@Getter
@Setter
@NoArgsConstructor
@ToString(of = {"firstname", "initials", "surname", "createdAt"})

@Document(collection = "retry-entries")
public class RetryEntry implements Comparable<RetryEntry> {

    @Id
    private String id;

    private String firstname;
    private String initials;
    private String surname;

    private Instant createdAt;

    public RetryEntry(String firstname, String initials, String surname, Instant createdAt) {
        this.firstname = firstname;
        this.initials = initials;
        this.surname = surname;
        this.createdAt = createdAt;
    }

    @Override
    public int compareTo(RetryEntry other) {
        return this.getCreatedAt().compareTo(other.getCreatedAt());
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/application/event/listener/ApplicationEnvironmentPreparedEventListener.java
package com.eresearch.repositorer.application.event.listener;

import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;

import lombok.extern.log4j.Log4j;

/**
 * This listener listens for the event which is produced when the environment is
 * known.
 * 
 * @author chriniko
 *
 */
@Log4j
public class ApplicationEnvironmentPreparedEventListener
		implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

	@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		// NOTE: add functionality according to your needs...
		log.info("~~~Application Environment Initialized(known)~~~");
	}

}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/authormatcher/request/AuthorComparisonDto.java
package com.eresearch.repositorer.dto.authormatcher.request;


import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
public class AuthorComparisonDto {

    @JsonProperty("first-author-name")
    private AuthorNameDto firstAuthorName;

    @JsonProperty("second-author-name")
    private AuthorNameDto secondAuthorName;

}
<file_sep>/src/main/java/com/eresearch/repositorer/connector/AuthorMatcherConnector.java
package com.eresearch.repositorer.connector;

import com.eresearch.repositorer.dto.authormatcher.request.AuthorComparisonDto;
import com.eresearch.repositorer.dto.authormatcher.response.AuthorMatcherResultsDto;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.exception.error.RepositorerError;
import lombok.extern.log4j.Log4j;
import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.net.URI;

@Log4j
@Component
public class AuthorMatcherConnector implements Connector {

    @Value("${author.matcher.url}")
    private String authorMatcherUrl;

    @Autowired
    @Qualifier("repositorerRestTemplate")
    private RestTemplate restTemplate;

    @Autowired
    @Qualifier("basicRetryPolicyForConnectors")
    private RetryPolicy basicRetryPolicyForConnectors;

    private AuthorMatcherResultsDto performAuthorMatching(AuthorComparisonDto authorComparisonDto) {

        final String url = authorMatcherUrl;

        final RequestEntity<AuthorComparisonDto> requestEntity = new RequestEntity<>(
                authorComparisonDto,
                HttpMethod.POST,
                URI.create(url));

        final ResponseEntity<AuthorMatcherResultsDto> responseEntity = restTemplate.exchange(
                requestEntity,
                AuthorMatcherResultsDto.class);

        return responseEntity.getBody();
    }

    public AuthorMatcherResultsDto performAuthorMatchingWithRetries(AuthorComparisonDto authorComparisonDto) {
        return Failsafe
                .with(basicRetryPolicyForConnectors)
                .withFallback(() -> {
                    throw new RepositorerBusinessException(RepositorerError.CONNECTOR_CONNECTION_ERROR,
                            RepositorerError.CONNECTOR_CONNECTION_ERROR.getMessage(),
                            this.getClass().getName());
                })
                .onSuccess(s -> log.info("AuthorMatcherConnector#performAuthorMatchingWithRetries, completed successfully!"))
                .onFailure(error -> log.error("AuthorMatcherConnector#performAuthorMatchingWithRetries, failed!"))
                .onAbort(error -> log.error("AuthorMatcherConnector#performAuthorMatchingWithRetries, aborted!"))
                .get((context) -> {

                    long startTime = context.getStartTime().toMillis();
                    long elapsedTime = context.getElapsedTime().toMillis();
                    int executions = context.getExecutions();

                    String message = String.format("AuthorMatcherConnector#performAuthorMatchingWithRetries, retrying...with params: " +
                            "[executions=%s, startTime(ms)=%s, elapsedTime(ms)=%s, authorComparisonDto=%s]", executions, startTime, elapsedTime, authorComparisonDto);

                    log.warn(message);

                    return performAuthorMatching(authorComparisonDto);
                });
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/exception/business/RepositorerBusinessException.java
package com.eresearch.repositorer.exception.business;

import com.eresearch.repositorer.exception.error.RepositorerError;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public class RepositorerBusinessException extends RuntimeException {

    private final RepositorerError repositorerError;
    private final String crashedComponentName;

    public RepositorerBusinessException(RepositorerError repositorerError, String message, Throwable cause, String crashedComponentName) {
        super(message, cause);
        this.repositorerError = repositorerError;
        this.crashedComponentName = crashedComponentName;
    }

    public RepositorerBusinessException(RepositorerError repositorerError, String message, String crashedComponentName) {
        super(message);
        this.repositorerError = repositorerError;
        this.crashedComponentName = crashedComponentName;
    }

    public RepositorerBusinessException(RepositorerError repositorerError, String message) {
        this(repositorerError, message, null);
    }

    public RepositorerError getRepositorerError() {
        return repositorerError;
    }

    public String getCrashedComponentName() {
        return crashedComponentName;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/error/scheduler/RecoveryEntryCreatorScheduler.java
package com.eresearch.repositorer.error.scheduler;

import com.eresearch.repositorer.domain.lookup.NameLookup;
import com.eresearch.repositorer.domain.lookup.NameLookupStatus;
import com.eresearch.repositorer.domain.retry.RetryEntry;
import com.eresearch.repositorer.repository.DynamicExternalSystemMessagesAwaitingRepository;
import com.eresearch.repositorer.repository.NamesLookupRepository;
import com.eresearch.repositorer.repository.RetryEntryRepository;
import com.google.common.collect.Sets;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Component
@Log4j
public class RecoveryEntryCreatorScheduler {

    @Autowired
    private NamesLookupRepository namesLookupRepository;

    @Autowired
    private DynamicExternalSystemMessagesAwaitingRepository externalSystemMessagesAwaitingRepository;

    @Autowired
    private RetryEntryRepository retryEntryRepository;

    @Autowired
    private Clock clock;

    @Value("${healer.scheduler.enabled}")
    private String isHealerSchedulerEnabled;

    @Value("${entry.creator.scheduler.minutes.waiting.before.recovery.mode}")
    private String minutesWaitingBeforeEnterInRecoverModeForDbConsistencyStr;

    private long minutesWaitingBeforeEnterInRecoverModeForDbConsistency;

    private Set<NameLookupStatus> nameLookupStatusesToNotProcess;

    @PostConstruct
    public void init() {
        minutesWaitingBeforeEnterInRecoverModeForDbConsistency = Long.parseLong(minutesWaitingBeforeEnterInRecoverModeForDbConsistencyStr);

        nameLookupStatusesToNotProcess = Sets.newHashSet(NameLookupStatus.COMPLETED, NameLookupStatus.TIMED_OUT);
    }

    /*
        NOTE:
        crone expression => second, minute, hour, day of month, month, day(s) of week


        ADDITIONAL NOTES:
        1) (*) means match any
        2) * / X means 'every X'
        3) ? ("no specific value") - useful when you need to specify something in one
        of the two fields in which the character is allowed,
        but not the other. For example, if I want my trigger to fire on a particular day
        of the month (say, the 10th), but don't care what day of the week that happens to be,
        I would put "10" in the day-of-month field, and "?" in the day-of-week field.

            +-------------------- second (0 - 59)
            |  +----------------- minute (0 - 59)
            |  |  +-------------- hour (0 - 23)
            |  |  |  +----------- day of month (1 - 31)
            |  |  |  |  +-------- month (1 - 12)
            |  |  |  |  |  +----- day of week (0 - 6) (Sunday=0 or 7)
            |  |  |  |  |  |  +-- year [optional]
            |  |  |  |  |  |  |
            *  *  *  *  *  *  * command to be executed

         */
    @Scheduled(cron = "0 0/5 * * * *") //every five minutes.
    public void doWork() {

        List<NameLookup> nameLookups = namesLookupRepository.findAll();
        if (nameLookups == null || nameLookups.isEmpty()) {
            return;
        }

        //keep records which are only in PENDING status....
        nameLookups = getNameLookupsWithPendingStatus(nameLookups);

        log.info("RecoveryEntryCreatorScheduler#doWork --- fired, nameLookups = [" + nameLookups + "]");

        final HashSet<NameLookup> uniqueNameLookups = Sets.newHashSet(nameLookups);
        for (NameLookup uniqueNameLookup : uniqueNameLookups) {

            Instant createdAt = uniqueNameLookup.getCreatedAt();

            ZonedDateTime zonedCreatedAt = ZonedDateTime.ofInstant(createdAt, clock.getZone());
            ZonedDateTime now = ZonedDateTime.now(clock);

            final long minutesPassed = Duration.between(zonedCreatedAt.toInstant(), now.toInstant()).toMinutes();
            final boolean shouldEnterIntoRecoverModeForDbConsistency = minutesPassed >= minutesWaitingBeforeEnterInRecoverModeForDbConsistency;

            if (shouldEnterIntoRecoverModeForDbConsistency) {
                recoverModeForDbConsistency(uniqueNameLookup, minutesPassed);
            }

        }
    }

    private List<NameLookup> getNameLookupsWithPendingStatus(List<NameLookup> nameLookups) {
        return nameLookups
                .stream()
                .filter(nameLookup -> !nameLookupStatusesToNotProcess.contains(nameLookup.getNameLookupStatus()))
                .collect(Collectors.toList());
    }

    private void recoverModeForDbConsistency(NameLookup uniqueNameLookup, long minutesPassed) {

        //Note: in order to be sure and also remove duplicated entries...
        String firstname = uniqueNameLookup.getFirstname();
        String initials = uniqueNameLookup.getInitials();
        String surname = uniqueNameLookup.getSurname();

        final List<NameLookup> nameLookupsToRemove = namesLookupRepository.findAllByFirstnameEqualsAndInitialsEqualsAndSurnameEquals(
                firstname,
                initials,
                surname);

        for (NameLookup nameLookupToRemove : nameLookupsToRemove) {

            String txId = nameLookupToRemove.getTransactionId();

            //Note: update collection if needed -> names-lookup
            NameLookup fetchedNameLookupToUpdate = namesLookupRepository.findNamesLookupByTransactionIdEquals(txId);
            if (fetchedNameLookupToUpdate.getNameLookupStatus() == NameLookupStatus.PENDING) {
                fetchedNameLookupToUpdate.setNameLookupStatus(NameLookupStatus.TIMED_OUT);
                namesLookupRepository.save(fetchedNameLookupToUpdate);
            }

            //Note: clean collection -> dynamic-external-systems-messages-awaiting
            externalSystemMessagesAwaitingRepository.deleteByTransactionIdEquals(txId);
        }

        //Note: create a new entry (if selected via properties), so another scheduler (healer) retries extraction process again in the future.
        boolean recordNotExists = retryEntryRepository.findRetryEntryByFirstnameEqualsAndInitialsEqualsAndSurnameEquals(firstname, initials, surname) == null;

        if (recordNotExists && Boolean.valueOf(isHealerSchedulerEnabled)) {
            log.info("RecoveryScheduler#doRecovery --- going to create a recovery entry, nameLookup = [ " + uniqueNameLookup + "], minutesPassed = [" + minutesPassed + "]");
            retryEntryRepository.insert(new RetryEntry(firstname, initials, surname, Instant.now(clock)));
        }
    }

}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/repositorer/request/ErrorReportSearchDto.java
package com.eresearch.repositorer.dto.repositorer.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.LocalDate;
import java.time.LocalTime;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ErrorReportSearchDto {

    @JsonProperty("date")
    private LocalDate date;

    @JsonProperty("time")
    private LocalTime time;
}
<file_sep>/src/main/java/com/eresearch/repositorer/resource/NameLookupResource.java
package com.eresearch.repositorer.resource;

import com.eresearch.repositorer.dto.repositorer.request.NameLookupSearchDto;
import com.eresearch.repositorer.dto.repositorer.response.NameLookupDeleteOperationResultDto;
import com.eresearch.repositorer.dto.repositorer.response.RandomEntriesResponseDto;
import com.eresearch.repositorer.dto.repositorer.response.RetrievedNameLookupDtos;
import com.eresearch.repositorer.exception.data.RepositorerValidationException;
import org.springframework.http.ResponseEntity;

public interface NameLookupResource {

    ResponseEntity<RetrievedNameLookupDtos> find(NameLookupSearchDto nameLookupSearchDto) throws RepositorerValidationException;

    ResponseEntity<RetrievedNameLookupDtos> findAll();

    ResponseEntity<RandomEntriesResponseDto> addRandomEntriesForTesting();

    ResponseEntity<NameLookupDeleteOperationResultDto> deleteAll();

    ResponseEntity<NameLookupDeleteOperationResultDto> delete(NameLookupSearchDto nameLookupSearchDto) throws RepositorerValidationException;

}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/dblp/response/generated/Incollection.java
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2017.02.27 at 11:20:24 PM EET 
//


package com.eresearch.repositorer.dto.dblp.response.generated;

import com.eresearch.repositorer.transformer.results.dblp.processor.common.CommonDblpSource;
import lombok.Getter;
import lombok.Setter;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.NormalizedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.List;


@Getter
@Setter

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "incollection")
public class Incollection implements CommonDblpSource {

    @XmlAttribute(name = "key", required = true)
    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
    protected String key;

    @XmlAttribute(name = "mdate")
    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
    protected String mdate;

    @XmlAttribute(name = "publtype")
    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
    protected String publtype;

    @XmlAttribute(name = "cdate")
    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
    protected String cdate;

    @XmlElement(name = "author", type = Author.class)
    private List<Author> authors;

    @XmlElement(name = "editor", type = Editor.class)
    private List<Editor> editors;

    @XmlElement(name = "title", type = Title.class)
    private List<Title> titles;

    @XmlElement(name = "booktitle", type = Booktitle.class)
    private List<Booktitle> booktitles;

    @XmlElement(name = "pages", type = Pages.class)
    private List<Pages> pages;

    @XmlElement(name = "year", type = Year.class)
    private List<Year> years;

    @XmlElement(name = "address", type = Address.class)
    private List<Address> addresses;

    @XmlElement(name = "journal", type = Journal.class)
    private List<Journal> journals;

    @XmlElement(name = "volume", type = Volume.class)
    private List<Volume> volumes;

    @XmlElement(name = "number", type = Number.class)
    private List<Number> numbers;

    @XmlElement(name = "month", type = Month.class)
    private List<Month> months;

    @XmlElement(name = "url", type = Url.class)
    private List<Url> urls;

    @XmlElement(name = "ee", type = Ee.class)
    private List<Ee> ees;

    @XmlElement(name = "cdrom", type = Cdrom.class)
    private List<Cdrom> cdroms;

    @XmlElement(name = "cite", type = Cite.class)
    private List<Cite> cites;

    @XmlElement(name = "publisher", type = Publisher.class)
    private List<Publisher> publishers;

    @XmlElement(name = "note", type = Note.class)
    private List<Note> notes;

    @XmlElement(name = "crossref", type = Crossref.class)
    private List<Crossref> crossrefs;

    @XmlElement(name = "isbn", type = Isbn.class)
    private List<Isbn> isbns;

    @XmlElement(name = "series", type = Series.class)
    private List<Series> series;

    @XmlElement(name = "school", type = School.class)
    private List<School> schools;

    @XmlElement(name = "chapter", type = Chapter.class)
    private List<Chapter> chapters;

    @XmlElement(name = "publnr", type = Publnr.class)
    private List<Publnr> publnrs;

    @Override
    public String getReviewid() {
        throw new UnsupportedOperationException("not containing reviewId");
    }

    @Override
    public String getRating() {
        throw new UnsupportedOperationException("not containing rating");
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/notes.txt
-------------
--- NOTES ---
-------------

1)  In order to see jacoco reports, when you have the target/jacoco.exec generated, then run the following maven goal: jacoco:report.
    Then go to target/site/jacoco/index.html where you can see the results.<file_sep>/src/main/java/com/eresearch/repositorer/dto/repositorer/response/RandomEntriesResponseDto.java
package com.eresearch.repositorer.dto.repositorer.response;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RandomEntriesResponseDto {

    private String result;
}
<file_sep>/src/main/java/com/eresearch/repositorer/aggregator/correlation/TransactionIdCorrelationStrategy.java
package com.eresearch.repositorer.aggregator.correlation;

import org.springframework.integration.aggregator.CorrelationStrategy;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

@Component
public class TransactionIdCorrelationStrategy implements CorrelationStrategy {

    private static final String TRANSACTION_ID = "Transaction-Id";

    @Override
    public Object getCorrelationKey(Message<?> message) {
        return message.getHeaders().get(TRANSACTION_ID);
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/elsevierauthor/response/AuthorSearchViewEntry.java
package com.eresearch.repositorer.dto.elsevierauthor.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.EqualsAndHashCode;

import java.util.Collection;
import java.util.Objects;

@EqualsAndHashCode(of = {"dcIdentifier"})
public class AuthorSearchViewEntry {

    @JsonProperty("@force-array")
    private String forceArray;

    @JsonProperty("error")
    private String error;

    @JsonProperty("@_fa")
    private String fa;

    @JsonProperty("link")
    private Collection<AuthorSearchViewLink> links;

    @JsonProperty("prism:url")
    private String prismUrl;

    @JsonProperty("dc:identifier")
    private String dcIdentifier;

    @JsonProperty("eid")
    private String eid;

    @JsonProperty("orcid")
    private String orcId;

    @JsonProperty("preferred-name")
    private AuthorSearchViewPreferredName preferredName;

    @JsonProperty("name-variant")
    private Collection<AuthorSearchViewNameVariant> nameVariants;

    @JsonProperty("document-count")
    private String documentCount;

    @JsonProperty("subject-area")
    private Collection<AuthorSearchViewSubjectArea> subjectAreas;

    @JsonProperty("affiliation-current")
    private AuthorSearchViewAffiliationCurrent affiliationCurrent;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;
        AuthorSearchViewEntry that = (AuthorSearchViewEntry) o;
        return Objects.equals(dcIdentifier, that.dcIdentifier);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), dcIdentifier);
    }

    public AuthorSearchViewEntry() {
    }

    public AuthorSearchViewEntry(String forceArray, String error, String fa, Collection<AuthorSearchViewLink> links, String prismUrl, String dcIdentifier, String eid, String orcId, AuthorSearchViewPreferredName preferredName, Collection<AuthorSearchViewNameVariant> nameVariants, String documentCount, Collection<AuthorSearchViewSubjectArea> subjectAreas, AuthorSearchViewAffiliationCurrent affiliationCurrent) {
        this.forceArray = forceArray;
        this.error = error;
        this.fa = fa;
        this.links = links;
        this.prismUrl = prismUrl;
        this.dcIdentifier = dcIdentifier;
        this.eid = eid;
        this.orcId = orcId;
        this.preferredName = preferredName;
        this.nameVariants = nameVariants;
        this.documentCount = documentCount;
        this.subjectAreas = subjectAreas;
        this.affiliationCurrent = affiliationCurrent;
    }

    public String getForceArray() {
        return forceArray;
    }

    public void setForceArray(String forceArray) {
        this.forceArray = forceArray;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }

    public String getFa() {
        return fa;
    }

    public void setFa(String fa) {
        this.fa = fa;
    }

    public Collection<AuthorSearchViewLink> getLinks() {
        return links;
    }

    public void setLinks(Collection<AuthorSearchViewLink> links) {
        this.links = links;
    }

    public String getPrismUrl() {
        return prismUrl;
    }

    public void setPrismUrl(String prismUrl) {
        this.prismUrl = prismUrl;
    }

    public String getDcIdentifier() {
        return dcIdentifier;
    }

    public void setDcIdentifier(String dcIdentifier) {
        this.dcIdentifier = dcIdentifier;
    }

    public String getEid() {
        return eid;
    }

    public void setEid(String eid) {
        this.eid = eid;
    }

    public String getOrcId() {
        return orcId;
    }

    public void setOrcId(String orcId) {
        this.orcId = orcId;
    }

    public AuthorSearchViewPreferredName getPreferredName() {
        return preferredName;
    }

    public void setPreferredName(AuthorSearchViewPreferredName preferredName) {
        this.preferredName = preferredName;
    }

    public Collection<AuthorSearchViewNameVariant> getNameVariants() {
        return nameVariants;
    }

    public void setNameVariants(Collection<AuthorSearchViewNameVariant> nameVariants) {
        this.nameVariants = nameVariants;
    }

    public String getDocumentCount() {
        return documentCount;
    }

    public void setDocumentCount(String documentCount) {
        this.documentCount = documentCount;
    }

    public Collection<AuthorSearchViewSubjectArea> getSubjectAreas() {
        return subjectAreas;
    }

    public void setSubjectAreas(Collection<AuthorSearchViewSubjectArea> subjectAreas) {
        this.subjectAreas = subjectAreas;
    }

    public AuthorSearchViewAffiliationCurrent getAffiliationCurrent() {
        return affiliationCurrent;
    }

    public void setAffiliationCurrent(AuthorSearchViewAffiliationCurrent affiliationCurrent) {
        this.affiliationCurrent = affiliationCurrent;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/domain/lookup/NameLookup.java
package com.eresearch.repositorer.domain.lookup;

import com.eresearch.repositorer.deserializer.InstantDeserializer;
import com.eresearch.repositorer.domain.common.NameVariant;
import com.eresearch.repositorer.serializer.InstantSerializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.Instant;
import java.util.Collection;


/**
 * This domain class holds the binding between author name to perform extraction and transaction id.
 */


@NoArgsConstructor
@Getter
@Setter
@ToString(of = {"firstname", "initials", "surname"})
@EqualsAndHashCode(of = {"firstname", "initials", "surname"})

@Document(collection = "names-lookup")
public class NameLookup {

    @Id
    private String id;

    private String transactionId;

    private String firstname;
    private String initials;
    private String surname;

    private Collection<NameVariant> nameVariants;

    private NameLookupStatus nameLookupStatus;

    @JsonDeserialize(using = InstantDeserializer.class)
    @JsonSerialize(using = InstantSerializer.class)
    private Instant createdAt;

    public NameLookup(String transactionId,
                      String firstname,
                      String initials,
                      String surname,
                      Collection<NameVariant> nameVariants,
                      NameLookupStatus nameLookupStatus,
                      Instant createdAt) {
        this.transactionId = transactionId;
        this.firstname = firstname;
        this.initials = initials;
        this.surname = surname;
        this.nameVariants = nameVariants;
        this.nameLookupStatus = nameLookupStatus;
        this.createdAt = createdAt;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/error/handler/ErrorHandler.java
package com.eresearch.repositorer.error.handler;


import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.repository.ErrorReportRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Clock;

@Component
public abstract class ErrorHandler {

    final ErrorReportRepository errorReportRepository;
    final Clock clock;

    @Autowired
    public ErrorHandler(ErrorReportRepository errorReportRepository, Clock clock) {
        this.errorReportRepository = errorReportRepository;
        this.clock = clock;
    }

    public abstract boolean canHandleIt(MessageHandlingException messageHandlingException);

    public abstract void handle(MessageHandlingException messageHandlingException);

    String getErrorStacktrace(Throwable businessException) {
        StringWriter sw = new StringWriter();
        businessException.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/service/RepositorerExtractorImpl.java
package com.eresearch.repositorer.service;

import com.eresearch.repositorer.dto.repositorer.request.RepositorerFindDto;
import com.eresearch.repositorer.dto.repositorer.request.RepositorerFindDtos;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.exception.error.RepositorerError;
import com.eresearch.repositorer.extractor.BatchExtractor;
import com.eresearch.repositorer.gateway.AuthorExtractor;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Log4j
@Service
public class RepositorerExtractorImpl implements RepositorerExtractor {

    @Autowired
    private TransactionId transactionId;

    @Autowired
    private AuthorExtractor authorExtractor;

    @Autowired
    private BatchExtractor batchExtractor;

    @Override
    public void extractAuthorInfo(final RepositorerFindDto dto, String transactionId) {

        log.info("RepositorerExtractorImpl --> " + Thread.currentThread().getName());

        final String txId = transactionId == null ? this.transactionId.getTransactionId() : transactionId;

        authorExtractor.extract(dto, txId);

        log.info("RepositorerExtractorImpl#extractAuthorInfo --- fired, with parameters: dto = "
                + dto
                + ", txId = "
                + txId);
    }

    @Override
    public void extractAuthorInfo(RepositorerFindDtos dtos) {

        // investigate if too many extraction processes are still active, if yes respond accordingly....
        if (!batchExtractor.canAcceptIncomingExtraction()) {
            throw new RepositorerBusinessException(RepositorerError.TOO_MANY_EXTRACTION_PROCESSES_ACTIVE,
                    RepositorerError.TOO_MANY_EXTRACTION_PROCESSES_ACTIVE.getMessage());
        }

        batchExtractor.handleExtraction(dtos);
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/results/dblp/processor/DblpProcessor.java
package com.eresearch.repositorer.transformer.results.dblp.processor;

import com.eresearch.repositorer.domain.record.Entry;
import com.eresearch.repositorer.dto.dblp.response.DblpAuthor;
import com.eresearch.repositorer.dto.dblp.response.generated.Dblp;
import com.fasterxml.jackson.core.JsonProcessingException;

import java.util.List;

public interface DblpProcessor {

    void doProcessing(List<Entry> entries, Dblp dblp, DblpAuthor dblpAuthor) throws JsonProcessingException;

    boolean allowProcessing();
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/authormatcher/response/StringMetricResultDto.java
package com.eresearch.repositorer.dto.authormatcher.response;

import com.fasterxml.jackson.annotation.JsonProperty;

public class StringMetricResultDto {

    @JsonProperty("comparison-result")
    private Double comparisonResult;

    @JsonProperty("comparison-result-floor")
    private Double comparisonResultFloor;

    @JsonProperty("comparison-result-ceil")
    private Double comparisonResultCeil;

    public StringMetricResultDto() {
    }

    public StringMetricResultDto(Double comparisonResult, Double comparisonResultFloor, Double comparisonResultCeil) {
        this.comparisonResult = comparisonResult;
        this.comparisonResultFloor = comparisonResultFloor;
        this.comparisonResultCeil = comparisonResultCeil;
    }

    public Double getComparisonResult() {
        return comparisonResult;
    }

    public void setComparisonResult(Double comparisonResult) {
        this.comparisonResult = comparisonResult;
    }

    public Double getComparisonResultFloor() {
        return comparisonResultFloor;
    }

    public void setComparisonResultFloor(Double comparisonResultFloor) {
        this.comparisonResultFloor = comparisonResultFloor;
    }

    public Double getComparisonResultCeil() {
        return comparisonResultCeil;
    }

    public void setComparisonResultCeil(Double comparisonResultCeil) {
        this.comparisonResultCeil = comparisonResultCeil;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/dto/RepositorerFindDtos.java
package com.eresearch.repositorer.transformer.dto;

import com.eresearch.repositorer.dto.repositorer.request.RepositorerFindDto;

import java.util.Collection;


public class RepositorerFindDtos {

    private Collection<RepositorerFindDto> dtos;

    public RepositorerFindDtos(Collection<RepositorerFindDto> dtos) {
        this.dtos = dtos;
    }

    public RepositorerFindDtos() {
    }

    public Collection<RepositorerFindDto> getDtos() {
        return dtos;
    }

    public void setDtos(Collection<RepositorerFindDto> dtos) {
        this.dtos = dtos;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/application/actuator/health/EresearchRepositorerHealthCheck.java
package com.eresearch.repositorer.application.actuator.health;

import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Log4j
@Component
public class EresearchRepositorerHealthCheck extends AbstractHealthIndicator {

    @Autowired
    private JmsTemplate jmsTemplate;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        this.performBasicHealthChecks();

        Optional<Exception> ex = this.specificHealthCheck();

        if (ex.isPresent()) {
            builder.down(ex.get());
        } else {
            builder.up();
        }
    }

    private void performBasicHealthChecks() {
        //check disk...
        DiskSpaceHealthIndicatorProperties diskSpaceHealthIndicatorProperties
                = new DiskSpaceHealthIndicatorProperties();
        diskSpaceHealthIndicatorProperties.setThreshold(10737418240L); /*10 GB*/
        new DiskSpaceHealthIndicator(diskSpaceHealthIndicatorProperties);

        //check datasource...
        new MongoHealthIndicator(mongoTemplate);

        //check jms (active mq) is up...
        new JmsHealthIndicator(jmsTemplate.getConnectionFactory());
    }

    private Optional<Exception> specificHealthCheck() {
        //Note: add more thing to test for health if needed.
        return Optional.empty();
    }
}
<file_sep>/README.md
# ERESEARCH REPOSITORER SERVICE #

### Description

This is the backbone service of eresearch repository platform.
It integrates with external systems, collects --> aggregates based on a correlation id --> transforms and stores them
in a document based database which in our case is MongoDB.


This service integrates with the following external systems:
* [eresearch-author-matcher](https://github.com/chriniko13/eresearch-author-matcher)
* [eresearch-dblp-consumer](https://github.com/chriniko13/eresearch-dblp-consumer)
* [eresearch-scidir-consumer](https://github.com/chriniko13/eresearch-sciencedirect-consumer)
* [eresearch-author-finder](https://github.com/chriniko13/eresearch-author-finder)
* [eresearch-scopus-consumer](https://github.com/chriniko13/eresearch-scopus-consumer)


It consists of two workflows in order to collect and store the data.
* `repositorer-first-step-workflow.xml`

![first](first_workflow.jpg)


* `repositorer-second-step-workflow.xml`

![second](second_workflow.jpg)



Also it handles errors with the following workflows:
* `repositorer-error-handling-aggregators-workflow.xml`
* `repositorer-error-handling-workflow.xml`


### Convenient UI (Admin Portal) in order to run repositorer operations
* [eresearch-repositorer-admin-portal](https://github.com/chriniko13/eresearch-repositorer-admin-portal)



### External Dependencies needed in order to run service (Other services && Infrastructure)

* Dependencies

    * Infrastructure
        * MongoDB
        * ActiveMQ

    * Platform Services (Build docker images for the following services (detailed info on how to build docker image can be found on README.md
                         of each one of the following described services))
        * [eresearch-author-matcher](https://github.com/chriniko13/eresearch-author-matcher)
        * [eresearch-dblp-consumer](https://github.com/chriniko13/eresearch-dblp-consumer)
        * [eresearch-scidir-consumer](https://github.com/chriniko13/eresearch-sciencedirect-consumer)
        * [eresearch-author-finder](https://github.com/chriniko13/eresearch-author-finder)
        * [eresearch-scopus-consumer](https://github.com/chriniko13/eresearch-scopus-consumer)

* How to init dependencies
    * Execute: `docker-compose up`
    * Execute: `docker-compose down`


### Unit Tests (written in Groovy with Spock Framework)

* Execute: `mvn clean test`


### Integration Tests (run docker-compose first, [eresearch-author-matcher](https://github.com/chriniko13/eresearch-author-matcher) is the only needed from platform's services, all the others are mocked with WireMock)

* Execute: `mvn clean verify`


### Create Docker Image
* Execute: `mvn clean install -DskipUTs=true -DskipITs=true`
* Execute: `docker build -t chriniko/eresearch-repositorer:1.0 .` in order to build docker image

* Fast: `mvn clean install -DskipUTs=true -DskipITs=true && docker build -t chriniko/eresearch-repositorer:1.0 .`


### How to run service (not dockerized)
1. Execute: `docker-compose up`
2. Execute: `mvn clean install -DskipUTs=true -DskipITs=true`
3. Execute: `java -Dspring.profiles.active=dev -jar target/eresearch-repositorer-1.0-boot.jar`
4. (Optional) Start: [eresearch-repositorer-admin-portal](https://github.com/chriniko13/eresearch-repositorer-admin-portal) with: `mvn tomcat7:run`
5. (Optional) To shutdown: `docker-compose down`


### Example Request for Author Extraction

* Hitting with HTTP POST the endpoint: `http://localhost:8889/repositorer/extract`

```json
{
	"firstname":"Anastasios",
	"initials":"",
	"surname":"Tsolakidis"
}
```


### Example Response from Author Exctration

```json
{
    "message": "Extraction fired."
}
```

#### After Extraction, we hit MongoDB in order to see the contents of the just fired extraction (Records Resource)

* Hitting with HTTP GET the endpoint: `http://localhost:8889/repositorer/records/find-all`

```json

{
    "retrievedRecordDtos": [
        {
            "filename": "RECORDAnastasios_NoValue_Tsolakidis#2019-04-18T20:25:14.619"
        }
    ]
}


```

* Hitting with HTTP GET the endpoint: `http://localhost:8889/repositorer/records/find-all?full-fetch=true`


```json
{
    "retrievedRecordDtos": [
        {
            "filename": "RECORDAnastasios_NoValue_Tsolakidis#2019-04-18T20:25:14.619",
            "record": {
                "id": "e9943aff-5810-474c-b9cf-5e0e6223ca6c",
                "transactionId": "f414a76c-0edb-4da6-b45b-e7c7d5bf6c6a",
                "firstname": "Anastasios",
                "initials": "",
                "lastname": "Tsolakidis",
                "nameVariants": [
                    {
                        "firstname": "A.",
                        "initials": "",
                        "surname": "Tsolakidis"
                    },
                    {
                        "firstname": "A",
                        "initials": "",
                        "surname": "Tsolakidis"
                    }
                ],
                "createdAt": "2019-04-18T17:25:14.619Z",
                "entries": [
                    {
                        "title": "AcademIS: an ontology for representing academic activity and collaborations within HEIs.",
                        "authors": [
                            {
                                "firstname": "Evangelia",
                                "initials": null,
                                "surname": "Triperina"
                            },
                            {
                                "firstname": "Cleo",
                                "initials": null,
                                "surname": "Sgouropoulou"
                            },
                            {
                                "firstname": "Anastasios",
                                "initials": null,
                                "surname": "Tsolakidis"
                            }
                        ],
                        "metadata": {
                            "Source": "DBLP",
                            "Dblp Author": "{\"authorName\":\"<NAME>\",\"urlpt\":\"t/Tsolakidis:Anastasios\"}",
                            "Key": "conf/pci/TriperinaST13",
                            "Mdate": "2018-11-06",
                            "Publtype": null,
                            "Cdate": null,
                            "Authors": "[\"<NAME>\",\"<NAME>\",\"<NAME>\"]",
                            "Editors": null,
                            "Titles": "[\"AcademIS: an ontology for representing academic activity and collaborations within HEIs.\"]",
                            "Booktitles": "[\"Panhellenic Conference on Informatics\"]",
                            "Pages": "[\"264-271\"]",
                            "Years": "[\"2013\"]",
                            "Addresses": null,
                            "Journals": null,
                            "Volumes": null,
                            "Numbers": null,
                            "Months": null,
                            "Urls": "[\"db/conf/pci/pci2013.html#TriperinaST13\"]",
                            "Ees": "[\"https://doi.org/10.1145/2491845.2491884\"]",
                            "Cd roms": null,
                            "Cites": null,
                            "Publishers": null,
                            "Notes": null,
                            "Crossrefs": "[\"conf/pci/2013\"]",
                            "Isbns": null,
                            "Series": null,
                            "Schools": null,
                            "Chapters": null,
                            "Publnrs": null
                        }
                    },
                    {
                        "title": "PP-297. Factors that can affect birth type 10<ce:hsp sp=0.25></ce:hsp>years study",
                        "authors": [
                            {
                                "firstname": "Dimitrios",
                                "initials": null,
                                "surname": "Papadimitriou"
                            },
                            {
                                "firstname": "Athanasios",
                                "initials": null,
                                "surname": "Tsolakidis"
                            },
                            {
                                "firstname": "Hacer",
                                "initials": null,
                                "surname": "Hasan"
                            },
                            {
                                "firstname": "Anna",
                                "initials": null,
                                "surname": "Pantazi"
                            },
                            {
                                "firstname": "Eleni",
                                "initials": null,
                                "surname": "Karanikolaou"
                            }
                        ],
                        "metadata": {
                            "Source": "Science Direct",
                            "Links": "[\"https://api.elsevier.com/content/article/pii/S0378378210005906\",\"https://www.sciencedirect.com/science/article/pii/S0378378210005906?dgcid=api_sd_search-api-endpoint\"]",
                            "Load Date": "2010-11-06T00:00:00Z",
                            "Prism Url": "https://api.elsevier.com/content/article/pii/S0378378210005906",
                            "Dc Identifier": "DOI:10.1016/j.earlhumdev.2010.09.353",
                            "Open Access": "false",
                            "Open Access Flag": null,
                            "Dc Title": "PP-297. Factors that can affect birth type 10<ce:hsp sp=0.25></ce:hsp>years study",
                            "Prism Publication Name": "Early Human Development",
                            "Prism Isbn": null,
                            "Prism Issn": null,
                            "Prism Volume": "86",
                            "Prism Issue Identifier": null,
                            "Prism Issue Name": null,
                            "Prism Edition": null,
                            "Prism Starting Page": "s134",
                            "Prism Ending Page": "s135",
                            "Prism Cover Date": "2010-11-30",
                            "Prism Cover Display Date": null,
                            "Dc Creator": "<NAME>",
                            "Authors": "[{\"$\":\"<NAME>\"},{\"$\":\"<NAME>\"},{\"$\":\"<NAME>\"},{\"$\":\"<NAME>\"},{\"$\":\"<NAME>\"}]",
                            "Prism Doi": "10.1016/j.earlhumdev.2010.09.353",
                            "Pii": "S0378378210005906",
                            "Pubtype": null,
                            "Prism Teaser": null,
                            "Dc Description": null,
                            "Author Keywords": null,
                            "Prism Aggregation Type": null,
                            "Prism Copyright": null,
                            "Scopus Id": null,
                            "Eid": null,
                            "Scopus Eid": null,
                            "Pubmed Id": null,
                            "Open Access Article": null,
                            "Open Archive Article": null,
                            "Open Access User License": null
                        }
                    },
                    
                    .....
```


* Hitting with HTTP POST the endpoint: `http://localhost:8889/repositorer/records/find-by-filename?full-fetch=true`

    * Payload:
    ```json
      {
        "filename":"RECORDGrammati_NoValue_Pantziou#2019-04-18T21:44:50.218"
      }
    
    ```
    
    * Result:
    ```json
      {
          "retrievedRecordDtos": [
              {
                  "filename": "RECORDGrammati_NoValue_Pantziou#2019-04-18T21:44:50.218",
                  "record": {
                      "id": "7df75fa8-b063-439f-bbf4-714909a346a5",
                      "transactionId": "bd09a46a-9108-4102-8ef4-689ab53714a1",
                      "firstname": "Grammati",
                      "initials": "",
                      "lastname": "Pantziou",
                      "nameVariants": [
                          {
                              "firstname": "G.",
                              "initials": "",
                              "surname": "Pantziou"
                          },
                          {
                              "firstname": "G",
                              "initials": "",
                              "surname": "Pantziou"
                          }
                      ],
                      "createdAt": "2019-04-18T18:44:50.218Z",
                      "entries": [
                          {
                              "title": "Clustering in mobile ad hoc networks through neighborhood stability-based mobility prediction",
                              "authors": [
                                  {
                                      "firstname": "Charalampos",
                                      "initials": null,
                                      "surname": "Konstantopoulos"
                                  },
                                  {
                                      "firstname": "Grammati",
                                      "initials": null,
                                      "surname": "Pantziou"
                                  },
                                  {
                                      "firstname": "Damianos",
                                      "initials": null,
                                      "surname": "Gavalas"
                                  }
                              ],
                              "metadata": {
                                  "Source": "Science Direct",
                                  "Links": "[\"https://api.elsevier.com/content/article/pii/S1389128608000923\",\"https://www.sciencedirect.com/science/article/pii/S1389128608000923?dgcid=api_sd_search-api-endpoint\"]",
                                  "Load Date": "2008-03-16T00:00:00Z",
                                  "Prism Url": "https://api.elsevier.com/content/article/pii/S1389128608000923",
                                  "Dc Identifier": "DOI:10.1016/j.comnet.2008.01.018",
                                  "Open Access": "false",
                                  "Open Access Flag": null,
                                  "Dc Title": "Clustering in mobile ad hoc networks through neighborhood stability-based mobility prediction",
                                  "Prism Publication Name": "Computer Networks",
                                  "Prism Isbn": null,
                                  "Prism Issn": null,
                                  "Prism Volume": "52",
                                  "Prism Issue Identifier": null,
                                  "Prism Issue Name": null,
                                  "Prism Edition": null,
                                  "Prism Starting Page": "1797",
                                  "Prism Ending Page": "1824",
                                  "Prism Cover Date": "2008-06-26",
                                  "Prism Cover Display Date": null,
                                  "Dc Creator": "<NAME>",
                                  "Authors": "[{\"$\":\"<NAME>\"},{\"$\":\"<NAME>\"},{\"$\":\"<NAME>\"}]",
                                  "Prism Doi": "10.1016/j.comnet.2008.01.018",
                                  "Pii": "S1389128608000923",
                                  "Pubtype": null,
                                  "Prism Teaser": null,
                                  "Dc Description": null,
                                  "Author Keywords": null,
                                  "Prism Aggregation Type": null,
                                  "Prism Copyright": null,
                                  "Scopus Id": null,
                                  "Eid": null,
                                  "Scopus Eid": null,
                                  "Pubmed Id": null,
                                  "Open Access Article": null,
                                  "Open Archive Article": null,
                                  "Open Access User License": null
                              }
                          },
                          {
                              "title": "Chapter 13: Design and management of vehicle-sharing systems: a survey of algorithmic approaches",
                              "authors": [
                                  {
                                      "firstname": "G.",
                                      "initials": null,
                                      "surname": "Pantziou"
                                  },
                                  {
                                      "firstname": "C.",
                                      "initials": null,
                                      "surname": "Konstantopoulos"
                                  },
                                  {
                                      "firstname": "D.",
                                      "initials": null,
                                      "surname": "Gavalas"
                                  }
                              ],
                              "metadata": {
                                  "Source": "Science Direct",
                                  "Links": "[\"https://api.elsevier.com/content/article/pii/B9780128034545000134\",\"https://www.sciencedirect.com/science/article/pii/B9780128034545000134?dgcid=api_sd_search-api-endpoint\"]",
                                  "Load Date": "2016-06-17T00:00:00Z",
                                  "Prism Url": "https://api.elsevier.com/content/article/pii/B9780128034545000134",
                                  "Dc Identifier": "DOI:10.1016/B978-0-12-803454-5.00013-4",
                                  "Open Access": "false",
                                  "Open Access Flag": null,
                                  "Dc Title": "Chapter 13: Design and management of vehicle-sharing systems: a survey of algorithmic approaches",
                                  "Prism Publication Name": "Smart Cities and Homes",
                                  "Prism Isbn": null,
                                  "Prism Issn": null,
                                  "Prism Volume": null,
                                  "Prism Issue Identifier": null,
                                  "Prism Issue Name": null,
                                  "Prism Edition": null,
                                  "Prism Starting Page": "261",
                                  "Prism Ending Page": "289",
                                  "Prism Cover Date": "2016-12-31",
                                  "Prism Cover Display Date": null,
                                  "Dc Creator": "<NAME>",
                                  "Authors": "[{\"$\":\"<NAME>\"},{\"$\":\"<NAME>\"},{\"$\":\"<NAME>\"}]",
                                  "Prism Doi": "10.1016/B978-0-12-803454-5.00013-4",
                                  "Pii": "B9780128034545000134",
                                  "Pubtype": null,
                                  "Prism Teaser": null,
                                  "Dc Description": null,
                                  "Author Keywords": null,
                                  "Prism Aggregation Type": null,
                                  "Prism Copyright": null,
                                  "Scopus Id": null,
                                  "Eid": null,
                                  "Scopus Eid": null,
                                  "Pubmed Id": null,
                                  "Open Access Article": null,
                                  "Open Archive Article": null,
                                  "Open Access User License": null
                              }
                          },
                        .....
    
    ```


### Error Report Sample

* Hitting with HTTP GET the endpoint: `http://localhost:8889/repositorer/error-reports/find-all?full-fetch=true`

```json

{
    "retrievedErrorReportDtos": [
        {
            "filename": "ERROR_REPORT2019-04-18T15:38:34.281Z",
            "errorReport": {
                "id": "5cb04b8e-445e-4b9b-990d-2bd883d6387f",
                "createdAt": "2019-04-18T15:38:34.281Z",
                "exceptionToString": "com.eresearch.repositorer.exception.business.RepositorerBusinessException: Could not deserialize message.",
                "repositorerError": "COULD_NOT_DESERIALIZE_MESSAGE",
                "crashedComponentName": "com.eresearch.repositorer.transformer.results.sciencedirect.ScienceDirectResultsTransformer",
                "errorStacktrace": "com.eresearch.repositorer.exception.business.RepositorerBusinessException: Could not deserialize message.\n\tat com.eresearch.repositorer.transformer.results.sciencedirect.ScienceDirectResultsTransformer.transform(ScienceDirectResultsTransformer.java:60)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.inv
                
                ...

```


### Name Lookup Sample

* Hitting with HTTP GET the endpoint: `http://localhost:8889/repositorer/name-lookups/find-all`

```json
{
    "nameLookups": [
        {
            "id": "5cb8b2e21cf6f43290dba9fe",
            "transactionId": "f414a76c-0edb-4da6-b45b-e7c7d5bf6c6a",
            "firstname": "Anastasios",
            "initials": "",
            "surname": "Tsolakidis",
            "nameVariants": [
                {
                    "firstname": "A.",
                    "initials": "",
                    "surname": "Tsolakidis"
                },
                {
                    "firstname": "A",
                    "initials": "",
                    "surname": "Tsolakidis"
                }
            ],
            "nameLookupStatus": "COMPLETED",
            "createdAt": "2019-04-18T17:24:50.865Z"
        }
    ]
}
```


### Discarded Message Sample

TODO


<file_sep>/src/main/java/com/eresearch/repositorer/connector/ScienceDirectConsumerConnector.java
package com.eresearch.repositorer.connector;

import com.eresearch.repositorer.dto.sciencedirect.request.ElsevierScienceDirectConsumerDto;
import com.eresearch.repositorer.dto.sciencedirect.response.SciDirImmediateResultDto;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.eresearch.repositorer.exception.error.RepositorerError;
import lombok.extern.log4j.Log4j;
import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Log4j
@Component
public class ScienceDirectConsumerConnector implements Connector {

    private static final String TRANSACTION_ID = "Transaction-Id";

    @Value("${sciencedirect.consumer.url}")
    private String scienceDirectConsumerUrl;

    @Autowired
    @Qualifier("repositorerRestTemplate")
    private RestTemplate restTemplate;

    @Autowired
    @Qualifier("basicRetryPolicyForConnectors")
    private RetryPolicy basicRetryPolicyForConnectors;

    private SciDirImmediateResultDto extractInfoFromScienceDirect(ElsevierScienceDirectConsumerDto elsevierScienceDirectConsumerDto, String transactionId) {

        final String url = scienceDirectConsumerUrl;

        final HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(TRANSACTION_ID, transactionId);

        final HttpEntity<ElsevierScienceDirectConsumerDto> httpEntity
                = new HttpEntity<>(elsevierScienceDirectConsumerDto, httpHeaders);

        final ResponseEntity<SciDirImmediateResultDto> responseEntity = restTemplate.exchange(
                url,
                HttpMethod.POST,
                httpEntity,
                SciDirImmediateResultDto.class);

        return responseEntity.getBody();
    }

    public SciDirImmediateResultDto extractInfoFromScienceDirectWithRetries(ElsevierScienceDirectConsumerDto elsevierScienceDirectConsumerDto, String transactionId) {

        return Failsafe
                .with(basicRetryPolicyForConnectors)
                .withFallback(() -> {
                    throw new RepositorerBusinessException(RepositorerError.CONNECTOR_CONNECTION_ERROR,
                            RepositorerError.CONNECTOR_CONNECTION_ERROR.getMessage(),
                            this.getClass().getName());
                })
                .onSuccess(s -> log.info("ScienceDirectConsumerConnector#extractInfoFromScienceDirectWithRetries, completed successfully!"))
                .onFailure(error -> log.error("ScienceDirectConsumerConnector#extractInfoFromScienceDirectWithRetries, failed!"))
                .onAbort(error -> log.error("ScienceDirectConsumerConnector#extractInfoFromScienceDirectWithRetries, aborted!"))
                .get((context) -> {

                    long startTime = context.getStartTime().toMillis();
                    long elapsedTime = context.getElapsedTime().toMillis();
                    int executions = context.getExecutions();

                    String message = String.format("ScienceDirectConsumerConnector#extractInfoFromScienceDirectWithRetries, retrying...with params: " +
                            "[executions=%s, startTime(ms)=%s, elapsedTime(ms)=%s, elsevierScienceDirectConsumerDto=%s, transactionId=%s]", executions, startTime, elapsedTime, elsevierScienceDirectConsumerDto, transactionId);

                    log.warn(message);

                    return extractInfoFromScienceDirect(elsevierScienceDirectConsumerDto, transactionId);
                });
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/elsevierauthor/response/AuthorSearchViewPreferredName.java
package com.eresearch.repositorer.dto.elsevierauthor.response;

import com.fasterxml.jackson.annotation.JsonProperty;

public class AuthorSearchViewPreferredName {

    @JsonProperty("surname")
    private String surname;

    @JsonProperty("given-name")
    private String givenName;

    @JsonProperty("initials")
    private String initials;

    public AuthorSearchViewPreferredName(String surname, String givenName, String initials) {
        this.surname = surname;
        this.givenName = givenName;
        this.initials = initials;
    }

    public AuthorSearchViewPreferredName() {
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public String getGivenName() {
        return givenName;
    }

    public void setGivenName(String givenName) {
        this.givenName = givenName;
    }

    public String getInitials() {
        return initials;
    }

    public void setInitials(String initials) {
        this.initials = initials;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/elsevierauthor/response/AuthorSearchViewSubjectArea.java
package com.eresearch.repositorer.dto.elsevierauthor.response;

import com.fasterxml.jackson.annotation.JsonProperty;


public class AuthorSearchViewSubjectArea {

    @JsonProperty("@abbrev")
    private String abbrev;

    @JsonProperty("@frequency")
    private String frequency;

    @JsonProperty("$")
    private String subjectArea;

    public AuthorSearchViewSubjectArea() {
    }

    public AuthorSearchViewSubjectArea(String abbrev, String frequency, String subjectArea) {
        this.abbrev = abbrev;
        this.frequency = frequency;
        this.subjectArea = subjectArea;
    }

    public String getAbbrev() {
        return abbrev;
    }

    public void setAbbrev(String abbrev) {
        this.abbrev = abbrev;
    }

    public String getFrequency() {
        return frequency;
    }

    public void setFrequency(String frequency) {
        this.frequency = frequency;
    }

    public String getSubjectArea() {
        return subjectArea;
    }

    public void setSubjectArea(String subjectArea) {
        this.subjectArea = subjectArea;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/test/README.txt
Remove this code, when service is fully functional.
Thanks!

<NAME>.<file_sep>/src/main/java/com/eresearch/repositorer/resource/ErrorReportResourceImpl.java
package com.eresearch.repositorer.resource;

import com.eresearch.repositorer.dto.repositorer.request.ErrorReportFilenameDto;
import com.eresearch.repositorer.dto.repositorer.request.ErrorReportSearchDto;
import com.eresearch.repositorer.dto.repositorer.response.ErrorReportDeleteOperationResultDto;
import com.eresearch.repositorer.dto.repositorer.response.RandomEntriesResponseDto;
import com.eresearch.repositorer.dto.repositorer.response.RetrievedErrorReportDtos;
import com.eresearch.repositorer.exception.data.RepositorerValidationException;
import com.eresearch.repositorer.service.ErrorReportService;
import com.eresearch.repositorer.validator.Validator;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;

@Log4j
@RestController
@RequestMapping("/repositorer/error-reports")
public class ErrorReportResourceImpl implements ErrorReportResource {

    private static final String DEV_PROFILE_NOT_ACTIVE_MESSAGE = "Method could not be executed, because dev profile is not active.";

    @Autowired
    private Environment environment;

    @Autowired
    private ErrorReportService errorReportService;

    @Autowired
    private Validator<ErrorReportSearchDto> errorReportSearchDtoValidator;

    @Autowired
    private Validator<ErrorReportFilenameDto> errorReportFilenameDtoValidator;

    @RequestMapping(method = RequestMethod.POST, path = "/find", consumes = {
            MediaType.APPLICATION_JSON_UTF8_VALUE}, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    @Override
    public @ResponseBody
    ResponseEntity<RetrievedErrorReportDtos> find(@RequestBody ErrorReportSearchDto errorReportSearchDto,
                                                  @RequestParam(name = "full-fetch", defaultValue = "false") boolean fullFetch) throws RepositorerValidationException {

        try {
            errorReportSearchDtoValidator.validate(errorReportSearchDto);
            return ResponseEntity.ok(new RetrievedErrorReportDtos(errorReportService.find(errorReportSearchDto, fullFetch)));
        } catch (RepositorerValidationException e) {
            log.error("ErrorReportResourceImpl#find(ErrorReportSearchDto) --- error occurred.", e);
            throw e;
        }
    }

    @RequestMapping(method = RequestMethod.GET, path = "/find-all", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    @Override
    public @ResponseBody
    ResponseEntity<RetrievedErrorReportDtos> findAll(@RequestParam(name = "full-fetch", defaultValue = "false") boolean fullFetch) {
        return ResponseEntity.ok(new RetrievedErrorReportDtos(errorReportService.findAll(fullFetch)));
    }

    @RequestMapping(method = RequestMethod.GET, path = "/add-random-entries", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    @Override
    public @ResponseBody
    ResponseEntity<RandomEntriesResponseDto> addRandomEntriesForTesting() {


        boolean isDevProfileActive = Arrays.stream(environment.getActiveProfiles()).anyMatch(activeProfile -> activeProfile.equals("dev"));
        if (!isDevProfileActive) {
            return ResponseEntity.ok(new RandomEntriesResponseDto(DEV_PROFILE_NOT_ACTIVE_MESSAGE));
        }

        return ResponseEntity.ok(errorReportService.addRandomEntriesForTesting());
    }

    @RequestMapping(method = RequestMethod.DELETE, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    @Override
    public @ResponseBody
    ResponseEntity<ErrorReportDeleteOperationResultDto> deleteAll() {
        ErrorReportDeleteOperationResultDto result = errorReportService.deleteAll();
        return ResponseEntity.ok(result);
    }

    @RequestMapping(method = RequestMethod.DELETE,
            consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE},
            produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    @Override
    public @ResponseBody
    ResponseEntity<ErrorReportDeleteOperationResultDto> delete(@RequestBody ErrorReportFilenameDto errorReportFilenameDto)
            throws RepositorerValidationException {

        try {
            errorReportFilenameDtoValidator.validate(errorReportFilenameDto);

            final ErrorReportDeleteOperationResultDto result = errorReportService.delete(errorReportFilenameDto);
            return ResponseEntity.ok(result);

        } catch (RepositorerValidationException e) {
            log.error("ErrorReportResourceImpl#delete(ErrorReportFilenameDto) --- error occurred.", e);
            throw e;
        }
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/resource/RepositorerResource.java
package com.eresearch.repositorer.resource;


import com.eresearch.repositorer.dto.repositorer.request.RepositorerFindDto;
import com.eresearch.repositorer.dto.repositorer.request.RepositorerFindDtos;
import com.eresearch.repositorer.dto.repositorer.response.RepositorerImmediateResultDto;
import com.eresearch.repositorer.exception.data.RepositorerValidationException;
import org.springframework.http.ResponseEntity;

public interface RepositorerResource {

    ResponseEntity<RepositorerImmediateResultDto> extractAuthorInfo(RepositorerFindDto dto, String transactionId)
            throws RepositorerValidationException;

    ResponseEntity<RepositorerImmediateResultDto> extractAuthorInfo(RepositorerFindDtos dto)
            throws RepositorerValidationException;
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/scopus/response/ScopusSearchAffiliation.java
package com.eresearch.repositorer.dto.scopus.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Collection;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ScopusSearchAffiliation {

    @JsonProperty("@_fa")
    private String fa;

    @JsonProperty("affilname")
    private String affiliationName;

    @JsonProperty("affiliation-city")
    private String affiliationCity;

    @JsonProperty("affiliation-country")
    private String affiliationCountry;

    @JsonProperty("afid")
    private String affiliationId;

    @JsonProperty("affiliation-url")
    private String affiliationUrl;

    @JsonProperty("name-variant")
    private Collection<ScopusSearchAffiliationNameVariant> affiliationNameVariant;
}
<file_sep>/src/main/java/com/eresearch/repositorer/test/AuthorMatcherIntegrationTest.java
package com.eresearch.repositorer.test;

import com.eresearch.repositorer.connector.AuthorMatcherConnector;
import com.eresearch.repositorer.dto.authormatcher.request.AuthorComparisonDto;
import com.eresearch.repositorer.dto.authormatcher.request.AuthorNameDto;
import com.eresearch.repositorer.dto.authormatcher.response.AuthorMatcherResultsDto;
import com.eresearch.repositorer.exception.business.RepositorerBusinessException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AuthorMatcherIntegrationTest {

    @Autowired
    private AuthorMatcherConnector authorMatcherConnector;

    @Autowired
    private ObjectMapper objectMapper;

    public void testIntegrationWithAuthorMatcher() throws RepositorerBusinessException, JsonProcessingException {

        AuthorComparisonDto authorComparisonDto = AuthorComparisonDto.builder()
                .firstAuthorName(AuthorNameDto.builder()
                        .firstName("Nikolaos")
                        .surname("Christidis")
                        .build())
                .secondAuthorName(AuthorNameDto.builder()
                        .firstName("N.")
                        .surname("Christidis")
                        .build())
                .build();

        AuthorMatcherResultsDto authorMatcherResultsDto = authorMatcherConnector.performAuthorMatchingWithRetries(authorComparisonDto);

        String resultAsAString = objectMapper.writeValueAsString(authorMatcherResultsDto);
        System.out.println("[AUTHOR MATCHER INTEGRATION RESULT] == " + resultAsAString);

    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/connector/Connector.java
package com.eresearch.repositorer.connector;

/**
 * This is used as a marker interface.
 * Every class which implements it, means that access external systems (third-parties).
 */
public interface Connector {
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/repositorer/response/RetrievedErrorReportDto.java
package com.eresearch.repositorer.dto.repositorer.response;

import com.eresearch.repositorer.domain.error.ErrorReport;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString(of = {"filename"})
@EqualsAndHashCode(of = {"filename"})

@JsonInclude(value = JsonInclude.Include.NON_NULL) //in order to not include null error report.
public class RetrievedErrorReportDto {

    private String filename;
    private ErrorReport errorReport;
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/scopus/response/ScopusSearchViewLink.java
package com.eresearch.repositorer.dto.scopus.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ScopusSearchViewLink {

    @JsonProperty("@_fa")
    private String fa;

    @JsonProperty("@href")
    private String href;

    @JsonProperty("@ref")
    private String ref;

    @JsonProperty("@type")
    private String type;

}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/elsevierauthor/request/AuthorFinderDto.java
package com.eresearch.repositorer.dto.elsevierauthor.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.ToString;


@Builder
@ToString
public class AuthorFinderDto {

    @JsonProperty("author-name")
    private AuthorNameDto authorName;

    public AuthorFinderDto() {
    }

    public AuthorFinderDto(AuthorNameDto authorName) {
        this.authorName = authorName;
    }

    public AuthorNameDto getAuthorName() {
        return authorName;
    }

    public void setAuthorName(AuthorNameDto authorName) {
        this.authorName = authorName;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/dblp/response/generated/Person.java
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2017.02.27 at 11:20:24 PM EET 
//


package com.eresearch.repositorer.dto.dblp.response.generated;

import lombok.Getter;
import lombok.Setter;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.NormalizedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.util.List;

@Getter
@Setter

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "person")
public class Person {

    @XmlAttribute(name = "key", required = true)
    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
    protected String key;

    @XmlAttribute(name = "mdate")
    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
    protected String mdate;

    @XmlAttribute(name = "cdate")
    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
    protected String cdate;

    @XmlElement(name = "author", type = Author.class)
    private List<Author> authors;

    @XmlElement(name = "note", type = Note.class)
    private List<Note> notes;

    @XmlElement(name = "url", type = Url.class)
    private List<Url> urls;

    @XmlElement(name = "cite", type = Cite.class)
    private List<Cite> cites;

    @XmlElement(name = "crossref", type = Crossref.class)
    private List<Crossref> crossrefs;

}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/results/dblp/processor/DblpIncollectionsProcessorBasic.java
package com.eresearch.repositorer.transformer.results.dblp.processor;

import com.eresearch.repositorer.domain.record.Entry;
import com.eresearch.repositorer.dto.dblp.response.DblpAuthor;
import com.eresearch.repositorer.dto.dblp.response.generated.Dblp;
import com.eresearch.repositorer.dto.dblp.response.generated.Incollection;
import com.eresearch.repositorer.transformer.results.dblp.processor.common.EntryCreator;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.LinkedList;
import java.util.List;

@Component
public class DblpIncollectionsProcessorBasic implements DblpProcessor {

    private final EntryCreator entryCreator;

    @Autowired
    public DblpIncollectionsProcessorBasic(EntryCreator entryCreator) {
        this.entryCreator = entryCreator;
    }

    @Override
    public void doProcessing(List<Entry> entries, Dblp dblp, DblpAuthor dblpAuthor) throws JsonProcessingException {

        List<Incollection> incollections = dblp.getIncollections();

        if (incollections != null && !incollections.isEmpty()) {

            final List<Entry> collectedEntries = new LinkedList<>();

            for (Incollection incollection : incollections) {

                entryCreator.create(dblpAuthor, collectedEntries, incollection);
            }

            entries.addAll(collectedEntries);
        }
    }

    @Override
    public boolean allowProcessing() {
        return true;
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/transformer/results/dblp/processor/authors/AuthorsExtractor.java
package com.eresearch.repositorer.transformer.results.dblp.processor.authors;

import com.eresearch.repositorer.domain.record.Author;
import com.eresearch.repositorer.transformer.results.dblp.processor.common.CommonDblpSource;
import com.eresearch.repositorer.transformer.results.dblp.processor.common.ObjectAcceptor;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public interface AuthorsExtractor {

    default Set<Author> extractAuthors(Object source) {

        CommonDblpSource commonDblpSource = ObjectAcceptor.isAcceptedObject(source);

        Set<com.eresearch.repositorer.domain.record.Author> entryAuthors
                = new LinkedHashSet<>();

        List<com.eresearch.repositorer.dto.dblp.response.generated.Author> authors = commonDblpSource.getAuthors();

        if (authors != null && !authors.isEmpty()) {

            for (com.eresearch.repositorer.dto.dblp.response.generated.Author author : authors) {

                String authorFullName = author.getValue();

                String[] splittedAuthorFullName = authorFullName.split(" ");

                if (splittedAuthorFullName.length == 2) {

                    entryAuthors.add(new com.eresearch.repositorer.domain.record.Author(
                            splittedAuthorFullName[0],
                            null,
                            splittedAuthorFullName[1]));

                } else if (splittedAuthorFullName.length == 3) {

                    entryAuthors.add(new com.eresearch.repositorer.domain.record.Author(
                            splittedAuthorFullName[0],
                            splittedAuthorFullName[1],
                            splittedAuthorFullName[2]));

                } else {

                    entryAuthors.add(new com.eresearch.repositorer.domain.record.Author(
                            null,
                            null,
                            authorFullName));
                }
            }
        }
        return entryAuthors;
    }

}
<file_sep>/src/main/java/com/eresearch/repositorer/dto/scopus/response/ScopusSearchViewEntry.java
package com.eresearch.repositorer.dto.scopus.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import java.util.Collection;
import java.util.List;

@EqualsAndHashCode(of = {"dcIdentifier", "dcTitle"})
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ScopusSearchViewEntry {

    @JsonProperty("@force-array")
    private String forceArray;

    @JsonProperty("error")
    private String error;

    @JsonProperty("@_fa")
    private String fa;

    @JsonProperty("link")
    private Collection<ScopusSearchViewLink> links;

    @JsonProperty("prism:url")
    private String prismUrl;

    @JsonProperty("dc:identifier")
    private String dcIdentifier;

    @JsonProperty("eid")
    private String eid;

    @JsonProperty("dc:title")
    private String dcTitle;

    @JsonProperty("prism:aggregationType")
    private String prismAggregationType;

    @JsonProperty("citedby-count")
    private String citedByCount;

    @JsonProperty("prism:publicationName")
    private String prismPublicationName;

    @JsonProperty("prism:isbn")
    private List<ScopusSearchPrismIsbn> prismIsbns;

    @JsonProperty("prism:issn")
    private String prismIssn;

    @JsonProperty("prism:eIssn")
    private String prismEissn;

    @JsonProperty("prism:volume")
    private String prismVolume;

    @JsonProperty("prism:issueIdentifier")
    private String prismIssueIdentifier;

    @JsonProperty("prism:pageRange")
    private String prismPageRange;

    @JsonProperty("prism:coverDate")
    private String prismCoverDate;

    @JsonProperty("prism:coverDisplayDate")
    private String prismCoverDisplayDate;

    @JsonProperty("prism:doi")
    private String prismDoi;

    @JsonProperty("pii")
    private String pii;

    @JsonProperty("pubmed-id")
    private String pubmedId;

    @JsonProperty("orcid")
    private String orcId;

    @JsonProperty("dc:creator")
    private String dcCreator;

    @JsonProperty("affiliation")
    private Collection<ScopusSearchAffiliation> affiliations;

    @JsonProperty("author")
    private Collection<ScopusSearchAuthor> authors;

    @JsonProperty("author-count")
    private ScopusSearchAuthorCount authorCount;

    @JsonProperty("dc:description")
    private String dcDescription;

    @JsonProperty("authkeywords")
    private String authorKeywords;

    @JsonProperty("article-number")
    private String articleNumber;

    @JsonProperty("subtype")
    private String subtype;

    @JsonProperty("subtypeDescription")
    private String subtypeDescription;

    @JsonProperty("source-id")
    private String sourceId;

    @JsonProperty("fund-acr")
    private String fundingAgencyAcronym;

    @JsonProperty("fund-no")
    private String fundingAgencyIdentification;

    @JsonProperty("fund-sponsor")
    private String fundingAgencyName;

    @JsonProperty("message")
    private String message;

    @JsonProperty("openaccess")
    private String openAccess;

    @JsonProperty("openaccessFlag")
    private String openAccessFlag;
}
<file_sep>/src/main/java/com/eresearch/repositorer/validator/ErrorReportFilenameDtoValidator.java
package com.eresearch.repositorer.validator;

import com.eresearch.repositorer.dto.repositorer.request.ErrorReportFilenameDto;
import com.eresearch.repositorer.exception.data.RepositorerValidationException;
import com.eresearch.repositorer.exception.error.RepositorerError;
import lombok.extern.log4j.Log4j;
import org.springframework.stereotype.Component;

@Component
@Log4j
public class ErrorReportFilenameDtoValidator implements Validator<ErrorReportFilenameDto> {

    @Override
    public void validate(ErrorReportFilenameDto data) throws RepositorerValidationException {

        if (data.getFilename() == null
                || data.getFilename().isEmpty()) {

            log.error("ErrorReportFilenameDtoValidator#validate --- error occurred (first validation) --- errorReportFilenameDto = " + data);
            throw new RepositorerValidationException(RepositorerError.INVALID_DATA_ERROR, RepositorerError.INVALID_DATA_ERROR.getMessage());
        }
    }
}
<file_sep>/src/main/java/com/eresearch/repositorer/domain/error/ErrorReport.java
package com.eresearch.repositorer.domain.error;


import com.eresearch.repositorer.deserializer.InstantDeserializer;
import com.eresearch.repositorer.exception.error.RepositorerError;
import com.eresearch.repositorer.serializer.InstantSerializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.*;

import java.io.Serializable;
import java.time.Instant;

/**
 * This domain class holds info about error(s) raised-produced in our service.
 */

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ErrorReport implements Serializable {

    private String id;

    @JsonDeserialize(using = InstantDeserializer.class)
    @JsonSerialize(using = InstantSerializer.class)
    private Instant createdAt;
    private String exceptionToString;
    private RepositorerError repositorerError;
    private String crashedComponentName;
    private String errorStacktrace;

    private String failedMessage;
}
