View Javadoc
1   /*
2    * Copyright (c) 2002-2017, Mairie de 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.directory.service.upload;
35  
36  import fr.paris.lutece.plugins.blobstore.service.BlobStoreClientException;
37  import fr.paris.lutece.plugins.blobstore.service.IBlobStoreClientService;
38  import fr.paris.lutece.plugins.directory.business.EntryHome;
39  import fr.paris.lutece.plugins.directory.business.EntryTypeDownloadUrl;
40  import fr.paris.lutece.plugins.directory.business.File;
41  import fr.paris.lutece.plugins.directory.business.IEntry;
42  import fr.paris.lutece.plugins.directory.business.PhysicalFile;
43  import fr.paris.lutece.plugins.directory.business.PhysicalFileHome;
44  import fr.paris.lutece.plugins.directory.business.Record;
45  import fr.paris.lutece.plugins.directory.business.RecordField;
46  import fr.paris.lutece.plugins.directory.business.RecordFieldFilter;
47  import fr.paris.lutece.plugins.directory.business.RecordFieldHome;
48  import fr.paris.lutece.plugins.directory.service.DirectoryPlugin;
49  import fr.paris.lutece.plugins.directory.utils.DirectoryErrorException;
50  import fr.paris.lutece.plugins.directory.utils.DirectoryUtils;
51  import fr.paris.lutece.plugins.directory.utils.JSONUtils;
52  import fr.paris.lutece.plugins.directory.utils.UrlUtils;
53  import fr.paris.lutece.portal.service.fileupload.FileUploadService;
54  import fr.paris.lutece.portal.service.i18n.I18nService;
55  import fr.paris.lutece.portal.service.plugin.Plugin;
56  import fr.paris.lutece.portal.service.plugin.PluginService;
57  import fr.paris.lutece.portal.service.spring.SpringContextService;
58  import fr.paris.lutece.portal.service.util.AppException;
59  import fr.paris.lutece.portal.service.util.AppLogService;
60  import fr.paris.lutece.portal.web.upload.IAsynchronousUploadHandler;
61  import fr.paris.lutece.portal.web.upload.MultipartHttpServletRequest;
62  import fr.paris.lutece.util.filesystem.FileSystemUtil;
63  import fr.paris.lutece.util.filesystem.UploadUtil;
64  
65  import net.sf.json.JSONObject;
66  
67  import org.apache.commons.fileupload.FileItem;
68  import org.apache.commons.fileupload.FileItemHeaders;
69  import org.apache.commons.lang.StringUtils;
70  
71  import java.io.ByteArrayInputStream;
72  import java.io.IOException;
73  import java.io.InputStream;
74  import java.io.OutputStream;
75  import java.io.UnsupportedEncodingException;
76  
77  import java.util.ArrayList;
78  import java.util.Collections;
79  import java.util.Enumeration;
80  import java.util.Iterator;
81  import java.util.List;
82  import java.util.Locale;
83  import java.util.Map;
84  import java.util.concurrent.ConcurrentHashMap;
85  
86  import javax.servlet.http.HttpServletRequest;
87  import javax.servlet.http.HttpServletResponse;
88  import javax.servlet.http.HttpSession;
89  
90  /**
91   * Handler for asynchronous uploads. The <code>jessionid</code> parameter should be the <strong>REAL</strong> session id, not the flash player one. The uploaded
92   * files are deleted by SubForm when filling fields.
93   *
94   */
95  public class DirectoryAsynchronousUploadHandler implements IAsynchronousUploadHandler
96  {
97      /** contains uploaded file items */
98      /**
99       * &lt;sessionId,&lt;fieldName,fileItems&gt;&gt;
100      */
101     public static Map<String, Map<String, List<FileItem>>> _mapAsynchronousUpload = new ConcurrentHashMap<String, Map<String, List<FileItem>>>( );
102     private static final String BEAN_DIRECTORY_ASYNCHRONOUS_UPLOAD_HANDLER = "directory.asynchronousUploadHandler";
103     private static final String PREFIX_ENTRY_ID = "directory_";
104 
105     // UPLOAD
106     private static final String UPLOAD_SUBMIT_PREFIX = "_directory_upload_submit_directory_";
107     private static final String UPLOAD_DELETE_PREFIX = "_directory_upload_delete_directory_";
108     private static final String UPLOAD_CHECKBOX_PREFIX = "_directory_upload_checkbox_directory_";
109 
110     // PARAMETERS
111     private static final String PARAMETER_BLOB_KEY = "blob_key";
112     private static final String PARAMETER_BLOBSTORE = "blobstore";
113     private static final String PARAMETER_PLUGIN_NAME = "plugin_name";
114     private static final String PARAMETER_FIELD_NAME = "field_name";
115     private static final String PARAMETER_JSESSION_ID = "jsessionid";
116 
117     // PROPERTIES
118     private static final String PROPERTY_MESSAGE_ERROR_UPLOADING_FILE_SESSION_LOST = "directory.message.error.uploading_file.session_lost";
119     private IBlobStoreClientService _blobStoreClientService;
120 
121     /**
122      * Private constructor
123      */
124     private DirectoryAsynchronousUploadHandler( )
125     {
126     }
127 
128     /**
129      * Get the handler
130      * 
131      * @return the handler
132      */
133     public static DirectoryAsynchronousUploadHandler getHandler( )
134     {
135         return SpringContextService.getBean( BEAN_DIRECTORY_ASYNCHRONOUS_UPLOAD_HANDLER );
136     }
137 
138     /**
139      * Set the blobstore client service
140      * 
141      * @param blobStoreClientService
142      *            the blob store client service
143      */
144     public void setBlobStoreClientService( IBlobStoreClientService blobStoreClientService )
145     {
146         _blobStoreClientService = blobStoreClientService;
147     }
148 
149     /**
150      * {@inheritDoc}
151      */
152     @Override
153     public boolean isInvoked( HttpServletRequest request )
154     {
155         return DirectoryPlugin.PLUGIN_NAME.equals( request.getParameter( PARAMETER_PLUGIN_NAME ) );
156     }
157 
158     /**
159      * Check if the service is available
160      * 
161      * @return true if the service is available, false otherwise
162      */
163     public boolean isBlobStoreClientServiceAvailable( )
164     {
165         return _blobStoreClientService != null;
166     }
167 
168     /**
169      * {@inheritDoc}
170      * 
171      * category CALLED_BY_JS (directoryupload.js)
172      */
173     @Override
174     public void process( HttpServletRequest request, HttpServletResponse response, JSONObject mainObject, List<FileItem> listFileItemsToUpload )
175     {
176         // prevent 0 or multiple uploads for the same field
177         if ( ( listFileItemsToUpload == null ) || listFileItemsToUpload.isEmpty( ) )
178         {
179             throw new AppException( "No file uploaded" );
180         }
181 
182         String strIdSession = request.getParameter( PARAMETER_JSESSION_ID );
183 
184         if ( StringUtils.isNotBlank( strIdSession ) )
185         {
186             String strFieldName = request.getParameter( PARAMETER_FIELD_NAME );
187 
188             if ( StringUtils.isBlank( strFieldName ) )
189             {
190                 throw new AppException( "id entry is not provided for the current file upload" );
191             }
192 
193             initMap( strIdSession, strFieldName );
194 
195             // find session-related files in the map
196             Map<String, List<FileItem>> mapFileItemsSession = _mapAsynchronousUpload.get( strIdSession );
197 
198             List<FileItem> fileItemsSession = mapFileItemsSession.get( strFieldName );
199 
200             if ( canUploadFiles( strFieldName, fileItemsSession, listFileItemsToUpload, mainObject, request.getLocale( ) ) )
201             {
202                 fileItemsSession.addAll( listFileItemsToUpload );
203 
204                 JSONObject jsonListFileItems = JSONUtils.getUploadedFileJSON( fileItemsSession );
205                 mainObject.accumulateAll( jsonListFileItems );
206                 // add entry id to json
207                 JSONUtils.buildJsonSuccess( strFieldName, mainObject );
208             }
209         }
210         else
211         {
212             AppLogService.error( DirectoryAsynchronousUploadHandler.class.getName( ) + " : Session does not exists" );
213 
214             String strMessage = I18nService.getLocalizedString( PROPERTY_MESSAGE_ERROR_UPLOADING_FILE_SESSION_LOST, request.getLocale( ) );
215             JSONUtils.buildJsonError( mainObject, strMessage );
216         }
217     }
218 
219     /**
220      * Do upload a file in the blobstore webapp
221      * 
222      * @param strBaseUrl
223      *            the base url
224      * @param fileItem
225      *            the file
226      * @param strBlobStore
227      *            the blobstore service name
228      * @return the blob key of the uploaded file
229      * @throws BlobStoreClientException
230      *             Exception if there is an issue
231      */
232     public String doUploadFile( String strBaseUrl, FileItem fileItem, String strBlobStore ) throws BlobStoreClientException
233     {
234         if ( isBlobStoreClientServiceAvailable( ) )
235         {
236             return _blobStoreClientService.doUploadFile( strBaseUrl, fileItem, strBlobStore );
237         }
238 
239         return StringUtils.EMPTY;
240     }
241 
242     /**
243      * Do remove a file from a given record and entry
244      * 
245      * @param record
246      *            the record
247      * @param entry
248      *            the entry
249      * @param strWSRestUrl
250      *            the url of the WS rest
251      * @throws BlobStoreClientException
252      *             Exception if there is an issue
253      */
254     public void doRemoveFile( Record record, IEntry entry, String strWSRestUrl ) throws BlobStoreClientException
255     {
256         Plugin pluginDirectory = PluginService.getPlugin( DirectoryPlugin.PLUGIN_NAME );
257         RecordFieldFilter recordFieldFilter = new RecordFieldFilter( );
258         recordFieldFilter.setIdDirectory( record.getDirectory( ).getIdDirectory( ) );
259         recordFieldFilter.setIdEntry( entry.getIdEntry( ) );
260         recordFieldFilter.setIdRecord( record.getIdRecord( ) );
261 
262         List<RecordField> listRecordFields = RecordFieldHome.getRecordFieldList( recordFieldFilter, pluginDirectory );
263 
264         if ( ( listRecordFields != null ) && !listRecordFields.isEmpty( ) )
265         {
266             for ( RecordField recordField : listRecordFields )
267             {
268                 doRemoveFile( recordField, entry, strWSRestUrl );
269             }
270         }
271     }
272 
273     /**
274      * Do remove a file from a given record field
275      * 
276      * @param recordField
277      *            the record field
278      * @param entry
279      *            the entry
280      * @param strWSRestUrl
281      *            the url of the WS rest
282      * @throws BlobStoreClientException
283      *             Exception if there is an issue
284      */
285     public void doRemoveFile( RecordField recordField, IEntry entry, String strWSRestUrl ) throws BlobStoreClientException
286     {
287         if ( isBlobStoreClientServiceAvailable( ) && ( recordField != null ) )
288         {
289             // Get the download file url
290             String strDownloadFileUrl = entry.convertRecordFieldTitleToString( recordField, null, false );
291 
292             if ( StringUtils.isNotBlank( strDownloadFileUrl ) )
293             {
294                 // Parse the download file url to fetch the parameters
295                 Map<String, List<String>> mapParameters = UrlUtils.getMapParametersFromUrl( strDownloadFileUrl );
296                 List<String> parameterBlobKey = mapParameters.get( PARAMETER_BLOB_KEY );
297                 List<String> parameterBlobStore = mapParameters.get( PARAMETER_BLOBSTORE );
298 
299                 if ( ( parameterBlobKey != null ) && !parameterBlobKey.isEmpty( ) && ( parameterBlobStore != null ) && !parameterBlobStore.isEmpty( ) )
300                 {
301                     String strBlobKey = parameterBlobKey.get( 0 );
302                     String strBlobStore = parameterBlobStore.get( 0 );
303                     _blobStoreClientService.doDeleteFile( strWSRestUrl, strBlobStore, strBlobKey );
304                 }
305             }
306         }
307     }
308 
309     /**
310      * Get the file url
311      * 
312      * @param strBaseUrl
313      *            the base url
314      * @param strBlobKey
315      *            the blob key
316      * @param strBlobStore
317      *            the blobstore service name
318      * @return the file url
319      * @throws BlobStoreClientException
320      *             Exception if there is an issue
321      */
322     public String getFileUrl( String strBaseUrl, String strBlobKey, String strBlobStore ) throws BlobStoreClientException
323     {
324         if ( isBlobStoreClientServiceAvailable( ) )
325         {
326             return _blobStoreClientService.getFileUrl( strBaseUrl, strBlobKey, strBlobStore );
327         }
328 
329         return StringUtils.EMPTY;
330     }
331 
332     /**
333      * Get the file name from a given url
334      * 
335      * @param strUrl
336      *            the url
337      * @return the file name
338      * @throws BlobStoreClientException
339      *             Exception if there is an issue
340      */
341     public String getFileName( String strUrl ) throws BlobStoreClientException
342     {
343         if ( isBlobStoreClientServiceAvailable( ) )
344         {
345             return _blobStoreClientService.getFileName( strUrl );
346         }
347 
348         return StringUtils.EMPTY;
349     }
350 
351     /**
352      * Do download the file
353      * 
354      * @param strUrl
355      *            the file of the file to download
356      * @param strFilePath
357      *            the file path to download
358      * @throws BlobStoreClientException
359      *             exception if there is an error
360      */
361     public void doDownloadFile( String strUrl, String strFilePath ) throws BlobStoreClientException
362     {
363         if ( isBlobStoreClientServiceAvailable( ) )
364         {
365             _blobStoreClientService.doDownloadFile( strUrl, strFilePath );
366         }
367     }
368 
369     /**
370      * Do download the file
371      * 
372      * @param strUrl
373      *            the file of the file to download
374      * @return a {@link FileItem}
375      * @throws BlobStoreClientException
376      *             exception if there is an error
377      */
378     public FileItem doDownloadFile( String strUrl ) throws BlobStoreClientException
379     {
380         if ( isBlobStoreClientServiceAvailable( ) )
381         {
382             return _blobStoreClientService.doDownloadFile( strUrl );
383         }
384 
385         return null;
386     }
387 
388     /**
389      * Gets the fileItem for the entry and the given session.
390      * 
391      * @param strIdEntry
392      *            the entry
393      * @param strSessionId
394      *            the session id
395      * @return the fileItem found, <code>null</code> otherwise.
396      */
397     public List<FileItem> getFileItems( String strIdEntry, String strSessionId )
398     {
399         initMap( strSessionId, buildFieldName( strIdEntry ) );
400 
401         if ( StringUtils.isBlank( strIdEntry ) )
402         {
403             throw new AppException( "id entry is not provided for the current file upload" );
404         }
405 
406         // find session-related files in the map
407         Map<String, List<FileItem>> mapFileItemsSession = _mapAsynchronousUpload.get( strSessionId );
408 
409         return mapFileItemsSession.get( buildFieldName( strIdEntry ) );
410     }
411 
412     /**
413      * Removes the file from the list.
414      *
415      * @param strIdEntry
416      *            the entry id
417      * @param strSessionId
418      *            the session id
419      * @param nIndex
420      *            the n index
421      */
422     public synchronized void removeFileItem( String strIdEntry, String strSessionId, int nIndex )
423     {
424         // Remove the file (this will also delete the file physically)
425         List<FileItem> uploadedFiles = getFileItems( strIdEntry, strSessionId );
426 
427         if ( ( uploadedFiles != null ) && !uploadedFiles.isEmpty( ) && ( uploadedFiles.size( ) > nIndex ) )
428         {
429             // Remove the object from the Hashmap
430             FileItem fileItem = uploadedFiles.remove( nIndex );
431             fileItem.delete( );
432         }
433     }
434 
435     /**
436      * Removes all files associated to the session
437      * 
438      * @param strSessionId
439      *            the session id
440      */
441     public synchronized void removeSessionFiles( String strSessionId )
442     {
443         _mapAsynchronousUpload.remove( strSessionId );
444     }
445 
446     /**
447      * Add file item to the list of uploaded files
448      * 
449      * @param fileItem
450      *            the file item
451      * @param strIdEntry
452      *            the id entry
453      * @param session
454      *            the session
455      */
456     public void addFileItemToUploadedFile( FileItem fileItem, String strIdEntry, HttpSession session )
457     {
458         // This is the name that will be displayed in the form. We keep
459         // the original name, but clean it to make it cross-platform.
460         String strFileName = UploadUtil.cleanFileName( FileUploadService.getFileNameOnly( fileItem ) );
461 
462         // Check if this file has not already been uploaded
463         List<FileItem> uploadedFiles = getFileItems( strIdEntry, session.getId( ) );
464 
465         if ( ( uploadedFiles != null ) && !uploadedFiles.isEmpty( ) )
466         {
467             Iterator<FileItem> iterUploadedFiles = uploadedFiles.iterator( );
468             boolean bNew = true;
469 
470             while ( bNew && iterUploadedFiles.hasNext( ) )
471             {
472                 FileItem uploadedFile = iterUploadedFiles.next( );
473                 String strUploadedFileName = UploadUtil.cleanFileName( FileUploadService.getFileNameOnly( uploadedFile ) );
474                 // If we find a file with the same name and the same
475                 // length, we consider that the current file has
476                 // already been uploaded
477                 bNew = !( strUploadedFileName.equals( strFileName ) && ( uploadedFile.getSize( ) == fileItem.getSize( ) ) );
478             }
479 
480             if ( !bNew )
481             {
482                 // Delete the temporary file
483                 // file.delete( );
484 
485                 // TODO : Raise an error
486             }
487         }
488 
489         if ( uploadedFiles != null )
490         {
491             uploadedFiles.add( fileItem );
492         }
493     }
494 
495     /**
496      * Build the field name from a given id entry i.e. : directory_1
497      * 
498      * @param strIdEntry
499      *            the id entry
500      * @return the field name
501      */
502     public String buildFieldName( String strIdEntry )
503     {
504         return PREFIX_ENTRY_ID + strIdEntry;
505     }
506 
507     /**
508      * Checks the request parameters to see if an upload submit has been called.
509      *
510      * @param request
511      *            the HTTP request
512      * @return the name of the upload action, if any. Null otherwise.
513      */
514     public String getUploadAction( HttpServletRequest request )
515     {
516         Enumeration<String> enumParamNames = request.getParameterNames( );
517 
518         while ( enumParamNames.hasMoreElements( ) )
519         {
520             String paramName = enumParamNames.nextElement( );
521 
522             if ( paramName.startsWith( UPLOAD_SUBMIT_PREFIX ) || paramName.startsWith( UPLOAD_DELETE_PREFIX ) )
523             {
524                 return paramName;
525             }
526         }
527 
528         return null;
529     }
530 
531     /**
532      * Performs an upload action.
533      *
534      * @param request
535      *            the HTTP request
536      * @param strUploadAction
537      *            the name of the upload action
538      * @param map
539      *            the map of idEntry, RecordFields 
540      * @param record
541      *            the record
542      * @param plugin
543      *            the plugin
544      * @throws DirectoryErrorException
545      *             exception if there is an error
546      */
547     public void doUploadAction( HttpServletRequest request, String strUploadAction, Map<String, List<RecordField>> map, Record record, Plugin plugin )
548             throws DirectoryErrorException
549     {
550         String strIdEntry = findIdEntryFromAction( strUploadAction );
551 
552         if ( strUploadAction.startsWith( UPLOAD_SUBMIT_PREFIX ) )
553         {
554             doUploadFile( request, strIdEntry, map, record );
555         }
556         else
557             if ( strUploadAction.startsWith( UPLOAD_DELETE_PREFIX ) )
558             {
559                 doDeleteFile( request, strIdEntry, map );
560             }
561     }
562 
563     /**
564      * Finds the entry id from the specified action
565      * 
566      * @param strAction
567      *            the action
568      * @return the entry id as a {@code String}
569      */
570     private String findIdEntryFromAction( String strAction )
571     {
572         return strAction.startsWith( UPLOAD_SUBMIT_PREFIX ) ? strAction.substring( UPLOAD_SUBMIT_PREFIX.length( ) ) : strAction.substring( UPLOAD_DELETE_PREFIX
573                 .length( ) );
574     }
575 
576     /**
577      * Uploads a file
578      * 
579      * @param request
580      *            the request containing the file to upload
581      * @param strIdEntry
582      *            the entry id linked to the file
583      * @param map
584      *            the map of {@code RecordFields}
585      * @param record
586      *            the record on which the file is added
587      * @throws DirectoryErrorException
588      *             if there is an error during the upload
589      */
590     public void doUploadFile( HttpServletRequest request, String strIdEntry, Map<String, List<RecordField>> map, Record record ) throws DirectoryErrorException
591     {
592         FileItem fileUploaded = findUploadedFileFromRequest( request, strIdEntry );
593 
594         if ( fileUploaded != null && StringUtils.isNotBlank( fileUploaded.getName( ) ) )
595         {
596             canUploadFile( request, fileUploaded, strIdEntry );
597 
598             List<RecordField> listRecordFields = findRecordFields( map, strIdEntry );
599             RecordField recordFieldForUploadedFile = createRecordFieldForUploadedFile( fileUploaded, strIdEntry );
600             addRecordFieldForUploadedFile( recordFieldForUploadedFile, record, listRecordFields );
601             addFileItemToUploadedFile( fileUploaded, strIdEntry, request.getSession( ) );
602         }
603     }
604 
605     /**
606      * Finds the uploaded file from the specified request
607      * 
608      * @param request
609      *            the request containing the file to upload
610      * @param strIdEntry
611      *            the entry id linked to the file
612      * @return the uploaded file as a {@code FileItem}
613      */
614     private FileItem findUploadedFileFromRequest( HttpServletRequest request, String strIdEntry )
615     {
616         MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
617         String strFileParameter = buildFieldName( strIdEntry );
618 
619         return multipartRequest.getFile( strFileParameter );
620     }
621 
622     /**
623      * Tests if the file can be uploaded
624      * 
625      * @param request
626      *            the request used to test
627      * @param fileItem
628      *            the file to test
629      * @param strIdEntry
630      *            the entry id linked to the file
631      * @throws DirectoryErrorException
632      *             if the file cannot be uploaded
633      */
634     private void canUploadFile( HttpServletRequest request, FileItem fileItem, String strIdEntry ) throws DirectoryErrorException
635     {
636         HttpSession session = request.getSession( );
637         String strFieldName = buildFieldName( strIdEntry );
638         List<FileItem> listFileItemsToUpload = new ArrayList<FileItem>( );
639         listFileItemsToUpload.add( fileItem );
640 
641         canUploadFiles( strFieldName, getFileItems( strIdEntry, session.getId( ) ), listFileItemsToUpload, request.getLocale( ) );
642     }
643 
644     /**
645      * Finds the record fields linked to the specified entry from the specified map
646      * 
647      * @param map
648      *            the map of {@code RecordFields}
649      * @param strIdEntry
650      *            the entry id
651      * @return the list of record fields
652      */
653     private List<RecordField> findRecordFields( Map<String, List<RecordField>> map, String strIdEntry )
654     {
655         List<RecordField> listRecordFields = map.get( strIdEntry );
656 
657         if ( listRecordFields == null )
658         {
659             listRecordFields = new ArrayList<RecordField>( );
660             map.put( strIdEntry, listRecordFields );
661         }
662 
663         return listRecordFields;
664     }
665 
666     /**
667      * Creates a {@code RecordField} for the uploaded file
668      * 
669      * @param fileItem
670      *            the uploaded file
671      * @param strIdEntry
672      *            the entry id linked to the file
673      * @return the created {@code RecordField}
674      */
675     private RecordField createRecordFieldForUploadedFile( FileItem fileItem, String strIdEntry )
676     {
677         RecordField recordFieldForUploadedFile = null;
678         Plugin plugin = PluginService.getPlugin( DirectoryPlugin.PLUGIN_NAME );
679 
680         // Add the file to the map of idEntry, RecordFields
681         IEntry entry = EntryHome.findByPrimaryKey( DirectoryUtils.convertStringToInt( strIdEntry ), plugin );
682 
683         if ( entry != null )
684         {
685             recordFieldForUploadedFile = new RecordField( );
686             recordFieldForUploadedFile.setEntry( entry );
687 
688             String strFilename = FileUploadService.getFileNameOnly( fileItem );
689 
690             if ( ( fileItem.get( ) != null ) && ( fileItem.getSize( ) < Integer.MAX_VALUE ) )
691             {
692                 if ( entry instanceof EntryTypeDownloadUrl )
693                 {
694                     recordFieldForUploadedFile.setFileName( strFilename );
695                     recordFieldForUploadedFile.setFileExtension( FileSystemUtil.getMIMEType( strFilename ) );
696                 }
697                 else
698                 {
699                     PhysicalFile physicalFile = new PhysicalFile( );
700                     physicalFile.setValue( fileItem.get( ) );
701 
702                     File file = new File( );
703                     file.setPhysicalFile( physicalFile );
704                     file.setTitle( strFilename );
705                     file.setSize( (int) fileItem.getSize( ) );
706                     file.setMimeType( FileSystemUtil.getMIMEType( strFilename ) );
707 
708                     recordFieldForUploadedFile.setFile( file );
709                 }
710             }
711         }
712 
713         return recordFieldForUploadedFile;
714     }
715 
716     /**
717      * Adds the record field in the specified record and in the specified list
718      * 
719      * @param recordFieldForUploadedFile
720      *            the record field to add
721      * @param record
722      *            the record
723      * @param listRecordFields
724      *            the list of record fields
725      */
726     private void addRecordFieldForUploadedFile( RecordField recordFieldForUploadedFile, Record record, List<RecordField> listRecordFields )
727     {
728         if ( recordFieldForUploadedFile != null )
729         {
730             recordFieldForUploadedFile.setRecord( record );
731             listRecordFields.add( recordFieldForUploadedFile );
732         }
733     }
734 
735     /**
736      * Deletes a file
737      * 
738      * @param request
739      *            the request containing the file to delete
740      * @param strIdEntry
741      *            the entry id linked to the file
742      * @param map
743      *            the map of {@code RecordFields}
744      */
745     public void doDeleteFile( HttpServletRequest request, String strIdEntry, Map<String, List<RecordField>> map )
746     {
747         HttpSession session = request.getSession( false );
748 
749         if ( session != null )
750         {
751             // Some previously uploaded files were deleted
752             // Build the prefix of the associated checkboxes
753             String strPrefix = UPLOAD_CHECKBOX_PREFIX + strIdEntry;
754 
755             // Look for the checkboxes in the request
756             Enumeration<String> enumParamNames = request.getParameterNames( );
757             List<Integer> listIndexes = new ArrayList<Integer>( );
758 
759             while ( enumParamNames.hasMoreElements( ) )
760             {
761                 String strParamName = enumParamNames.nextElement( );
762 
763                 if ( strParamName.startsWith( strPrefix ) )
764                 {
765                     // Get the index from the name of the checkbox
766                     listIndexes.add( Integer.parseInt( strParamName.substring( strPrefix.length( ) ) ) );
767                 }
768             }
769 
770             Collections.sort( listIndexes );
771             Collections.reverse( listIndexes );
772 
773             for ( int nIndex : listIndexes )
774             {
775                 // Remove from the map of <idEntry, RecordField>
776                 List<RecordField> listRecordFields = map.get( strIdEntry );
777 
778                 if ( listRecordFields != null )
779                 {
780                     listRecordFields.remove( nIndex );
781                 }
782 
783                 // Remove from the asynchronous uploaded files map
784                 removeFileItem( strIdEntry, session.getId( ), nIndex );
785             }
786         }
787     }
788 
789     /**
790      * Performs an upload action.
791      *
792      * @param request
793      *            the HTTP request
794      * @param strUploadAction
795      *            the name of the upload action
796      * @param map
797      *            the map of idEntry, RecordFields
798      * @throws DirectoryErrorException
799      *             exception if there is an error
800      */
801     public void doUploadAction( HttpServletRequest request, String strUploadAction, Map<String, List<RecordField>> map ) throws DirectoryErrorException
802     {
803         String strIdEntry = findIdEntryFromAction( strUploadAction );
804 
805         if ( strUploadAction.startsWith( UPLOAD_SUBMIT_PREFIX ) )
806         {
807             doUploadFile( request, strIdEntry, map );
808         }
809         else
810             if ( strUploadAction.startsWith( UPLOAD_DELETE_PREFIX ) )
811             {
812                 doDeleteFile( request, strIdEntry, map );
813             }
814     }
815 
816     /**
817      * Uploads a file
818      * 
819      * @param request
820      *            the request containing the file to upload
821      * @param strIdEntry
822      *            the entry id linked to the file
823      * @param map
824      *            the map of {@code RecordFields}
825      * @throws DirectoryErrorException
826      *             if there is an error during the upload
827      */
828     public void doUploadFile( HttpServletRequest request, String strIdEntry, Map<String, List<RecordField>> map ) throws DirectoryErrorException
829     {
830         FileItem fileUploaded = findUploadedFileFromRequest( request, strIdEntry );
831 
832         if ( fileUploaded != null && StringUtils.isNotBlank( fileUploaded.getName( ) ) )
833         {
834             canUploadFile( request, fileUploaded, strIdEntry );
835 
836             addFileItemToUploadedFile( fileUploaded, strIdEntry, request.getSession( ) );
837         }
838     }
839 
840     /**
841      * Reinit the map with the default files stored in database and blobstore
842      * 
843      * @param request
844      *            the HTTP request
845      * @param map
846      *            the map idEntry, RecordFields
847      * @param plugin
848      *            the plugin
849      */
850     public void reinitMap( HttpServletRequest request, Map<String, List<RecordField>> map, Plugin plugin )
851     {
852         HttpSession session = request.getSession( );
853         removeSessionFiles( session.getId( ) );
854 
855         if ( ( map != null ) && !map.isEmpty( ) )
856         {
857             for ( java.util.Map.Entry<String, List<RecordField>> param : map.entrySet( ) )
858             {
859                 for ( RecordField recordField : param.getValue( ) )
860                 {
861                     if ( recordField != null )
862                     {
863                         IEntry entry = recordField.getEntry( );
864 
865                         if ( ( recordField.getFile( ) != null ) && ( recordField.getFile( ).getPhysicalFile( ) != null ) && !recordField.isLittleThumbnail( )
866                                 && !recordField.isBigThumbnail( ) )
867                         {
868                             // The little thumbnail and the big thumbnail should not be stored in the session
869                             File file = recordField.getFile( );
870                             PhysicalFile physicalFile = PhysicalFileHome.findByPrimaryKey( file.getPhysicalFile( ).getIdPhysicalFile( ), plugin );
871                             FileItem fileItem = new DirectoryFileItem( physicalFile.getValue( ), file.getTitle( ) );
872                             // Add the file item to the map
873                             addFileItemToUploadedFile( fileItem, Integer.toString( entry.getIdEntry( ) ), session );
874                         }
875                         else
876                             if ( recordField.getEntry( ) instanceof EntryTypeDownloadUrl && isBlobStoreClientServiceAvailable( ) )
877                             {
878                                 // Different behaviour if the entry is an EntryTypeDownloadUrl
879                                 FileItem fileItem;
880 
881                                 try
882                                 {
883                                     fileItem = doDownloadFile( recordField.getValue( ) );
884 
885                                     FileItem directoryFileItem = new DirectoryFileItem( fileItem.get( ), fileItem.getName( ) );
886                                     // Add the file item to the map
887                                     addFileItemToUploadedFile( directoryFileItem, Integer.toString( entry.getIdEntry( ) ), session );
888                                 }
889                                 catch( BlobStoreClientException e )
890                                 {
891                                     AppLogService.error( DirectoryAsynchronousUploadHandler.class.getName( ) + " - Error when reinit map. Cause : "
892                                             + e.getMessage( ) );
893                                 }
894                             }
895                     }
896                 }
897             }
898         }
899     }
900 
901     /**
902      * Init the map
903      * 
904      * @param strSessionId
905      *            the session id
906      * @param strFieldName
907      *            the field name
908      */
909     private void initMap( String strSessionId, String strFieldName )
910     {
911         // find session-related files in the map
912         Map<String, List<FileItem>> mapFileItemsSession = _mapAsynchronousUpload.get( strSessionId );
913 
914         // create map if not exists
915         if ( mapFileItemsSession == null )
916         {
917             synchronized( _mapAsynchronousUpload )
918             {
919                 if ( _mapAsynchronousUpload.get( strSessionId ) == null )
920                 {
921                     mapFileItemsSession = new ConcurrentHashMap<String, List<FileItem>>( );
922                     _mapAsynchronousUpload.put( strSessionId, mapFileItemsSession );
923                 }
924             }
925         }
926 
927         if ( mapFileItemsSession != null )
928         {
929             List<FileItem> listFileItems = mapFileItemsSession.get( strFieldName );
930 
931             if ( listFileItems == null )
932             {
933                 listFileItems = new ArrayList<FileItem>( );
934                 mapFileItemsSession.put( strFieldName, listFileItems );
935             }
936         }
937     }
938 
939     /**
940      * Check if the file can be uploaded or not. This method will check the size of each file and the number max of files that can be uploaded.
941      * 
942      * @param strFieldName
943      *            the field name
944      * @param listUploadedFileItems
945      *            the list of uploaded files
946      * @param listFileItemsToUpload
947      *            the list of files to upload
948      * @param mainObject
949      *            the JSON object to complete if there is an error
950      * @param locale
951      *            the locale
952      * @return true if the list of files can be uploaded, false otherwise
953      * category CALLED_BY_JS (directoryupload.js)
954      */
955     private boolean canUploadFiles( String strFieldName, List<FileItem> listUploadedFileItems, List<FileItem> listFileItemsToUpload, JSONObject mainObject,
956             Locale locale )
957     {
958         if ( StringUtils.isNotBlank( strFieldName ) )
959         {
960             String strIdEntry = strFieldName.substring( PREFIX_ENTRY_ID.length( ) );
961             int nIdEntry = DirectoryUtils.convertStringToInt( strIdEntry );
962             IEntry entry = EntryHome.findByPrimaryKey( nIdEntry, DirectoryUtils.getPlugin( ) );
963 
964             if ( entry != null )
965             {
966                 try
967                 {
968                     entry.canUploadFiles( listUploadedFileItems, listFileItemsToUpload, locale );
969                 }
970                 catch( DirectoryErrorException e )
971                 {
972                     JSONUtils.buildJsonError( mainObject, e.getErrorMessage( ) );
973 
974                     return false;
975                 }
976 
977                 return true;
978             }
979         }
980 
981         return false;
982     }
983 
984     /**
985      * Check if the file can be uploaded or not. This method will check the size of each file and the number max of files that can be uploaded.
986      * 
987      * @param strFieldName
988      *            the field name
989      * @param listUploadedFileItems
990      *            the list of uploaded files
991      * @param listFileItemsToUpload
992      *            the list of files to upload
993      * @param locale
994      *            the locale
995      * @throws DirectoryErrorException
996      *             exception if there is an error
997      */
998     private void canUploadFiles( String strFieldName, List<FileItem> listUploadedFileItems, List<FileItem> listFileItemsToUpload, Locale locale )
999             throws DirectoryErrorException
1000     {
1001         if ( StringUtils.isNotBlank( strFieldName ) )
1002         {
1003             String strIdEntry = strFieldName.substring( PREFIX_ENTRY_ID.length( ) );
1004             int nIdEntry = DirectoryUtils.convertStringToInt( strIdEntry );
1005             IEntry entry = EntryHome.findByPrimaryKey( nIdEntry, DirectoryUtils.getPlugin( ) );
1006 
1007             if ( entry != null )
1008             {
1009                 entry.canUploadFiles( listUploadedFileItems, listFileItemsToUpload, locale );
1010             }
1011         }
1012     }
1013 
1014     /**
1015      *
1016      * DirectoryFileItem : builds fileItem from json response.
1017      *
1018      */
1019     private static class DirectoryFileItem implements FileItem
1020     {
1021         private static final long serialVersionUID = 1L;
1022         private byte [ ] _bValue;
1023         private final String _strFileName;
1024         private FileItemHeaders _fileItemHeaders;
1025 
1026         /**
1027          * FormFileItem
1028          * 
1029          * @param bValue
1030          *            the byte value
1031          * @param strFileName
1032          *            the file name
1033          */
1034         public DirectoryFileItem( byte [ ] bValue, String strFileName )
1035         {
1036             _bValue = bValue;
1037             _strFileName = strFileName;
1038         }
1039 
1040         /**
1041          * {@inheritDoc}
1042          */
1043         @Override
1044         public void delete( )
1045         {
1046             _bValue = null;
1047         }
1048 
1049         /**
1050          * {@inheritDoc}
1051          */
1052         @Override
1053         public byte [ ] get( )
1054         {
1055             return _bValue;
1056         }
1057 
1058         /**
1059          * {@inheritDoc}
1060          */
1061         @Override
1062         public String getContentType( )
1063         {
1064             return FileSystemUtil.getMIMEType( _strFileName );
1065         }
1066 
1067         /**
1068          * {@inheritDoc}
1069          */
1070         @Override
1071         public String getFieldName( )
1072         {
1073             return null;
1074         }
1075 
1076         /**
1077          * {@inheritDoc}
1078          */
1079         @Override
1080         public InputStream getInputStream( ) throws IOException
1081         {
1082             return new ByteArrayInputStream( _bValue );
1083         }
1084 
1085         /**
1086          * {@inheritDoc}
1087          */
1088         @Override
1089         public String getName( )
1090         {
1091             return _strFileName;
1092         }
1093 
1094         /**
1095          * {@inheritDoc}
1096          */
1097         @Override
1098         public OutputStream getOutputStream( ) throws IOException
1099         {
1100             return null;
1101         }
1102 
1103         /**
1104          * {@inheritDoc}
1105          */
1106         @Override
1107         public long getSize( )
1108         {
1109             return _bValue.length;
1110         }
1111 
1112         /**
1113          * {@inheritDoc}
1114          */
1115         @Override
1116         public String getString( )
1117         {
1118             return new String( _bValue );
1119         }
1120 
1121         /**
1122          * {@inheritDoc}
1123          */
1124         @Override
1125         public String getString( String encoding ) throws UnsupportedEncodingException
1126         {
1127             return new String( _bValue, encoding );
1128         }
1129 
1130         /**
1131          * {@inheritDoc}
1132          */
1133         @Override
1134         public boolean isFormField( )
1135         {
1136             return false;
1137         }
1138 
1139         /**
1140          * {@inheritDoc}
1141          */
1142         @Override
1143         public boolean isInMemory( )
1144         {
1145             return true;
1146         }
1147 
1148         /**
1149          * {@inheritDoc}
1150          */
1151         @Override
1152         public void setFieldName( String strName )
1153         {
1154             // nothing
1155         }
1156 
1157         /**
1158          * {@inheritDoc}
1159          */
1160         @Override
1161         public void setFormField( boolean bState )
1162         {
1163             // nothing
1164         }
1165 
1166         /**
1167          * {@inheritDoc}
1168          */
1169         @Override
1170         public void write( java.io.File file ) throws Exception
1171         {
1172             // nothing
1173         }
1174 
1175         /**
1176          * {@inheritDoc}
1177          */
1178         @Override
1179         public FileItemHeaders getHeaders( )
1180         {
1181             return _fileItemHeaders;
1182         }
1183 
1184         /**
1185          * {@inheritDoc}
1186          */
1187         @Override
1188         public void setHeaders( FileItemHeaders headers )
1189         {
1190             _fileItemHeaders = headers;
1191         }
1192     }
1193 }