/** * 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.InputStream; import java.io.OutputStream; import java.util.Arrays; import org.apache.camel.Exchange; import org.apache.camel.spi.DataFormat; import org.apache.camel.spi.DataFormatName; import org.apache.camel.support.ServiceSupport; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.QuoteMode; /** * CSV Data format. * <p/> * By default, columns are autogenerated in the resulting CSV. Subsequent * messages use the previously created columns with new fields being added at * the end of the line. Thus, field order is the same from message to message. * Autogeneration can be disabled. In this case, only the fields defined in * csvConfig are written on the output. */ public class CsvDataFormat extends ServiceSupport implements DataFormat, DataFormatName { // CSV format options private CSVFormat format = CSVFormat.DEFAULT; private boolean commentMarkerDisabled; private Character commentMarker; private Character delimiter; private boolean escapeDisabled; private Character escape; private boolean headerDisabled; private String[] header; private Boolean allowMissingColumnNames; private Boolean ignoreEmptyLines; private Boolean ignoreSurroundingSpaces; private boolean nullStringDisabled; private String nullString; private boolean quoteDisabled; private Character quote; private QuoteMode quoteMode; private boolean recordSeparatorDisabled; private String recordSeparator; private Boolean skipHeaderRecord; private Boolean trim; private Boolean ignoreHeaderCase; private Boolean trailingDelimiter; // Unmarshal options private boolean lazyLoad; private boolean useMaps; private CsvRecordConverter<?> recordConverter; private volatile CsvMarshaller marshaller; private volatile CsvUnmarshaller unmarshaller; public CsvDataFormat() { } public CsvDataFormat(CSVFormat format) { setFormat(format); } @Override public String getDataFormatName() { return "csv"; } public void marshal(Exchange exchange, Object object, OutputStream outputStream) throws Exception { marshaller.marshal(exchange, object, outputStream); } public Object unmarshal(Exchange exchange, InputStream inputStream) throws Exception { return unmarshaller.unmarshal(exchange, inputStream); } @Override protected void doStart() throws Exception { marshaller = CsvMarshaller.create(getActiveFormat(), this); unmarshaller = CsvUnmarshaller.create(getActiveFormat(), this); } @Override protected void doStop() throws Exception { // noop } CSVFormat getActiveFormat() { CSVFormat answer = format; if (commentMarkerDisabled) { answer = answer.withCommentMarker(null); // null disables the comment marker } else if (commentMarker != null) { answer = answer.withCommentMarker(commentMarker); } if (delimiter != null) { answer = answer.withDelimiter(delimiter); } if (escapeDisabled) { answer = answer.withEscape(null); // null disables the escape } else if (escape != null) { answer = answer.withEscape(escape); } if (headerDisabled) { answer = answer.withHeader((String[]) null); // null disables the header } else if (header != null) { answer = answer.withHeader(header); } if (allowMissingColumnNames != null) { answer = answer.withAllowMissingColumnNames(allowMissingColumnNames); } if (ignoreEmptyLines != null) { answer = answer.withIgnoreEmptyLines(ignoreEmptyLines); } if (ignoreSurroundingSpaces != null) { answer = answer.withIgnoreSurroundingSpaces(ignoreSurroundingSpaces); } if (nullStringDisabled) { answer = answer.withNullString(null); // null disables the null string replacement } else if (nullString != null) { answer = answer.withNullString(nullString); } if (quoteDisabled) { answer = answer.withQuote(null); // null disables quotes } else if (quote != null) { answer = answer.withQuote(quote); } if (quoteMode != null) { answer = answer.withQuoteMode(quoteMode); } if (recordSeparatorDisabled) { answer = answer.withRecordSeparator(null); // null disables the record separator } else if (recordSeparator != null) { answer = answer.withRecordSeparator(recordSeparator); } if (skipHeaderRecord != null) { answer = answer.withSkipHeaderRecord(skipHeaderRecord); } if (trim != null) { answer = answer.withTrim(trim); } if (ignoreHeaderCase != null) { answer = answer.withIgnoreHeaderCase(ignoreHeaderCase); } if (trailingDelimiter != null) { answer = answer.withTrailingDelimiter(trailingDelimiter); } return answer; } //region Getters/Setters /** * Gets the CSV format before applying any changes. * It cannot be {@code null}, the default one is {@link org.apache.commons.csv.CSVFormat#DEFAULT}. * * @return CSV format */ public CSVFormat getFormat() { return format; } /** * Sets the CSV format before applying any changes. * If {@code null}, then {@link org.apache.commons.csv.CSVFormat#DEFAULT} is used instead. * * @param format CSV format * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat * @see org.apache.commons.csv.CSVFormat#DEFAULT */ public CsvDataFormat setFormat(CSVFormat format) { this.format = (format == null) ? CSVFormat.DEFAULT : format; return this; } /** * Sets the CSV format by name before applying any changes. * * @param name CSV format name * @return Current {@code CsvDataFormat}, fluent API * @see #setFormat(org.apache.commons.csv.CSVFormat) * @see org.apache.commons.csv.CSVFormat */ public CsvDataFormat setFormatName(String name) { if (name == null) { setFormat(null); } else if ("DEFAULT".equals(name)) { setFormat(CSVFormat.DEFAULT); } else if ("RFC4180".equals(name)) { setFormat(CSVFormat.RFC4180); } else if ("EXCEL".equals(name)) { setFormat(CSVFormat.EXCEL); } else if ("TDF".equals(name)) { setFormat(CSVFormat.TDF); } else if ("MYSQL".equals(name)) { setFormat(CSVFormat.MYSQL); } else { throw new IllegalArgumentException("Unsupported format"); } return this; } /** * Indicates whether or not the comment markers are disabled. * * @return {@code true} if the comment markers are disabled, {@code false} otherwise */ public boolean isCommentMarkerDisabled() { return commentMarkerDisabled; } /** * Sets whether or not the comment markers are disabled. * * @param commentMarkerDisabled {@code true} if the comment markers are disabled, {@code false} otherwise * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withCommentMarker(java.lang.Character) */ public CsvDataFormat setCommentMarkerDisabled(boolean commentMarkerDisabled) { this.commentMarkerDisabled = commentMarkerDisabled; return this; } /** * Gets the comment marker. * If {@code null} then the default one of the format used. * * @return Comment marker */ public Character getCommentMarker() { return commentMarker; } /** * Sets the comment marker to use. * If {@code null} then the default one of the format used. * * @param commentMarker Comment marker * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withCommentMarker(Character) */ public CsvDataFormat setCommentMarker(Character commentMarker) { this.commentMarker = commentMarker; return this; } /** * Gets the delimiter. * If {@code null} then the default one of the format used. * * @return Delimiter */ public Character getDelimiter() { return delimiter; } /** * Sets the delimiter. * If {@code null} then the default one of the format used. * * @param delimiter Delimiter * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withDelimiter(char) */ public CsvDataFormat setDelimiter(Character delimiter) { this.delimiter = delimiter; return this; } /** * Indicates whether or not the escaping is disabled. * * @return {@code true} if the escaping is disabled, {@code false} otherwise */ public boolean isEscapeDisabled() { return escapeDisabled; } /** * Sets whether or not the escaping is disabled. * * @param escapeDisabled {@code true} if the escaping is disabled, {@code false} otherwise * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withEscape(Character) */ public CsvDataFormat setEscapeDisabled(boolean escapeDisabled) { this.escapeDisabled = escapeDisabled; return this; } /** * Gets the escape character. * If {@code null} then the default one of the format used. * * @return Escape character */ public Character getEscape() { return escape; } /** * Sets the escape character. * If {@code null} then the default one of the format used. * * @param escape Escape character * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withEscape(Character) */ public CsvDataFormat setEscape(Character escape) { this.escape = escape; return this; } /** * Indicates whether or not the headers are disabled. * * @return {@code true} if the headers are disabled, {@code false} otherwise */ public boolean isHeaderDisabled() { return headerDisabled; } /** * Sets whether or not the headers are disabled. * * @param headerDisabled {@code true} if the headers are disabled, {@code false} otherwise * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withHeader(String...) */ public CsvDataFormat setHeaderDisabled(boolean headerDisabled) { this.headerDisabled = headerDisabled; return this; } /** * Gets the header. * If {@code null} then the default one of the format used. If empty then it will be automatically handled. * * @return Header */ public String[] getHeader() { return header; } /** * Gets the header. * If {@code null} then the default one of the format used. If empty then it will be automatically handled. * * @param header Header * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withHeader(String...) */ public CsvDataFormat setHeader(String[] header) { this.header = Arrays.copyOf(header, header.length); return this; } /** * Indicates whether or not missing column names are allowed. * If {@code null} then the default value of the format used. * * @return Whether or not missing column names are allowed */ public Boolean getAllowMissingColumnNames() { return allowMissingColumnNames; } /** * Sets whether or not missing column names are allowed. * If {@code null} then the default value of the format used. * * @param allowMissingColumnNames Whether or not missing column names are allowed * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withAllowMissingColumnNames(boolean) */ public CsvDataFormat setAllowMissingColumnNames(Boolean allowMissingColumnNames) { this.allowMissingColumnNames = allowMissingColumnNames; return this; } /** * Indicates whether or not empty lines must be ignored. * If {@code null} then the default value of the format used. * * @return Whether or not empty lines must be ignored */ public Boolean getIgnoreEmptyLines() { return ignoreEmptyLines; } /** * Sets whether or not empty lines must be ignored. * If {@code null} then the default value of the format used. * * @param ignoreEmptyLines Whether or not empty lines must be ignored * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withIgnoreEmptyLines(boolean) */ public CsvDataFormat setIgnoreEmptyLines(Boolean ignoreEmptyLines) { this.ignoreEmptyLines = ignoreEmptyLines; return this; } /** * Indicates whether or not surrounding spaces must be ignored. * If {@code null} then the default value of the format used. * * @return Whether or not surrounding spaces must be ignored */ public Boolean getIgnoreSurroundingSpaces() { return ignoreSurroundingSpaces; } /** * Sets whether or not surrounding spaces must be ignored. * If {@code null} then the default value of the format used. * * @param ignoreSurroundingSpaces Whether or not surrounding spaces must be ignored * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withIgnoreSurroundingSpaces(boolean) */ public CsvDataFormat setIgnoreSurroundingSpaces(Boolean ignoreSurroundingSpaces) { this.ignoreSurroundingSpaces = ignoreSurroundingSpaces; return this; } /** * Indicates whether or not the null string replacement is disabled. * * @return {@code true} if the null string replacement is disabled, {@code false} otherwise */ public boolean isNullStringDisabled() { return nullStringDisabled; } /** * Sets whether or not the null string replacement is disabled. * * @param nullStringDisabled {@code true} if the null string replacement is disabled, {@code false} otherwise * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withNullString(String) */ public CsvDataFormat setNullStringDisabled(boolean nullStringDisabled) { this.nullStringDisabled = nullStringDisabled; return this; } /** * Gets the null string replacement. * If {@code null} then the default one of the format used. * * @return Null string replacement */ public String getNullString() { return nullString; } /** * Sets the null string replacement. * If {@code null} then the default one of the format used. * * @param nullString Null string replacement * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withNullString(String) */ public CsvDataFormat setNullString(String nullString) { this.nullString = nullString; return this; } /** * Indicates whether or not quotes are disabled. * * @return {@code true} if quotes are disabled, {@code false} otherwise */ public boolean isQuoteDisabled() { return quoteDisabled; } /** * Sets whether or not quotes are disabled * * @param quoteDisabled {@code true} if quotes are disabled, {@code false} otherwise * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withQuote(Character) */ public CsvDataFormat setQuoteDisabled(boolean quoteDisabled) { this.quoteDisabled = quoteDisabled; return this; } /** * Gets the quote character. * If {@code null} then the default one of the format used. * * @return Quote character */ public Character getQuote() { return quote; } /** * Sets the quote character. * If {@code null} then the default one of the format used. * * @param quote Quote character * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withQuote(Character) */ public CsvDataFormat setQuote(Character quote) { this.quote = quote; return this; } /** * Gets the quote mode. * If {@code null} then the default one of the format used. * * @return Quote mode */ public QuoteMode getQuoteMode() { return quoteMode; } /** * Sets the quote mode. * If {@code null} then the default one of the format used. * * @param quoteMode Quote mode * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withQuoteMode(org.apache.commons.csv.QuoteMode) */ public CsvDataFormat setQuoteMode(QuoteMode quoteMode) { this.quoteMode = quoteMode; return this; } /** * Indicates whether or not the record separator is disabled. * * @return {@code true} if the record separator disabled, {@code false} otherwise */ public boolean isRecordSeparatorDisabled() { return recordSeparatorDisabled; } /** * Sets whether or not the record separator is disabled. * * @param recordSeparatorDisabled {@code true} if the record separator disabled, {@code false} otherwise * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withRecordSeparator(String) */ public CsvDataFormat setRecordSeparatorDisabled(boolean recordSeparatorDisabled) { this.recordSeparatorDisabled = recordSeparatorDisabled; return this; } /** * Gets the record separator. * If {@code null} then the default one of the format used. * * @return Record separator */ public String getRecordSeparator() { return recordSeparator; } /** * Sets the record separator. * If {@code null} then the default one of the format used. * * @param recordSeparator Record separator * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withRecordSeparator(String) */ public CsvDataFormat setRecordSeparator(String recordSeparator) { this.recordSeparator = recordSeparator; return this; } /** * Indicates whether or not header record must be skipped. * If {@code null} then the default value of the format used. * * @return Whether or not header record must be skipped */ public Boolean getSkipHeaderRecord() { return skipHeaderRecord; } /** * Sets whether or not header record must be skipped. * If {@code null} then the default value of the format used. * * @param skipHeaderRecord Whether or not header record must be skipped * @return Current {@code CsvDataFormat}, fluent API * @see org.apache.commons.csv.CSVFormat#withSkipHeaderRecord(boolean) */ public CsvDataFormat setSkipHeaderRecord(Boolean skipHeaderRecord) { this.skipHeaderRecord = skipHeaderRecord; return this; } /** * Indicates whether or not the unmarshalling should lazily load the records. * * @return {@code true} for lazy loading, {@code false} otherwise */ public boolean isLazyLoad() { return lazyLoad; } /** * Indicates whether or not the unmarshalling should lazily load the records. * * @param lazyLoad {@code true} for lazy loading, {@code false} otherwise * @return Current {@code CsvDataFormat}, fluent API */ public CsvDataFormat setLazyLoad(boolean lazyLoad) { this.lazyLoad = lazyLoad; return this; } /** * Indicates whether or not the unmarshalling should produce maps instead of lists. * * @return {@code true} for maps, {@code false} for lists */ public boolean isUseMaps() { return useMaps; } /** * Sets whether or not the unmarshalling should produce maps instead of lists. * * @param useMaps {@code true} for maps, {@code false} for lists * @return Current {@code CsvDataFormat}, fluent API */ public CsvDataFormat setUseMaps(boolean useMaps) { this.useMaps = useMaps; return this; } /** * Gets the record converter to use. If {@code null} then it will use {@link CsvDataFormat#isUseMaps()} for finding * the proper converter. * * @return Record converter to use */ public CsvRecordConverter<?> getRecordConverter() { return recordConverter; } /** * Sets the record converter to use. If {@code null} then it will use {@link CsvDataFormat#isUseMaps()} for finding * the proper converter. * * @param recordConverter Record converter to use * @return Current {@code CsvDataFormat}, fluent API */ public CsvDataFormat setRecordConverter(CsvRecordConverter<?> recordConverter) { this.recordConverter = recordConverter; return this; } //endregion /** * Sets whether or not to trim leading and trailing blanks. * <p> * If {@code null} then the default value of the format used. * </p> * * @param trim whether or not to trim leading and trailing blanks. * <code>null</code> value allowed. * @return Current {@code CsvDataFormat}, fluent API. */ public CsvDataFormat setTrim(Boolean trim) { this.trim = trim; return this; } /** * Indicates whether or not to trim leading and trailing blanks. * * @return {@link Boolean#TRUE} if leading and trailing blanks should be * trimmed. {@link Boolean#FALSE} otherwise. Could return * <code>null</code> if value has NOT been set. */ public Boolean getTrim() { return trim; } /** * Sets whether or not to ignore case when accessing header names. * <p> * If {@code null} then the default value of the format used. * </p> * * @param ignoreHeaderCase whether or not to ignore case when accessing header names. * <code>null</code> value allowed. * @return Current {@code CsvDataFormat}, fluent API. */ public CsvDataFormat setIgnoreHeaderCase(Boolean ignoreHeaderCase) { this.ignoreHeaderCase = ignoreHeaderCase; return this; } /** * Indicates whether or not to ignore case when accessing header names. * * @return {@link Boolean#TRUE} if case should be ignored when accessing * header name. {@link Boolean#FALSE} otherwise. Could return * <code>null</code> if value has NOT been set. */ public Boolean getIgnoreHeaderCase() { return ignoreHeaderCase; } /** * Sets whether or not to add a trailing delimiter. * <p> * If {@code null} then the default value of the format used. * </p> * * @param trailingDelimiter whether or not to add a trailing delimiter. * @return Current {@code CsvDataFormat}, fluent API. */ public CsvDataFormat setTrailingDelimiter(Boolean trailingDelimiter) { this.trailingDelimiter = trailingDelimiter; return this; } /** * Indicates whether or not to add a trailing delimiter. * * @return {@link Boolean#TRUE} if a trailing delimiter should be added. * {@link Boolean#FALSE} otherwise. Could return <code>null</code> * if value has NOT been set. */ public Boolean getTrailingDelimiter() { return trailingDelimiter; } }