/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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 org.apache.camel.dataformat.csv; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.camel.Exchange; import org.apache.camel.util.IOHelper; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; /** * This class unmarshal CSV into lists or maps depending on the configuration. */ abstract class CsvUnmarshaller { protected final CSVFormat format; protected final CsvRecordConverter<?> converter; private CsvUnmarshaller(CSVFormat format, CsvDataFormat dataFormat) { this.format = format; this.converter = extractConverter(dataFormat); } public static CsvUnmarshaller create(CSVFormat format, CsvDataFormat dataFormat) { // If we want to use maps, thus the header must be either fixed or automatic if (dataFormat.isUseMaps() && format.getHeader() == null) { format = format.withHeader(); } // If we want to skip the header record it must automatic otherwise it's not working if (format.getSkipHeaderRecord() && format.getHeader() == null) { format = format.withHeader(); } if (dataFormat.isLazyLoad()) { return new StreamCsvUnmarshaller(format, dataFormat); } return new BulkCsvUnmarshaller(format, dataFormat); } /** * Unmarshal the CSV * * @param exchange Exchange (used for accessing type converter) * @param inputStream Input CSV stream * @return Unmarshalled CSV * @throws IOException if the stream cannot be read properly */ public abstract Object unmarshal(Exchange exchange, InputStream inputStream) throws IOException; private static CsvRecordConverter<?> extractConverter(CsvDataFormat dataFormat) { if (dataFormat.getRecordConverter() != null) { return dataFormat.getRecordConverter(); } else if (dataFormat.isUseMaps()) { return CsvRecordConverters.mapConverter(); } else { return CsvRecordConverters.listConverter(); } } //region Implementations /** * This class reads all the CSV into one big list. */ private static final class BulkCsvUnmarshaller extends CsvUnmarshaller { private BulkCsvUnmarshaller(CSVFormat format, CsvDataFormat dataFormat) { super(format, dataFormat); } public Object unmarshal(Exchange exchange, InputStream inputStream) throws IOException { CSVParser parser = new CSVParser(new InputStreamReader(inputStream, IOHelper.getCharsetName(exchange)), format); try { return asList(parser.iterator(), converter); } finally { IOHelper.close(parser); } } private <T> List<T> asList(Iterator<CSVRecord> iterator, CsvRecordConverter<T> converter) { List<T> answer = new ArrayList<T>(); while (iterator.hasNext()) { answer.add(converter.convertRecord(iterator.next())); } return answer; } } /** * This class streams the content of the CSV */ @SuppressWarnings("unchecked") private static final class StreamCsvUnmarshaller extends CsvUnmarshaller { private StreamCsvUnmarshaller(CSVFormat format, CsvDataFormat dataFormat) { super(format, dataFormat); } @Override public Object unmarshal(Exchange exchange, InputStream inputStream) throws IOException { Reader reader = null; try { reader = new InputStreamReader(inputStream, IOHelper.getCharsetName(exchange)); CSVParser parser = new CSVParser(reader, format); CsvIterator answer = new CsvIterator(parser, converter); // add to UoW so we can close the iterator so it can release any resources exchange.addOnCompletion(new CsvUnmarshalOnCompletion(answer)); return answer; } catch (Exception e) { IOHelper.close(reader); throw e; } } } /** * This class converts the CSV iterator into the proper result type. * * @param <T> Converted type */ private static final class CsvIterator<T> implements Iterator<T>, Closeable { private final CSVParser parser; private final Iterator<CSVRecord> iterator; private final CsvRecordConverter<T> converter; private CsvIterator(CSVParser parser, CsvRecordConverter<T> converter) { this.parser = parser; this.iterator = parser.iterator(); this.converter = converter; } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public T next() { return converter.convertRecord(iterator.next()); } @Override public void remove() { iterator.remove(); } @Override public void close() throws IOException { if (!parser.isClosed()) { parser.close(); } } } //endregion }