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.ctv.handler;
35  
36  import java.io.File;
37  import java.io.IOException;
38  import java.nio.file.FileVisitOption;
39  import java.nio.file.FileVisitResult;
40  import java.nio.file.Files;
41  import java.nio.file.Path;
42  import java.nio.file.Paths;
43  import java.nio.file.SimpleFileVisitor;
44  import java.nio.file.attribute.BasicFileAttributes;
45  import java.text.SimpleDateFormat;
46  import java.util.ArrayList;
47  import java.util.Arrays;
48  import java.util.Date;
49  import java.util.EnumSet;
50  import java.util.Iterator;
51  import java.util.List;
52  import java.util.Locale;
53  import java.util.Map;
54  import java.util.concurrent.ConcurrentHashMap;
55  
56  import javax.servlet.http.HttpServletRequest;
57  import javax.servlet.http.HttpSession;
58  
59  import org.apache.commons.fileupload.FileItem;
60  import org.apache.commons.fileupload.disk.DiskFileItemFactory;
61  import org.apache.commons.io.FilenameUtils;
62  import org.apache.commons.lang.StringUtils;
63  import org.apache.commons.logging.Log;
64  import org.apache.commons.logging.LogFactory;
65  import org.springframework.beans.factory.annotation.Value;
66  
67  import fr.paris.lutece.plugins.asynchronousupload.service.AbstractAsynchronousUploadHandler;
68  import fr.paris.lutece.plugins.ctv.util.RemoteFilesUtils;
69  import fr.paris.lutece.portal.service.i18n.I18nService;
70  import fr.paris.lutece.portal.service.util.AppException;
71  import fr.paris.lutece.util.filesystem.UploadUtil;
72  
73  public abstract class AbstractCtvUploadHandler extends AbstractAsynchronousUploadHandler
74  {
75  
76      @Value( "${ctv.directory.upload}" )
77      protected String uploadDirectory;
78  
79      @Value( "${ctv.upload.max.file.size}" )
80      private String maxFileSize;
81  
82      @Value( "${ctv.file.remote.active}" )
83      private boolean activeRemoteFilesSynchro;
84  
85      public String getMaxFileSize( )
86      {
87          return maxFileSize;
88      }
89  
90      // private DeleteFilesOnEndUploadCleaningTracker tracker = new DeleteFilesOnEndUploadCleaningTracker();
91  
92      private static final String PREFIX_ENTRY_ID = "ctv_";
93      protected static final Log LOGGER = LogFactory.getLog( AbstractCtvUploadHandler.class );
94      public static final String ERROR_EXTENSION = "ctv.message.upload.file.error.extension";
95      public static final String ERROR_FILES_SIZE = "ctv.message.upload.file.error.files.size";
96      public static final String ERROR_FILE_CORRUPTED = "ctv.message.upload.file.error.file.corrupted";
97  
98      private static List<String> extensionList = Arrays.asList( "bmp", "jpg", "jpeg", "doc", "docx", "pdf", "png", "ppt", "pptx", "xls", "xlsx", "3ds", "drw",
99              "dwg", "dxf", "jpe", "pps", "pub", "tif", "tiff", "txt", "dgn", "ifc", "xlsm", "odt", "ods", "odp" );
100 
101     public AbstractCtvUploadHandler( )
102     {
103     }
104 
105     @Override
106     public void addFileItemToUploadedFilesList( FileItem fileItem, String strFieldName, HttpServletRequest request )
107     {
108         String strFileName = UploadUtil.cleanFileName( fileItem.getName( ).trim( ) );
109 
110         initMap( request.getSession( ), PREFIX_ENTRY_ID + strFieldName );
111 
112         List<FileItem> uploadedFiles = getListUploadedFiles( strFieldName, request.getSession( ) );
113 
114         if ( uploadedFiles != null )
115         {
116             boolean bNew = true;
117 
118             if ( !uploadedFiles.isEmpty( ) )
119             {
120                 Iterator<FileItem> iterUploadedFiles = uploadedFiles.iterator( );
121 
122                 while ( bNew && iterUploadedFiles.hasNext( ) )
123                 {
124                     FileItem uploadedFile = iterUploadedFiles.next( );
125                     String strUploadedFileName = UploadUtil.cleanFileName( uploadedFile.getName( ).trim( ) );
126                     bNew = !( StringUtils.equals( strUploadedFileName, strFileName ) && ( uploadedFile.getSize( ) == fileItem.getSize( ) ) );
127                 }
128             }
129 
130             if ( bNew )
131             {
132                 uploadedFiles.add( fileItem );
133             }
134         }
135     }
136 
137     @Override
138     public String canUploadFiles( HttpServletRequest request, String strFieldName, List<FileItem> listFileItemsToUpload, Locale locale )
139     {
140         if ( StringUtils.isNotBlank( strFieldName ) && ( strFieldName.length( ) > PREFIX_ENTRY_ID.length( ) ) )
141         {
142 
143             int maxUploadedFiles = 3;
144             if ( "executionTravauxDocument".equals( strFieldName ) || "demandeDocument".equals( strFieldName ) )
145             {
146                 maxUploadedFiles = 10;
147             }
148             if ( getListUploadedFiles( strFieldName, request.getSession( ) ).size( ) >= maxUploadedFiles )
149             {
150                 return I18nService.getLocalizedString( ERROR_FILES_SIZE, new Object [ ] {
151                         maxUploadedFiles
152                 }, locale );
153             }
154 
155             for ( FileItem fileItem : listFileItemsToUpload )
156             {
157                 if ( !extensionList.contains( FilenameUtils.getExtension( fileItem.getName( ).toLowerCase( ) ) ) )
158                 {
159                     return I18nService.getLocalizedString( ERROR_EXTENSION, locale ) + StringUtils.join( extensionList.toArray( ), ", " );
160                 }
161 
162                 if ( fileItem.get( ) == null )
163                 {
164                     return I18nService.getLocalizedString( ERROR_FILE_CORRUPTED, locale );
165                 }
166             }
167         }
168         return null;
169     }
170 
171     @Override
172     public List<FileItem> getListUploadedFiles( String strFieldName, HttpSession session )
173     {
174         if ( StringUtils.isBlank( strFieldName ) )
175         {
176             throw new AppException( "id field name is not provided for the current file upload" );
177         }
178 
179         initMap( session, strFieldName );
180 
181         Map<String, List<FileItem>> mapFileItemsSession = getMapAsynchronousUpload( ).get( session.getId( ) );
182 
183         return mapFileItemsSession.get( strFieldName );
184     }
185 
186     abstract Map<String, Map<String, List<FileItem>>> getMapAsynchronousUpload( );
187 
188     @Override
189     public void removeFileItem( String strFieldName, HttpSession session, int nIndex )
190     {
191         List<FileItem> uploadedFiles = getListUploadedFiles( strFieldName, session );
192         remove( strFieldName, session, nIndex, uploadedFiles );
193     }
194 
195     public void removeFilesItem( String strFieldName, HttpSession session )
196     {
197         List<FileItem> uploadedFiles = getListUploadedFiles( strFieldName, session );
198         for ( int nIndex = 0; nIndex < uploadedFiles.size( ); nIndex++ )
199         {
200             remove( strFieldName, session, nIndex, uploadedFiles );
201         }
202     }
203 
204     public FileItem getFileItemByName( HttpServletRequest request, String typeFichier, String nomFichier )
205     {
206         for ( FileItem fileItem : getListUploadedFiles( typeFichier, request.getSession( ) ) )
207         {
208             if ( fileItem.getName( ).equals( nomFichier ) )
209             {
210                 return fileItem;
211             }
212         }
213         return null;
214     }
215 
216     public int getFilesNumber( HttpServletRequest request )
217     {
218         HttpSession session = request.getSession( );
219         Object strSessionId = session.getId( );
220         Map<String, List<FileItem>> mapFileItemsSession = getMapAsynchronousUpload( ).get( strSessionId );
221 
222         if ( mapFileItemsSession == null )
223         {
224             return 0;
225         }
226         int count = 0;
227         for ( List<FileItem> files : mapFileItemsSession.values( ) )
228         {
229             count += files.size( );
230         }
231 
232         return count;
233     }
234 
235     private void remove( String strFieldName, HttpSession session, int nIndex, List<FileItem> uploadedFiles )
236     {
237         if ( ( uploadedFiles != null ) && !uploadedFiles.isEmpty( ) && ( uploadedFiles.size( ) > nIndex ) )
238         {
239             FileItem fileItem = uploadedFiles.remove( nIndex );
240             removeFile( session, fileItem, strFieldName );
241             fileItem.delete( );
242         }
243     }
244 
245     private void initMap( HttpSession session, String strFieldName )
246     {
247         String strSessionId = session.getId( );
248         Map<String, List<FileItem>> mapFileItemsSession = getMapAsynchronousUpload( ).get( strSessionId );
249 
250         if ( mapFileItemsSession == null )
251         {
252             synchronized( this )
253             {
254                 mapFileItemsSession = getMapAsynchronousUpload( ).get( strSessionId );
255 
256                 if ( mapFileItemsSession == null )
257                 {
258                     mapFileItemsSession = new ConcurrentHashMap<>( );
259                     loadFilesFromDisk( session, mapFileItemsSession );
260                     getMapAsynchronousUpload( ).put( strSessionId, mapFileItemsSession );
261                 }
262             }
263         }
264 
265         mapFileItemsSession.putIfAbsent(strFieldName, new ArrayList<>( ));
266     }
267 
268     protected void storeFile( HttpServletRequest request, FileItem fileToSave, String categoryName )
269     {
270         String id = getIdInSession( request.getSession( ) );
271         if ( ( null == id ) || ( null == fileToSave ) || StringUtils.isEmpty( categoryName ) )
272         {
273             return;
274         }
275 
276         String categoryPath = FilenameUtils.concat( getUploadDirectoryByChronology( request.getSession( ), getUploadDirectory( ) ), id );
277         categoryPath = FilenameUtils.concat( categoryPath, categoryName );
278 
279         File file = new File( categoryPath );
280         boolean isFolderCreation = file.mkdirs( );
281         if ( isFolderCreation )
282         {
283             LOGGER.info( "Création du path avec succès : " + categoryPath );
284         }
285 
286         String fileName = FilenameUtils.getName( fileToSave.getName( ) );
287         try
288         {
289             String filePath = FilenameUtils.concat( categoryPath, fileName );
290 
291             Path path = Paths.get( filePath );
292             Files.write( path, fileToSave.get( ) );
293 
294             if ( activeRemoteFilesSynchro )
295             {
296                 String remoteDirectoryFilesPath = FilenameUtils.concat( getRemoteDirectoryFilesPath( request.getSession( ), id ), categoryName );
297                 RemoteFilesUtils.upload( fileToSave, remoteDirectoryFilesPath );
298             }
299 
300             LOGGER.info( "Fichier sauvegardé avec succès : " + fileName );
301         }
302         catch( IOException ex )
303         {
304             LOGGER.error( "Impossible de sauvegarder le fichier : " + fileName, ex );
305         }
306     }
307 
308     private String getRemoteDirectoryFilesPath( HttpSession session, String id )
309     {
310         String [ ] directories = getUploadDirectory( ).split( "/" );
311         return FilenameUtils.concat( getUploadDirectoryByChronology( session, directories [directories.length - 1] ), id );
312     }
313 
314     private String getUploadDirectoryByChronology( HttpSession session, String uploadDirectory )
315     {
316         Date dateCreationDossier = getDateCreationDossier( session );
317         if ( dateCreationDossier != null )
318         {
319             SimpleDateFormat yearFormat = new SimpleDateFormat( "yyyy" );
320             SimpleDateFormat monthFormat = new SimpleDateFormat( "MM" );
321             String year = yearFormat.format( dateCreationDossier );
322             uploadDirectory = FilenameUtils.concat( uploadDirectory, year );
323             String month = monthFormat.format( dateCreationDossier );
324             uploadDirectory = FilenameUtils.concat( uploadDirectory, month );
325         }
326         return uploadDirectory;
327     }
328 
329     private void removeFile( HttpSession session, FileItem fileToSave, String categoryName )
330     {
331         String id = getIdInSession( session );
332         if ( ( null == id ) || ( null == fileToSave ) || StringUtils.isEmpty( categoryName ) )
333         {
334             return;
335         }
336 
337         String categoryPath = FilenameUtils.concat( getUploadDirectoryByChronology( session, getUploadDirectory( ) ), id );
338         categoryPath = FilenameUtils.concat( categoryPath, categoryName );
339 
340         String fileName = FilenameUtils.getName( fileToSave.getName( ) );
341         try
342         {
343             String filePath = FilenameUtils.concat( categoryPath, fileName );
344             Path path = Paths.get( filePath );
345             Files.deleteIfExists( path );
346 
347             if ( activeRemoteFilesSynchro )
348             {
349                 String remoteDirectoryFilesPath = FilenameUtils.concat( getRemoteDirectoryFilesPath( session, id ), categoryName );
350                 RemoteFilesUtils.removeRemoteFile( remoteDirectoryFilesPath + "/" + fileName );
351             }
352 
353             LOGGER.info( "Fichier supprimé avec succès : " + fileName );
354         }
355         catch( IOException ex )
356         {
357             LOGGER.error( "Impossible de supprimer le fichier : " + fileName, ex );
358         }
359     }
360 
361     private void loadFilesFromDisk( HttpSession session, final Map<String, List<FileItem>> mapUpload )
362     {
363         String directoryFilesPath = FilenameUtils.concat( getUploadDirectoryByChronology( session, getUploadDirectory( ) ), getIdInSession( session ) );
364         if ( directoryFilesPath != null )
365         {
366             if ( activeRemoteFilesSynchro )
367             {
368                 String remoteDirectoryFilesPath = getRemoteDirectoryFilesPath( session, getIdInSession( session ) );
369                 RemoteFilesUtils.getRemoteFilesInLocalDirectory( remoteDirectoryFilesPath, directoryFilesPath );
370             }
371 
372             Path pathUpload = Paths.get( directoryFilesPath );
373             if ( Files.exists( pathUpload ) )
374             {
375 
376                 try
377                 {
378                     Files.walkFileTree( pathUpload, EnumSet.noneOf( FileVisitOption.class ), 2, simpleFileVisitor( mapUpload ) );
379                 }
380                 catch( IOException e )
381                 {
382                     LOGGER.error( "Erreur lors de la lecture des fichiers présents sur le disque dans " + pathUpload.toString( ), e );
383                 }
384             }
385         }
386 
387     }
388 
389     public void removeFilesInSession( HttpServletRequest request )
390     {
391         getMapAsynchronousUpload( ).remove( request.getSession( ).getId( ) );
392         // getTracker().deleteTemporaryFiles();
393     }
394 
395     private SimpleFileVisitor<Path> simpleFileVisitor( final Map<String, List<FileItem>> mapUpload )
396     {
397         return new SimpleFileVisitor<Path>( )
398         {
399             private String currentDir;
400 
401             @Override
402             public FileVisitResult preVisitDirectory( Path dir, BasicFileAttributes attrs ) throws IOException
403             {
404                 currentDir = dir.getName( dir.getNameCount( ) - 1 ).toString( );
405                 return FileVisitResult.CONTINUE;
406             }
407 
408             @Override
409             public FileVisitResult visitFile( Path file, BasicFileAttributes attrs ) throws IOException
410             {
411                 if ( !attrs.isDirectory( ) )
412                 {
413                     // DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory( );
414                     // diskFileItemFactory.setFileCleaningTracker(getTracker());
415                     // FileItem fileItem = diskFileItemFactory.createItem( currentDir, Files.probeContentType( file ), false, FilenameUtils.getName(
416                     // file.getFileName( ).toString( ) ) );
417                     // Files.copy( file, fileItem.getOutputStream( ) );
418                     // List<FileItem> listFileItem;
419 
420                     FileItem fileItem = new DiskFileItemFactory( ).createItem( currentDir, Files.probeContentType( file ), false,
421                             FilenameUtils.getName( file.getFileName( ).toString( ) ) );
422                     Files.copy( file, fileItem.getOutputStream( ) );
423                     List<FileItem> listFileItem;
424 
425                     if ( mapUpload.get( currentDir ) != null )
426                     {
427                         listFileItem = mapUpload.get( currentDir );
428                     }
429                     else
430                     {
431                         listFileItem = new ArrayList<>( );
432                     }
433                     listFileItem.add( fileItem );
434                     mapUpload.put( currentDir, listFileItem );
435                 }
436                 return FileVisitResult.CONTINUE;
437             }
438         };
439     }
440 
441     public boolean storeFilesOnDisk( HttpServletRequest request, String fileName )
442     {
443         List<FileItem> filesList = getListUploadedFiles( fileName, request.getSession( ) );
444         boolean stored = false;
445         for ( FileItem fileItem : filesList )
446         {
447             storeFile( request, fileItem, fileName );
448             stored = true;
449         }
450 
451         return stored;
452     }
453 
454     public String getIdInSession( HttpSession session )
455     {
456         Object idInSession = session.getAttribute( getFileIdNameInSession( ) );
457         return idInSession != null ? idInSession.toString( ) : null;
458     }
459 
460     public abstract String getUploadDirectory( );
461 
462     public abstract String getFileIdNameInSession( );
463 
464     public abstract Date getDateCreationDossier( HttpSession session );
465 
466     // public DeleteFilesOnEndUploadCleaningTracker getTracker() {
467     // return tracker;
468     // }
469     //
470     // public void setTracker(DeleteFilesOnEndUploadCleaningTracker tracker) {
471     // this.tracker = tracker;
472     // }
473 
474 }