/* * (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.directory; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.schema.types.Field; import org.nuxeo.ecm.core.schema.types.Schema; import org.nuxeo.ecm.core.schema.types.Type; import org.nuxeo.ecm.core.schema.types.primitives.DateType; import org.nuxeo.runtime.api.Framework; /** * Helper to load data from a CSV file. * <p> * The actual consumer of rows is a parameter passed by the caller. * * @since 8.4 */ public class DirectoryCSVLoader { private static final Log log = LogFactory.getLog(DirectoryCSVLoader.class); /** * The special CSV value ({@value}) used to denote that a {@code null} should be used for a value. */ public static final String CSV_NULL_MARKER = "__NULL__"; private DirectoryCSVLoader() { } /** * Loads the CSV data file based on the provided schema, and creates the corresponding entries using the provided * loader. * * @param dataFileName the file name containing CSV data * @param delimiter the CSV column separator * @param schema the data schema * @param loader the actual consumer of loaded rows * @since 8.4 */ public static void loadData(String dataFileName, char delimiter, Schema schema, Consumer<Map<String, Object>> loader) throws DirectoryException { try (InputStream in = getResource(dataFileName); // CSVParser csvParser = new CSVParser(new InputStreamReader(in, "UTF-8"), CSVFormat.DEFAULT.withDelimiter(delimiter).withHeader())) { Map<String, Integer> header = csvParser.getHeaderMap(); List<Field> fields = new ArrayList<>(); for (String columnName : header.keySet()) { Field field = schema.getField(columnName.trim()); if (field == null) { throw new DirectoryException("Column not found: " + columnName + " in schema: " + schema.getName()); } fields.add(field); } int lineno = 1; // header was first line for (CSVRecord record : csvParser) { lineno++; if (record.size() == 0 || record.size() == 1 && StringUtils.isBlank(record.get(0))) { // NXP-2538: allow columns with only one value but skip empty lines continue; } if (!record.isConsistent()) { log.error("Invalid column count while reading CSV file: " + dataFileName + ", line: " + lineno + ", values: " + record); continue; } Map<String, Object> map = new HashMap<String, Object>(); for (int i = 0; i < header.size(); i++) { Field field = fields.get(i); String value = record.get(i); Object v = CSV_NULL_MARKER.equals(value) ? null : decode(field, value); map.put(field.getName().getPrefixedName(), v); } loader.accept(map); } } catch (IOException e) { throw new DirectoryException("Read error while reading data file: " + dataFileName, e); } } protected static Object decode(Field field, String value) { Type type = field.getType(); if (type instanceof DateType) { // compat with earlier code, interpret in the local timezone and not UTC Calendar cal = new GregorianCalendar(); cal.setTime(Timestamp.valueOf(value)); return cal; } else { return type.decode(value); } } @SuppressWarnings("resource") protected static InputStream getResource(String name) { InputStream in = DirectoryCSVLoader.class.getClassLoader().getResourceAsStream(name); if (in == null) { in = Framework.getResourceLoader().getResourceAsStream(name); if (in == null) { throw new DirectoryException("Data file not found: " + name); } } return in; } }