TextImportTemplate.java

package org.dynamoframework.importer.template;

/*-
 * #%L
 * Dynamo Framework
 * %%
 * Copyright (C) 2014 - 2024 Open Circle Solutions
 * %%
 * Licensed 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.
 * #L%
 */

import lombok.extern.slf4j.Slf4j;
import org.dynamoframework.exception.OCSImportException;
import org.dynamoframework.service.MessageService;
import org.dynamoframework.configuration.DynamoPropertiesHolder;

import java.util.*;

/**
 * Template class for importing data from a text (CSV or fixed width) file and
 * translating it to an entity
 *
 * @param <ID> the type of the ID of the entity
 * @param <T>  the type of the entity
 * @author bas.rutten
 */
@Slf4j
public abstract class TextImportTemplate<ID, T> {

	private final List<String[]> lines;

	private final boolean checkForDuplicates;

	private final List<String> errors;

	private final MessageService messageService;

	private final Set<ID> keys = new HashSet<>();

	/**
	 * Constructor
	 *
	 * @param lines              the lines that make up the text file
	 * @param errors             the errors that have occurred so far
	 * @param checkForDuplicates whether to check for duplicate rows
	 */
	protected TextImportTemplate(MessageService messageService, List<String[]> lines, List<String> errors,
								 boolean checkForDuplicates) {
		this.lines = lines;
		this.errors = errors;
		this.checkForDuplicates = checkForDuplicates;
		this.messageService = messageService;
	}

	/**
	 * Indicates whether the row is appropriate and can be processed
	 *
	 * @param row the representation of the row
	 * @return true if this is the case, false otherwise
	 */
	protected abstract boolean isAppropriateRow(String[] row);

	/**
	 * Processes a row
	 *
	 * @param rowNum the row number of the row
	 * @param row    the row (as an array of strings denoting the individual field
	 *               values)
	 * @return the result of the processing
	 */
	protected abstract T process(int rowNum, String[] row);

	/**
	 * Extracts a primary key value from a row
	 *
	 * @param row the row
	 * @return the key value
	 */
	protected abstract ID getKeyFromRow(T row);

	/**
	 * Executes the template
	 *
	 * @return the result of the processing
	 */
	public List<T> execute() {
		List<T> results = new ArrayList<>();
		for (int i = 0; i < lines.size(); i++) {
			String[] row = lines.get(i);
			if (row != null && i > 0 && isAppropriateRow(row)) {
				try {
					executeRow(i, row, results);
				} catch (OCSImportException ex) {
					log.error(ex.getMessage(), ex);
					// catch errors on a record by record level
					errors.add(String.format("Row %d: %s", i + 1, ex.getMessage()));
				}
			}
		}

		return results;
	}

	/**
	 * Processes a single row
	 *
	 * @param i       the index of the row
	 * @param row     the field values that together from the row
	 * @param results the list of current results
	 */
	@SuppressWarnings("unchecked")
	private void executeRow(int i, String[] row, List<T> results) {
		T t = process(i, row);
		ID key = getKeyFromRow(t);

		if (checkForDuplicates) {

			// lower case comparison for Strings
			if (key instanceof String) {
				key = (ID) ((String) key).toLowerCase();
			}

			if (!keys.contains(key)) {
				keys.add(key);
				results.add(t);
			} else {
				errors.add(messageService.getMessage("dynamoframework.duplicate.row", Locale.US, i + 1, key));
			}
		} else {
			results.add(t);
		}
	}
}