net.iaeste.iws.core.services.ExchangeCSVService.java Source code

Java tutorial

Introduction

Here is the source code for net.iaeste.iws.core.services.ExchangeCSVService.java

Source

/*
 * Licensed to IAESTE A.s.b.l. (IAESTE) under one or more contributor
 * license agreements.  See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership. The Authors
 * (See the AUTHORS file distributed with this work) licenses this file to
 * You under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a
 * copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.iaeste.iws.core.services;

import static net.iaeste.iws.common.utils.LogUtil.formatLogMessage;

import net.iaeste.iws.api.constants.IWSErrors;
import net.iaeste.iws.api.dtos.Address;
import net.iaeste.iws.api.dtos.Country;
import net.iaeste.iws.api.dtos.exchange.CSVProcessingErrors;
import net.iaeste.iws.api.dtos.exchange.Employer;
import net.iaeste.iws.api.dtos.exchange.Offer;
import net.iaeste.iws.api.enums.exchange.OfferFields;
import net.iaeste.iws.api.enums.exchange.OfferState;
import net.iaeste.iws.api.exceptions.IWSException;
import net.iaeste.iws.api.requests.exchange.OfferCSVUploadRequest;
import net.iaeste.iws.api.responses.exchange.OfferCSVUploadResponse;
import net.iaeste.iws.api.util.Verifications;
import net.iaeste.iws.common.configuration.Settings;
import net.iaeste.iws.core.transformers.CommonTransformer;
import net.iaeste.iws.core.transformers.ExchangeTransformer;
import net.iaeste.iws.persistence.AccessDao;
import net.iaeste.iws.persistence.Authentication;
import net.iaeste.iws.persistence.ExchangeDao;
import net.iaeste.iws.persistence.entities.GroupEntity;
import net.iaeste.iws.persistence.entities.exchange.EmployerEntity;
import net.iaeste.iws.persistence.entities.exchange.OfferEntity;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author  Kim Jensen / last $Author:$
 * @version $Revision:$ / $Date:$
 * @since   IWS 1.1
 */
public final class ExchangeCSVService extends CommonCSVService<ExchangeDao> {

    private static final Logger LOG = LoggerFactory.getLogger(ExchangeCSVService.class);

    private final AccessDao accessDao;

    public ExchangeCSVService(final Settings settings, final ExchangeDao dao, final AccessDao accessDao) {
        super(settings, dao);

        this.accessDao = accessDao;
    }

    public OfferCSVUploadResponse uploadOffers(final Authentication authentication,
            final OfferCSVUploadRequest request) {
        final Map<String, OfferCSVUploadResponse.ProcessingResult> processingResult = new HashMap<>();
        final OfferCSVUploadResponse response = new OfferCSVUploadResponse();
        final Map<String, CSVProcessingErrors> errors = new HashMap<>();

        try (Reader reader = new StringReader(request.getCsv());
                CSVParser parser = getDefaultCsvParser(reader, request.getDelimiter().getDescription())) {
            final Set<String> headers = readHeader(parser);
            final Set<String> expectedHeaders = new HashSet<>(createFirstRow(OfferFields.Type.UPLOAD));
            if (headers.containsAll(expectedHeaders)) {
                for (final CSVRecord record : parser.getRecords()) {
                    process(processingResult, errors, authentication, record);
                }
            } else {
                throw new IWSException(IWSErrors.PROCESSING_FAILURE, "Invalid CSV header");
            }
        } catch (IllegalArgumentException e) {
            throw new IWSException(IWSErrors.PROCESSING_FAILURE, "The header is invalid: " + e.getMessage() + '.',
                    e);
        } catch (IOException e) {
            throw new IWSException(IWSErrors.PROCESSING_FAILURE, "CSV upload processing failed", e);
        }

        response.setProcessingResult(processingResult);
        response.setErrors(errors);
        return response;
    }

    private static Set<String> readHeader(final CSVParser parser) {
        final Map<String, Integer> map = parser.getHeaderMap();

        if (map == null) {
            throw new IWSException(IWSErrors.CSV_HEADER_ERROR, "The CSV did not have a valid header.");
        }

        return map.keySet();
    }

    private static Offer extractOfferFromCSV(final Authentication authentication, final Map<String, String> errors,
            final CSVRecord record) {
        // Extract the Country from the Authentication Information
        final Country country = CommonTransformer.transform(authentication.getGroup().getCountry());

        // Read the Address from the CSV and assign the found Country to it
        final Address address = CommonTransformer.addressFromCsv(record, errors);
        address.setCountry(country);

        // Read the Employer from the CSV, and assign the transformed Address from it
        final Employer employer = ExchangeTransformer.employerFromCsv(record, errors);
        employer.setAddress(address);

        // Read the Offer from the CSV, and assign the transformed Employer to it
        final Offer offer = ExchangeTransformer.offerFromCsv(record, errors);
        offer.setEmployer(employer);

        // As all the Setters from the Offer has been invoked, all errors for
        // this Offer has already been caught. Invoking the validator will only
        // generate additional false error messages, since the validator will
        // apply null checks to those values that failed the Setter checks and
        // has not been set. Example, if the RefNo is wrong, then the Setter
        // will reject it but not set it, the Validator will see it as null and
        // set that as error as well, which is incorrect.
        return offer;
    }

    private void process(final Map<String, OfferCSVUploadResponse.ProcessingResult> processingResult,
            final Map<String, CSVProcessingErrors> errors, final Authentication authentication,
            final CSVRecord record) {
        final Map<String, String> conversionErrors = new HashMap<>(0);
        String refNo = "";

        try {
            refNo = record.get(OfferFields.REF_NO.getField());
            final Offer csvOffer = extractOfferFromCSV(authentication, conversionErrors, record);

            final CSVProcessingErrors validationErrors = new CSVProcessingErrors(conversionErrors);
            if (validationErrors.isEmpty()) {
                processingResult.put(refNo, processOffer(authentication, refNo, csvOffer));
            } else {
                LOG.warn(formatLogMessage(authentication,
                        "CSV Offer with RefNo " + refNo + " has some Problems: " + conversionErrors));
                processingResult.put(refNo, OfferCSVUploadResponse.ProcessingResult.ERROR);
                errors.put(refNo, validationErrors);
            }
        } catch (IllegalArgumentException | IWSException e) {
            LOG.debug(e.getMessage(), e);
            LOG.warn(formatLogMessage(authentication,
                    "CSV Offer with RefNo " + refNo + " has a Problem: " + e.getMessage()));
            processingResult.put(refNo, OfferCSVUploadResponse.ProcessingResult.ERROR);
            if (errors.containsKey(refNo)) {
                errors.get(refNo).put("general", e.getMessage());
            } else {
                final CSVProcessingErrors generalError = new CSVProcessingErrors();
                generalError.put("general", e.getMessage());
                if (!conversionErrors.isEmpty()) {
                    generalError.putAll(conversionErrors);
                }
                errors.put(refNo, generalError);
            }
        }
    }

    private OfferCSVUploadResponse.ProcessingResult processOffer(final Authentication authentication,
            final String refNo, final Offer csvOffer) {
        final OfferEntity existingEntity = dao.findOfferByRefNo(authentication, refNo);
        final OfferEntity newEntity = ExchangeTransformer.transform(csvOffer);
        final OfferCSVUploadResponse.ProcessingResult result;

        if (existingEntity != null) {
            permissionCheck(authentication, authentication.getGroup());

            //keep original offer state
            newEntity.setStatus(existingEntity.getStatus());

            csvOffer.getEmployer().setEmployerId(existingEntity.getEmployer().getExternalId());
            final EmployerEntity employerEntity = process(authentication, csvOffer.getEmployer());
            existingEntity.setEmployer(employerEntity);

            newEntity.setExternalId(existingEntity.getExternalId());
            dao.persist(authentication, existingEntity, newEntity);
            LOG.info(formatLogMessage(authentication, "CSV Update of Offer with RefNo '%s' completed.",
                    newEntity.getRefNo()));
            result = OfferCSVUploadResponse.ProcessingResult.UPDATED;
        } else {
            // First, we need an Employer for our new Offer. The Process
            // method will either find an existing Employer or create a
            // new one.
            final EmployerEntity employer = process(authentication, csvOffer.getEmployer());

            // Add the Group to the Offer, otherwise our ref.no checks will fail
            employer.setGroup(authentication.getGroup());

            newEntity.setEmployer(employer);

            ExchangeService.verifyRefnoValidity(newEntity);

            newEntity.setExchangeYear(Verifications.calculateExchangeYear());
            // Add the employer to the Offer
            newEntity.setEmployer(employer);
            // Set the Offer status to New
            newEntity.setStatus(OfferState.NEW);

            // Persist the Offer with history
            dao.persist(authentication, newEntity);
            LOG.info(formatLogMessage(authentication, "CSV Import of Offer with RefNo '%s' completed.",
                    newEntity.getRefNo()));
            result = OfferCSVUploadResponse.ProcessingResult.ADDED;
        }

        return result;
    }

    /**
     * Processes an Employer from the CSV file. This is done by first trying to
     * lookup the Employer via the unique characteristics for an Employer - and
     * only of no existing records is found, will a new record be created. If
     * a record is found, the changes will be merged and potentially also
     * persisted.<br />
     *   If more than one Employer is found, then an Identification Exception is
     * thrown.
     *
     * @param authentication The users Authentication information
     * @param employer       The Employer to find / create
     * @return Employer Entity found or created
     */
    private EmployerEntity process(final Authentication authentication, final Employer employer) {
        // If the Employer provided is having an Id set - then we need to update
        // the existing record, otherwise we will try to see if we can find a
        // similar Employer and update it. If we can neither find an Employer by
        // the Id, not the unique information - then we will create a new one.
        EmployerEntity entity;
        if (employer.getEmployerId() != null) {
            // Id exists, so we simply find the Employer based on that
            entity = dao.findEmployer(authentication, employer.getEmployerId());
            LOG.debug(formatLogMessage(authentication, "Employer lookup for Id '%s' gave '%s'.",
                    employer.getEmployerId(), entity.getName()));
        } else {
            // No Id was set, so we're trying to find the Employer based on the
            // Unique information
            entity = dao.findUniqueEmployer(authentication, employer);
            LOG.debug(formatLogMessage(authentication, "Unique Employer for name '%s' gave '%s'.",
                    employer.getName(), (entity != null) ? entity.getName() : "null"));
        }

        if (entity == null) {
            entity = ExchangeTransformer.transform(employer);
            final GroupEntity nationalGroup = accessDao.findNationalGroup(authentication.getUser());
            entity.setGroup(nationalGroup);
            processAddress(authentication, entity.getAddress());
            dao.persist(authentication, entity);
            LOG.info(formatLogMessage(authentication, "Have added the Employer '%s' for '%s'.", employer.getName(),
                    authentication.getGroup().getGroupName()));
        } else {
            final EmployerEntity updated = ExchangeTransformer.transform(employer);
            processAddress(authentication, entity.getAddress(), employer.getAddress());
            dao.persist(authentication, entity, updated);
            LOG.info(formatLogMessage(authentication, "Have updated the Employer '%s' for '%s'.",
                    employer.getName(), authentication.getGroup().getGroupName()));
        }

        return entity;
    }

    private static CSVParser getDefaultCsvParser(final Reader input, final char delimiter) {
        try {
            return CSVFormat.RFC4180.withDelimiter(delimiter).withHeader().parse(input);
        } catch (IOException e) {
            throw new IWSException(IWSErrors.PROCESSING_FAILURE, "Creating CSVParser failed", e);
        }
    }
}