View Javadoc
1   /*
2    * Copyright (c) 2002-2021, 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.plugins.importexport.service.importdata;
35  
36  import fr.paris.lutece.plugins.importexport.business.ImportExportElement;
37  import fr.paris.lutece.plugins.importexport.business.importdata.ImportDataDAO;
38  import fr.paris.lutece.plugins.importexport.business.importdata.ImportMessage;
39  import fr.paris.lutece.plugins.importexport.business.importdata.ImportResult;
40  import fr.paris.lutece.plugins.importexport.service.ImportExportPlugin;
41  import fr.paris.lutece.portal.business.user.AdminUser;
42  import fr.paris.lutece.portal.service.daemon.ThreadLauncherDaemon;
43  import fr.paris.lutece.portal.service.plugin.Plugin;
44  import fr.paris.lutece.portal.service.util.AppException;
45  import fr.paris.lutece.portal.service.util.AppLogService;
46  
47  import java.io.File;
48  import java.sql.SQLException;
49  import java.util.ArrayList;
50  import java.util.HashMap;
51  import java.util.List;
52  import java.util.Locale;
53  import java.util.Map;
54  
55  import org.apache.commons.fileupload.FileItem;
56  import org.apache.commons.lang3.StringUtils;
57  
58  /**
59   * Manager of imports
60   */
61  public final class ImportManager
62  {
63      private static List<IImportSourceFactory> _listImportSourceFactories = new ArrayList<IImportSourceFactory>( );
64  
65      private static final String CONSTANT_POINT = ".";
66  
67      private static Map<Integer, RunnableImportService> _mapWorkingRunnableImportServices = new HashMap<Integer, RunnableImportService>( );
68  
69      /**
70       * Private constructor
71       */
72      private ImportManager( )
73      {
74      }
75  
76      /**
77       * Register an import source factory
78       * 
79       * @param importSourceFactory
80       *            The import source factory to register
81       */
82      public static void registerImportSourceFactory( IImportSourceFactory importSourceFactory )
83      {
84          _listImportSourceFactories.add( importSourceFactory );
85      }
86  
87      /**
88       * Get an import source for a file item. The import source is instantiated from registered factories that are compatible with the given file extension.
89       * 
90       * @param fileItem
91       *            The file item to read data from
92       * @return The import source, or null if no factories are associated to the given file extension or if an error occurs during the instantiation of the
93       *         import source.
94       */
95      public static IImportSource getImportSource( FileItem fileItem )
96      {
97          String strFileName = fileItem.getName( );
98          String strFileExtention = null;
99          if ( StringUtils.isNotEmpty( strFileName ) )
100         {
101             strFileExtention = strFileName.substring( strFileName.lastIndexOf( CONSTANT_POINT ) + 1 );
102         }
103 
104         if ( StringUtils.isEmpty( strFileExtention ) )
105         {
106             return null;
107         }
108 
109         for ( IImportSourceFactory importSourceFactory : _listImportSourceFactories )
110         {
111             if ( importSourceFactory.isValidImportSource( strFileExtention ) )
112             {
113                 return importSourceFactory.getImportSource( fileItem );
114             }
115         }
116         return null;
117     }
118 
119     /**
120      * Get an import source for a file. The import source is instantiated from registered factories that are compatible with the given file extension.
121      * 
122      * @param file
123      *            The file to read data from
124      * @return The import source, or null if no factories are associated to the given file extension or if an error occurs during the instantiation of the
125      *         import source.
126      */
127     public static IImportSource getImportSource( File file )
128     {
129         String strFileName = file.getName( );
130         String strFileExtention = null;
131         if ( StringUtils.isNotEmpty( strFileName ) )
132         {
133             strFileExtention = strFileName.substring( strFileName.lastIndexOf( CONSTANT_POINT ) + 1 );
134         }
135 
136         if ( StringUtils.isEmpty( strFileExtention ) )
137         {
138             return null;
139         }
140 
141         for ( IImportSourceFactory importSourceFactory : _listImportSourceFactories )
142         {
143             if ( importSourceFactory.isValidImportSource( strFileExtention ) )
144             {
145                 return importSourceFactory.getImportSource( file );
146             }
147         }
148         return null;
149     }
150 
151     /**
152      * Do process the import of data from an import source to a given table in the database.
153      * 
154      * @param importSource
155      *            The import source to get data from
156      * @param strTableName
157      *            The name of the table in the database to import data to
158      * @param bUpdateExistingRows
159      *            True to update existing rows, false to ignore them
160      * @param bStopOnErrors
161      *            True to stop when an error occurred, false to skip the item and continue
162      * @param bEmptyTable
163      *            True to empty the table before importing data, false otherwise
164      * @param plugin
165      *            The plugin to get the pool from
166      * @param locale
167      *            The locale
168      * @return The result of the import
169      */
170     public static ImportResult doProcessImport( IImportSource importSource, String strTableName, boolean bUpdateExistingRows, boolean bStopOnErrors,
171             boolean bEmptyTable, Plugin plugin, Locale locale )
172     {
173         List<ImportExportElement> listElements;
174         int nCreatedElements = 0;
175         int nUpdatedElements = 0;
176         int nIgnoredElements = 0;
177         int nItemNumber = 0;
178         ImportDataDAO importElementDAO = null;
179         try
180         {
181             importElementDAO = new ImportDataDAO( importSource.getColumnsName( ), strTableName, plugin, locale );
182         }
183         catch( AppException e )
184         {
185             AppLogService.info( e.getMessage( ) );
186             return createErrorImportResult( e );
187         }
188 
189         List<ImportMessage> listErrors = new ArrayList<ImportMessage>( );
190         try
191         {
192             if ( bEmptyTable )
193             {
194                 try
195                 {
196                     importElementDAO.emptyTable( );
197                 }
198                 catch( AppException e )
199                 {
200                     AppLogService.error( e.getMessage( ), e );
201                 }
202                 catch( SQLException e )
203                 {
204                     AppLogService.error( e.getMessage( ), e );
205                 }
206             }
207             // While there is values in the import source
208             while ( ( listElements = importSource.getNextValues( ) ) != null )
209             {
210                 nItemNumber++;
211                 try
212                 {
213                     // If we didn't emptied the table, and the row already exists
214                     if ( !bEmptyTable && importElementDAO.checkElementExists( listElements ) )
215                     {
216                         // If we must update existing rows
217                         if ( bUpdateExistingRows )
218                         {
219                             importElementDAO.updateElement( listElements );
220                             nUpdatedElements++;
221                         }
222                         else
223                         {
224                             nIgnoredElements++;
225                         }
226                     }
227                     else
228                     {
229                         // If it doesn't exist, we insert a new one
230                         importElementDAO.insertElement( listElements );
231                         nCreatedElements++;
232                     }
233 
234                 }
235                 catch( AppException e )
236                 {
237                     ImportMessageort/business/importdata/ImportMessage.html#ImportMessage">ImportMessage importMessage = new ImportMessage( e.getMessage( ), ImportMessage.STATUS_ERROR, nItemNumber );
238                     listErrors.add( importMessage );
239                     nIgnoredElements++;
240                     if ( bStopOnErrors )
241                     {
242                         importElementDAO.rollbackTransaction( );
243                         return new ImportResult( nCreatedElements, nUpdatedElements, nIgnoredElements, listErrors );
244                     }
245                 }
246                 catch( SQLException e )
247                 {
248                     ImportMessageort/business/importdata/ImportMessage.html#ImportMessage">ImportMessage importMessage = new ImportMessage( e.getMessage( ), ImportMessage.STATUS_ERROR, nItemNumber );
249                     listErrors.add( importMessage );
250                     nIgnoredElements++;
251                     if ( bStopOnErrors )
252                     {
253                         importElementDAO.rollbackTransaction( );
254                         return new ImportResult( nCreatedElements, nUpdatedElements, nIgnoredElements, listErrors );
255                     }
256                 }
257             }
258             importElementDAO.commitTransaction( );
259         }
260         catch( Exception e )
261         {
262             AppLogService.error( e.getMessage( ), e );
263             importElementDAO.rollbackTransaction( );
264             ImportMessageort/business/importdata/ImportMessage.html#ImportMessage">ImportMessage importMessage = new ImportMessage( e.getMessage( ), ImportMessage.STATUS_ERROR, nItemNumber );
265             listErrors.add( importMessage );
266         }
267         return new ImportResult( nCreatedElements, nUpdatedElements, nIgnoredElements, listErrors );
268     }
269 
270     /**
271      * Do process an asynchronous import of data from an import source to a given table in the database.
272      * 
273      * @param importSource
274      *            The import source to get data from
275      * @param strTableName
276      *            The name of the table in the database to import data to
277      * @param plugin
278      *            The plugin to get the pool from
279      * @param locale
280      *            The locale
281      * @param bUpdateExistingRows
282      *            True to update existing rows, false to ignore them
283      * @param bStopOnErrors
284      *            True to stop when an error occurred, false to skip the item and continue
285      * @param bEmptyTable
286      *            True to empty the table before importing data, false otherwise
287      * @param admin
288      *            The admin user that started the import, or null if the import was started by a daemon
289      */
290     public static void doProcessAsynchronousImport( IImportSource importSource, String strTableName, Plugin plugin, Locale locale, boolean bUpdateExistingRows,
291             boolean bStopOnErrors, boolean bEmptyTable, AdminUser admin )
292     {
293         RunnableImportServicertdata/RunnableImportService.html#RunnableImportService">RunnableImportService runnableImportService = new RunnableImportService( importSource, strTableName, plugin, locale, bUpdateExistingRows, bStopOnErrors,
294                 bEmptyTable );
295         if ( admin != null )
296         {
297             _mapWorkingRunnableImportServices.put( admin.getUserId( ), runnableImportService );
298         }
299         ThreadLauncherDaemon.addItemToQueue( runnableImportService, strTableName, ImportExportPlugin.getPlugin( ) );
300     }
301 
302     /**
303      * Check if an admin user has an import processing
304      * 
305      * @param nAdminId
306      *            The id of the admin user
307      * @return True if the admin user has an import processing, false otherwise
308      */
309     public static boolean hasImportInProcess( int nAdminId )
310     {
311         if ( nAdminId > 0 )
312         {
313             RunnableImportService runnableImportService = _mapWorkingRunnableImportServices.get( nAdminId );
314             if ( runnableImportService != null )
315             {
316                 return runnableImportService.getServiceStatus( ) != RunnableImportService.STATUS_FINISHED;
317             }
318         }
319         return false;
320     }
321 
322     /**
323      * Get the result of an asynchronous import. The import service is then removed from the list of current imports
324      * 
325      * @param nAdminId
326      *            The id of the user that started the import
327      * @return The result of the import, or null if no result were found
328      */
329     public static ImportResult getAsynchronousImportResult( int nAdminId )
330     {
331         RunnableImportService runnableImportService = _mapWorkingRunnableImportServices.get( nAdminId );
332         if ( runnableImportService != null && runnableImportService.getServiceStatus( ) == RunnableImportService.STATUS_FINISHED )
333         {
334             ImportResult result = runnableImportService.getImportResult( );
335             _mapWorkingRunnableImportServices.remove( nAdminId );
336             return result;
337         }
338         return null;
339     }
340 
341     /**
342      * Creates a new import result from a throwable. The import result has one error message, which contain the message of the throwable.
343      * 
344      * @param throwable
345      *            The throwable to get the message from
346      * @return An import result
347      */
348     private static ImportResult createErrorImportResult( Throwable throwable )
349     {
350         ImportResultmportexport/business/importdata/ImportResult.html#ImportResult">ImportResult result = new ImportResult( );
351         ImportMessageortexport/business/importdata/ImportMessage.html#ImportMessage">ImportMessage message = new ImportMessage( throwable.getMessage( ), ImportMessage.STATUS_ERROR, 0 );
352         List<ImportMessage> listMessages = new ArrayList<ImportMessage>( );
353         listMessages.add( message );
354         result.setListImportMessage( listMessages );
355         return result;
356     }
357 }