CSVReaderService.java

  1. /*
  2.  * Copyright (c) 2002-2022, City of Paris
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  *
  9.  *  1. Redistributions of source code must retain the above copyright notice
  10.  *     and the following disclaimer.
  11.  *
  12.  *  2. Redistributions in binary form must reproduce the above copyright notice
  13.  *     and the following disclaimer in the documentation and/or other materials
  14.  *     provided with the distribution.
  15.  *
  16.  *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
  17.  *     contributors may be used to endorse or promote products derived from
  18.  *     this software without specific prior written permission.
  19.  *
  20.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21.  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
  24.  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26.  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27.  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28.  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30.  * POSSIBILITY OF SUCH DAMAGE.
  31.  *
  32.  * License 1.0
  33.  */
  34. package fr.paris.lutece.portal.service.csv;

  35. import java.io.ByteArrayInputStream;
  36. import java.io.FileReader;
  37. import java.io.IOException;
  38. import java.io.InputStream;
  39. import java.io.InputStreamReader;
  40. import java.io.Reader;
  41. import java.util.ArrayList;
  42. import java.util.Collections;
  43. import java.util.Iterator;
  44. import java.util.List;
  45. import java.util.Locale;

  46. import org.apache.commons.collections.CollectionUtils;
  47. import org.apache.commons.fileupload.FileItem;

  48. import au.com.bytecode.opencsv.CSVReader;
  49. import fr.paris.lutece.portal.business.file.File;
  50. import fr.paris.lutece.portal.business.physicalfile.PhysicalFile;
  51. import fr.paris.lutece.portal.business.physicalfile.PhysicalFileHome;
  52. import fr.paris.lutece.portal.service.i18n.I18nService;
  53. import fr.paris.lutece.portal.service.util.AppLogService;
  54. import fr.paris.lutece.portal.service.util.AppPropertiesService;
  55. import fr.paris.lutece.util.stream.StreamUtil;

  56. /**
  57.  * Service to get data from a CSV file. The CSV might be a physical file, or a memory file. Implementations can either be statefull or stateless, but if the
  58.  * separator or the escape character are controlled by the user, then it has to be statefull.
  59.  */
  60. public abstract class CSVReaderService
  61. {
  62.     private static final String MESSAGE_NO_FILE_FOUND = "portal.util.message.noFileFound";
  63.     private static final String MESSAGE_ERROR_READING_FILE = "portal.util.message.errorReadingFile";
  64.     private static final String MESSAGE_ERROR_NUMBER_COLUMNS = "portal.xsl.message.errorNumberColumns";
  65.     private static final String MESSAGE_UNKOWN_ERROR = "portal.xsl.message.errorUnknown";
  66.     private static final String PROPERTY_DEFAULT_CSV_SEPARATOR = "lutece.csvReader.defaultCSVSeparator";
  67.     private static final String PROPERTY_DEFAULT_CSV_ESCAPE_CHARACTER = "lutece.csvReader.defaultCSVEscapeCharacter";
  68.     private static final String CONSTANT_DEFAULT_CSV_SEPARATOR = ";";
  69.     private static final String CONSTANT_DEFAULT_CSV_ESCAPE_CHARACTER = "\"";
  70.     private Character _strCSVSeparator;
  71.     private Character _strCSVEscapeCharacter;

  72.     /**
  73.      * Read a line of the CSV file.
  74.      *
  75.      * @param strLineDataArray
  76.      *            The content of the line of the CSV file.
  77.      * @param nLineNumber
  78.      *            Number of the current line
  79.      * @param locale
  80.      *            the locale
  81.      * @param strBaseUrl
  82.      *            The base URL
  83.      * @return Returns the list of messages associated with the line.
  84.      */
  85.     protected abstract List<CSVMessageDescriptor> readLineOfCSVFile( String [ ] strLineDataArray, int nLineNumber, Locale locale, String strBaseUrl );

  86.     /**
  87.      * Check the line of the CSV file. This method is called once on each line of the file if the number of columns is correct. If the file is entirely checked
  88.      * before processing, then this method is called before any line is processed. Otherwise it is called just before the processing of the line.
  89.      *
  90.      * @param strLineDataArray
  91.      *            The content of the line of the CSV file.
  92.      * @param nLineNumber
  93.      *            Number of the current line
  94.      * @param locale
  95.      *            the locale
  96.      * @return The list of messages of the lines. <strong>Lines that contain messages with messages levels other than {@link CSVMessageLevel#INFO INFO} will NOT
  97.      *         be processed, and the global processing may stop if the ExitOnError flag has been set to true !</strong>
  98.      */
  99.     protected abstract List<CSVMessageDescriptor> checkLineOfCSVFile( String [ ] strLineDataArray, int nLineNumber, Locale locale );

  100.     /**
  101.      * Get messages after the process is completed.
  102.      *
  103.      * @param nNbLineParses
  104.      *            The number of lines parses. If the first line was skipped, it is not counted.
  105.      * @param nNbLinesWithoutErrors
  106.      *            the number of lines parses whitout error.
  107.      * @param locale
  108.      *            The locale
  109.      * @return A list of messages.
  110.      */
  111.     protected abstract List<CSVMessageDescriptor> getEndOfProcessMessages( int nNbLineParses, int nNbLinesWithoutErrors, Locale locale );

  112.     /**
  113.      * Get the default CSV separator to use. If the property of the default separator to use is not set, then the semi-colon is returned.
  114.      *
  115.      * @return the default CSV separator to use
  116.      */
  117.     public static Character getDefaultCSVSeparator( )
  118.     {
  119.         return AppPropertiesService.getProperty( PROPERTY_DEFAULT_CSV_SEPARATOR, CONSTANT_DEFAULT_CSV_SEPARATOR ).charAt( 0 );
  120.     }

  121.     /**
  122.      * Get the default CSV escape character to use. If the property of the default escape character to use is not set, then the comma is returned.
  123.      *
  124.      * @return the default CSV escape character to use
  125.      */
  126.     public static Character getDefaultCSVEscapeCharacter( )
  127.     {
  128.         return AppPropertiesService.getProperty( PROPERTY_DEFAULT_CSV_ESCAPE_CHARACTER, CONSTANT_DEFAULT_CSV_ESCAPE_CHARACTER ).charAt( 0 );
  129.     }

  130.     /**
  131.      * Read a CSV file and call the method {@link #readLineOfCSVFile(String[], int, Locale, String) readLineOfCSVFile} for each of its lines.
  132.      *
  133.      * @param fileItem
  134.      *            FileItem to get the CSV file from. If the creation of the input stream associated to this file throws a IOException, then an error is returned
  135.      *            and the file is not red.
  136.      * @param nColumnNumber
  137.      *            Number of columns of each lines. Use 0 to skip column number check (for example if every lines don't have the same number of columns)
  138.      * @param bCheckFileBeforeProcessing
  139.      *            Indicates if the file should be check before processing any of its line. If it is set to true, then then no line is processed if the file has
  140.      *            any error.
  141.      * @param bExitOnError
  142.      *            Indicates if the processing of the CSV file should end on the first error, or at the end of the file.
  143.      * @param bSkipFirstLine
  144.      *            Indicates if the first line of the file should be skipped or not.
  145.      * @param locale
  146.      *            the locale
  147.      * @param strBaseUrl
  148.      *            The base URL
  149.      * @return Returns the list of errors that occurred during the processing of the file. The returned list is sorted
  150.      * @see CSVMessageDescriptor#compareTo(CSVMessageDescriptor) CSVMessageDescriptor.compareTo(CSVMessageDescriptor) for information about sort
  151.      */
  152.     public List<CSVMessageDescriptor> readCSVFile( FileItem fileItem, int nColumnNumber, boolean bCheckFileBeforeProcessing, boolean bExitOnError,
  153.             boolean bSkipFirstLine, Locale locale, String strBaseUrl )
  154.     {
  155.         if ( fileItem != null )
  156.         {
  157.             InputStreamReader inputStreamReader = null;

  158.             try
  159.             {
  160.                 inputStreamReader = new InputStreamReader( fileItem.getInputStream( ) );
  161.             }
  162.             catch( IOException e )
  163.             {
  164.                 AppLogService.error( e.getMessage( ), e );
  165.             }

  166.             if ( inputStreamReader != null )
  167.             {
  168.                 CSVReader csvReader = new CSVReader( inputStreamReader, getCSVSeparator( ), getCSVEscapeCharacter( ) );

  169.                 return readCSVFile( inputStreamReader, csvReader, nColumnNumber, bCheckFileBeforeProcessing, bExitOnError, bSkipFirstLine, locale, strBaseUrl );
  170.             }
  171.         }

  172.         List<CSVMessageDescriptor> listErrors = new ArrayList<>( );
  173.         CSVMessageDescriptor errorDescription = new CSVMessageDescriptor( CSVMessageLevel.ERROR, 0,
  174.                 I18nService.getLocalizedString( MESSAGE_NO_FILE_FOUND, locale ) );
  175.         listErrors.add( errorDescription );

  176.         return listErrors;
  177.     }

  178.     /**
  179.      * Read a CSV file and call the method {@link #readLineOfCSVFile(String[], int, Locale, String) readLineOfCSVFile} for each of its lines.
  180.      *
  181.      * @param strPath
  182.      *            Path if the file to read in the file system.
  183.      * @param nColumnNumber
  184.      *            Number of columns of each lines. Use 0 to skip column number check (for example if every lines don't have the same number of columns)
  185.      * @param bCheckFileBeforeProcessing
  186.      *            Indicates if the file should be check before processing any of its line. If it is set to true, then then no line is processed if the file has
  187.      *            any error.
  188.      * @param bExitOnError
  189.      *            Indicates if the processing of the CSV file should end on the first error, or at the end of the file.
  190.      * @param bSkipFirstLine
  191.      *            Indicates if the first line of the file should be skipped or not.
  192.      * @param locale
  193.      *            the locale
  194.      * @param strBaseUrl
  195.      *            The base URL
  196.      * @return Returns the list of errors that occurred during the processing of the file. The returned list is sorted
  197.      * @see CSVMessageDescriptor#compareTo(CSVMessageDescriptor) CSVMessageDescriptor.compareTo(CSVMessageDescriptor) for information about sort
  198.      */
  199.     public List<CSVMessageDescriptor> readCSVFile( String strPath, int nColumnNumber, boolean bCheckFileBeforeProcessing, boolean bExitOnError,
  200.             boolean bSkipFirstLine, Locale locale, String strBaseUrl )
  201.     {
  202.         java.io.File file = new java.io.File( strPath );

  203.         try ( FileReader fileReader = new FileReader( file ) )
  204.         {
  205.             CSVReader csvReader = new CSVReader( fileReader, getCSVSeparator( ), getCSVEscapeCharacter( ) );

  206.             return readCSVFile( fileReader, csvReader, nColumnNumber, bCheckFileBeforeProcessing, bExitOnError, bSkipFirstLine, locale, strBaseUrl );
  207.         }
  208.         catch( IOException e )
  209.         {
  210.             AppLogService.error( e.getMessage( ), e );
  211.         }

  212.         List<CSVMessageDescriptor> listErrors = new ArrayList<>( );
  213.         CSVMessageDescriptor errorDescription = new CSVMessageDescriptor( CSVMessageLevel.ERROR, 0,
  214.                 I18nService.getLocalizedString( MESSAGE_NO_FILE_FOUND, locale ) );
  215.         listErrors.add( errorDescription );

  216.         return listErrors;
  217.     }

  218.     /**
  219.      * Read a CSV file and call the method {@link #readLineOfCSVFile(String[], int, Locale, String) readLineOfCSVFile} for each of its lines.
  220.      *
  221.      * @param file
  222.      *            File to get the values from. If the physical file of this file has no value, then it is gotten from the database.
  223.      * @param nColumnNumber
  224.      *            Number of columns of each lines. Use 0 to skip column number check (for example if every lines don't have the same number of columns)
  225.      * @param bCheckFileBeforeProcessing
  226.      *            Indicates if the file should be check before processing any of its line. If it is set to true, then then no line is processed if the file has
  227.      *            any error.
  228.      * @param bExitOnError
  229.      *            Indicates if the processing of the CSV file should end on the first error, or at the end of the file.
  230.      * @param bSkipFirstLine
  231.      *            Indicates if the first line of the file should be skipped or not.
  232.      * @param locale
  233.      *            the locale
  234.      * @param strBaseUrl
  235.      *            The base URL
  236.      * @return Returns the list of errors that occurred during the processing of the file. The returned list is sorted
  237.      * @see CSVMessageDescriptor#compareTo(CSVMessageDescriptor) CSVMessageDescriptor.compareTo(CSVMessageDescriptor) for information about sort
  238.      */
  239.     public List<CSVMessageDescriptor> readCSVFile( File file, int nColumnNumber, boolean bCheckFileBeforeProcessing, boolean bExitOnError,
  240.             boolean bSkipFirstLine, Locale locale, String strBaseUrl )
  241.     {
  242.         return readCSVFile( file.getPhysicalFile( ), nColumnNumber, bCheckFileBeforeProcessing, bExitOnError, bSkipFirstLine, locale, strBaseUrl );
  243.     }

  244.     /**
  245.      * Read a CSV file and call the method {@link #readLineOfCSVFile(String[], int, Locale, String) readLineOfCSVFile} for each of its lines.
  246.      *
  247.      * @param physicalFile
  248.      *            The physicalFile to get the values from. If the physical file has no value, then it is gotten from the database.
  249.      * @param nColumnNumber
  250.      *            Number of columns of each lines. Use 0 to skip column number check (for example if every lines don't have the same number of columns)
  251.      * @param bCheckFileBeforeProcessing
  252.      *            Indicates if the file should be check before processing any of its line. If it is set to true, then then no line is processed if the file has
  253.      *            any error.
  254.      * @param bExitOnError
  255.      *            Indicates if the processing of the CSV file should end on the first error, or at the end of the file.
  256.      * @param bSkipFirstLine
  257.      *            Indicates if the first line of the file should be skipped or not.
  258.      * @param locale
  259.      *            the locale
  260.      * @param strBaseUrl
  261.      *            The base URL
  262.      * @return Returns the list of errors that occurred during the processing of the file. The returned list is sorted
  263.      * @see CSVMessageDescriptor#compareTo(CSVMessageDescriptor) CSVMessageDescriptor.compareTo(CSVMessageDescriptor) for information about sort
  264.      */
  265.     public List<CSVMessageDescriptor> readCSVFile( PhysicalFile physicalFile, int nColumnNumber, boolean bCheckFileBeforeProcessing, boolean bExitOnError,
  266.             boolean bSkipFirstLine, Locale locale, String strBaseUrl )
  267.     {
  268.         PhysicalFile importedPhysicalFile = physicalFile;

  269.         if ( ( importedPhysicalFile != null ) && ( importedPhysicalFile.getValue( ) == null ) )
  270.         {
  271.             if ( importedPhysicalFile.getValue( ) == null )
  272.             {
  273.                 importedPhysicalFile = PhysicalFileHome.findByPrimaryKey( importedPhysicalFile.getIdPhysicalFile( ) );
  274.             }

  275.             if ( ( importedPhysicalFile != null ) && ( importedPhysicalFile.getValue( ) == null ) )
  276.             {
  277.                 InputStream inputStream = new ByteArrayInputStream( importedPhysicalFile.getValue( ) );
  278.                 InputStreamReader inputStreamReader = new InputStreamReader( inputStream );
  279.                 CSVReader csvReader = new CSVReader( inputStreamReader, getCSVSeparator( ), getCSVEscapeCharacter( ) );

  280.                 return readCSVFile( inputStreamReader, csvReader, nColumnNumber, bCheckFileBeforeProcessing, bExitOnError, bSkipFirstLine, locale, strBaseUrl );
  281.             }
  282.         }

  283.         List<CSVMessageDescriptor> listErrors = new ArrayList<>( );
  284.         CSVMessageDescriptor errorDescription = new CSVMessageDescriptor( CSVMessageLevel.ERROR, 0,
  285.                 I18nService.getLocalizedString( MESSAGE_NO_FILE_FOUND, locale ) );
  286.         listErrors.add( errorDescription );

  287.         return listErrors;
  288.     }

  289.     /**
  290.      * Read a CSV file and call the method {@link #readLineOfCSVFile(String[], int, Locale, String) readLineOfCSVFile} for each of its lines.
  291.      *
  292.      * @param reader
  293.      *            The file reader that was used to create the CSV reader. This reader will be closed by this method
  294.      * @param csvReader
  295.      *            CSV reader to use to read the CSV file
  296.      * @param nColumnNumber
  297.      *            Number of columns of each lines. Use 0 to skip column number check (for example if every lines don't have the same number of columns)
  298.      * @param bCheckFileBeforeProcessing
  299.      *            Indicates if the file should be check before processing any of its line. If it is set to true, then then no line is processed if the file has
  300.      *            any error.
  301.      * @param bExitOnError
  302.      *            Indicates if the processing of the CSV file should end on the first error, or at the end of the file.
  303.      * @param bSkipFirstLine
  304.      *            Indicates if the first line of the file should be skipped or not.
  305.      * @param locale
  306.      *            the locale
  307.      * @param strBaseUrl
  308.      *            The base URL
  309.      * @return Returns the list of errors that occurred during the processing of the file. The returned list is sorted
  310.      * @see CSVMessageDescriptor#compareTo(CSVMessageDescriptor) CSVMessageDescriptor.compareTo(CSVMessageDescriptor) for information about sort
  311.      */
  312.     protected List<CSVMessageDescriptor> readCSVFile( Reader reader, CSVReader csvReader, int nColumnNumber, boolean bCheckFileBeforeProcessing,
  313.             boolean bExitOnError, boolean bSkipFirstLine, Locale locale, String strBaseUrl )
  314.     {
  315.         List<CSVMessageDescriptor> listMessages = new ArrayList<>( );
  316.         int nLineNumber = 0;

  317.         if ( bSkipFirstLine )
  318.         {
  319.             try
  320.             {
  321.                 nLineNumber++;
  322.                 csvReader.readNext( );
  323.             }
  324.             catch( IOException e )
  325.             {
  326.                 AppLogService.error( e.getMessage( ), e );

  327.                 CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, 1,
  328.                         I18nService.getLocalizedString( MESSAGE_ERROR_READING_FILE, locale ) );
  329.                 listMessages.add( error );

  330.                 if ( bExitOnError )
  331.                 {
  332.                     StreamUtil.safeClose( csvReader );
  333.                     StreamUtil.safeClose( reader );

  334.                     return listMessages;
  335.                 }
  336.             }
  337.         }

  338.         List<String [ ]> listLines = null;

  339.         if ( bCheckFileBeforeProcessing )
  340.         {
  341.             listLines = new ArrayList<>( );

  342.             String [ ] strLine = null;

  343.             do
  344.             {
  345.                 try
  346.                 {
  347.                     nLineNumber++;
  348.                     strLine = csvReader.readNext( );
  349.                 }
  350.                 catch( IOException e )
  351.                 {
  352.                     AppLogService.error( e.getMessage( ), e );

  353.                     CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber,
  354.                             I18nService.getLocalizedString( MESSAGE_ERROR_READING_FILE, locale ) );
  355.                     listMessages.add( error );

  356.                     if ( bExitOnError )
  357.                     {
  358.                         StreamUtil.safeClose( csvReader );
  359.                         StreamUtil.safeClose( reader );

  360.                         Collections.sort( listMessages );

  361.                         return listMessages;
  362.                     }
  363.                 }

  364.                 if ( strLine != null )
  365.                 {
  366.                     listLines.add( strLine );
  367.                 }
  368.             }
  369.             while ( strLine != null );

  370.             List<CSVMessageDescriptor> listCheckErrors = checkCSVFileValidity( listLines, nColumnNumber, bSkipFirstLine, locale );

  371.             if ( CollectionUtils.isNotEmpty( listCheckErrors ) && doesListMessageContainError( listCheckErrors ) )
  372.             {
  373.                 listCheckErrors.addAll( 0, listMessages );
  374.                 StreamUtil.safeClose( csvReader );
  375.                 StreamUtil.safeClose( reader );

  376.                 Collections.sort( listMessages );

  377.                 return listCheckErrors;
  378.             }

  379.             nLineNumber = 0;
  380.         }

  381.         boolean bHasMoreLines = true;
  382.         int nNbLinesWithoutErrors = 0;
  383.         String [ ] strLine = null;
  384.         Iterator<String [ ]> iterator = null;

  385.         if ( listLines != null )
  386.         {
  387.             iterator = listLines.iterator( );
  388.         }

  389.         while ( bHasMoreLines )
  390.         {
  391.             nLineNumber++;

  392.             if ( iterator != null )
  393.             {
  394.                 if ( iterator.hasNext( ) )
  395.                 {
  396.                     strLine = iterator.next( );
  397.                 }
  398.                 else
  399.                 {
  400.                     strLine = null;
  401.                     bHasMoreLines = false;
  402.                 }
  403.             }
  404.             else
  405.             {
  406.                 try
  407.                 {
  408.                     strLine = csvReader.readNext( );
  409.                 }
  410.                 catch( IOException e )
  411.                 {
  412.                     strLine = null;
  413.                     AppLogService.error( e.getMessage( ), e );

  414.                     CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber,
  415.                             I18nService.getLocalizedString( MESSAGE_ERROR_READING_FILE, locale ) );
  416.                     listMessages.add( error );

  417.                     if ( bExitOnError )
  418.                     {
  419.                         bHasMoreLines = false;
  420.                     }
  421.                 }
  422.             }

  423.             if ( strLine != null )
  424.             {
  425.                 try
  426.                 {
  427.                     List<CSVMessageDescriptor> listLinesMessages = null;

  428.                     if ( !bCheckFileBeforeProcessing )
  429.                     {
  430.                         listLinesMessages = checkCSVLineColumnNumber( strLine, nColumnNumber, nLineNumber, locale );

  431.                         if ( !doesListMessageContainError( listLinesMessages ) )
  432.                         {
  433.                             List<CSVMessageDescriptor> listFileCheckMessages = checkLineOfCSVFile( strLine, nLineNumber, locale );

  434.                             if ( CollectionUtils.isNotEmpty( listFileCheckMessages ) )
  435.                             {
  436.                                 if ( CollectionUtils.isNotEmpty( listLinesMessages ) )
  437.                                 {
  438.                                     listLinesMessages.addAll( listFileCheckMessages );
  439.                                 }
  440.                                 else
  441.                                 {
  442.                                     listLinesMessages = listFileCheckMessages;
  443.                                 }
  444.                             }
  445.                         }

  446.                         if ( CollectionUtils.isNotEmpty( listLinesMessages ) )
  447.                         {
  448.                             listMessages.addAll( listLinesMessages );
  449.                         }
  450.                     }

  451.                     // If the line has no error
  452.                     if ( !doesListMessageContainError( listLinesMessages ) )
  453.                     {
  454.                         List<CSVMessageDescriptor> listMessagesOfCurrentLine = readLineOfCSVFile( strLine, nLineNumber, locale, strBaseUrl );

  455.                         if ( CollectionUtils.isNotEmpty( listMessagesOfCurrentLine ) )
  456.                         {
  457.                             listMessages.addAll( listMessagesOfCurrentLine );
  458.                         }

  459.                         if ( doesListMessageContainError( listMessagesOfCurrentLine ) )
  460.                         {
  461.                             if ( bExitOnError )
  462.                             {
  463.                                 bHasMoreLines = false;
  464.                             }
  465.                         }
  466.                         else
  467.                         {
  468.                             nNbLinesWithoutErrors++;
  469.                         }
  470.                     }
  471.                 }
  472.                 catch( Exception e )
  473.                 {
  474.                     AppLogService.error( e.getMessage( ), e );

  475.                     CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber,
  476.                             I18nService.getLocalizedString( MESSAGE_UNKOWN_ERROR, locale ) );
  477.                     listMessages.add( error );

  478.                     if ( bExitOnError )
  479.                     {
  480.                         bHasMoreLines = false;
  481.                     }
  482.                 }
  483.             }
  484.             else
  485.             {
  486.                 bHasMoreLines = false;
  487.             }
  488.         }
  489.         StreamUtil.safeClose( csvReader );
  490.         StreamUtil.safeClose( reader );

  491.         // We incremented the line number for the last line that didn't exist
  492.         nLineNumber--;

  493.         if ( bSkipFirstLine )
  494.         {
  495.             nLineNumber--;
  496.         }

  497.         List<CSVMessageDescriptor> listMessagesEndOfProcess = getEndOfProcessMessages( nLineNumber, nNbLinesWithoutErrors, locale );

  498.         if ( CollectionUtils.isNotEmpty( listMessagesEndOfProcess ) )
  499.         {
  500.             listMessages.addAll( 0, listMessagesEndOfProcess );
  501.         }

  502.         Collections.sort( listMessages );

  503.         return listMessages;
  504.     }

  505.     /**
  506.      * Check the validity of the whole CSV file.
  507.      *
  508.      * @param listLines
  509.      *            The list of lines of the file
  510.      * @param nColumnNumber
  511.      *            The number of columns every line must have.
  512.      * @param bSkipFirstLine
  513.      *            True if the first line should be ignored, false otherwise
  514.      * @param locale
  515.      *            The locale
  516.      * @return Returns a list of errors found in the file.
  517.      */
  518.     protected List<CSVMessageDescriptor> checkCSVFileValidity( List<String [ ]> listLines, int nColumnNumber, boolean bSkipFirstLine, Locale locale )
  519.     {
  520.         List<CSVMessageDescriptor> listErrors = new ArrayList<>( );
  521.         int nLineNumber = 0;

  522.         if ( bSkipFirstLine )
  523.         {
  524.             nLineNumber++;
  525.         }

  526.         for ( String [ ] strLine : listLines )
  527.         {
  528.             nLineNumber++;

  529.             List<CSVMessageDescriptor> listMessages = checkCSVLineColumnNumber( strLine, nColumnNumber, nLineNumber, locale );

  530.             if ( CollectionUtils.isNotEmpty( listMessages ) )
  531.             {
  532.                 listErrors.addAll( listMessages );
  533.             }

  534.             if ( !doesListMessageContainError( listMessages ) )
  535.             {
  536.                 listMessages = checkLineOfCSVFile( strLine, nLineNumber, locale );

  537.                 if ( CollectionUtils.isNotEmpty( listMessages ) )
  538.                 {
  539.                     listErrors.addAll( listMessages );
  540.                 }
  541.             }
  542.         }

  543.         return listErrors;
  544.     }

  545.     /**
  546.      * Check the number of columns of a line.
  547.      *
  548.      * @param strLine
  549.      *            The line to check
  550.      * @param nColumnNumber
  551.      *            The number of columns the line must have
  552.      * @param nLineNumber
  553.      *            The number of the current line
  554.      * @param locale
  555.      *            The locale
  556.      * @return The error if an error is found, or null if there is none.
  557.      */
  558.     protected List<CSVMessageDescriptor> checkCSVLineColumnNumber( String [ ] strLine, int nColumnNumber, int nLineNumber, Locale locale )
  559.     {
  560.         if ( ( strLine == null ) || ( ( nColumnNumber > 0 ) && ( strLine.length != nColumnNumber ) ) )
  561.         {
  562.             List<CSVMessageDescriptor> listMessages = new ArrayList<>( );
  563.             Object [ ] args = {
  564.                     ( strLine == null ) ? 0 : strLine.length, nColumnNumber
  565.             };
  566.             String strErrorMessage = I18nService.getLocalizedString( MESSAGE_ERROR_NUMBER_COLUMNS, args, locale );
  567.             CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber, strErrorMessage );
  568.             listMessages.add( error );

  569.             return listMessages;
  570.         }

  571.         return null;
  572.     }

  573.     /**
  574.      * Get the separator used for CSV files. If no separator has been set, then the default CSV separator is used.
  575.      *
  576.      * @return the separator used for CSV files, of the default one if non has been set.
  577.      */
  578.     public Character getCSVSeparator( )
  579.     {
  580.         if ( this._strCSVSeparator == null )
  581.         {
  582.             this._strCSVSeparator = getDefaultCSVSeparator( );
  583.         }

  584.         return _strCSVSeparator;
  585.     }

  586.     /**
  587.      * Set the separator to use for CSV files.
  588.      *
  589.      * @param strCSVSeparator
  590.      *            The separator to use for CSV files.
  591.      */
  592.     public void setCSVSeparator( Character strCSVSeparator )
  593.     {
  594.         this._strCSVSeparator = strCSVSeparator;
  595.     }

  596.     /**
  597.      * Get the escape character used for CSV files. If no escape character has been set, then the default CSV escape character is used.
  598.      *
  599.      * @return the escape character used for CSV files, of the default one if non has been set.
  600.      */
  601.     public Character getCSVEscapeCharacter( )
  602.     {
  603.         if ( this._strCSVEscapeCharacter == null )
  604.         {
  605.             this._strCSVEscapeCharacter = getDefaultCSVEscapeCharacter( );
  606.         }

  607.         return _strCSVEscapeCharacter;
  608.     }

  609.     /**
  610.      * Set the escape character to use for CSV files.
  611.      *
  612.      * @param strCSVEscapeCharacter
  613.      *            The escape character to use for CSV files.
  614.      */
  615.     public void setCSVEscapeCharacter( Character strCSVEscapeCharacter )
  616.     {
  617.         this._strCSVEscapeCharacter = strCSVEscapeCharacter;
  618.     }

  619.     /**
  620.      * Check if a list of messages contains messages with the {@link CSVMessageLevel#ERROR ERROR} level
  621.      *
  622.      * @param listMessageOfCurrentLine
  623.      *            The list of messages. The list might be null or empty.
  624.      * @return True if an error is found, false otherwise
  625.      */
  626.     private boolean doesListMessageContainError( List<CSVMessageDescriptor> listMessageOfCurrentLine )
  627.     {
  628.         if ( CollectionUtils.isNotEmpty( listMessageOfCurrentLine ) )
  629.         {
  630.             for ( CSVMessageDescriptor message : listMessageOfCurrentLine )
  631.             {
  632.                 if ( message.getMessageLevel( ) == CSVMessageLevel.ERROR )
  633.                 {
  634.                     return true;
  635.                 }
  636.             }
  637.         }

  638.         return false;
  639.     }
  640. }