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.asynchronousupload.service;
35  
36  import java.util.ArrayList;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Locale;
40  import java.util.Map;
41  import java.util.concurrent.ConcurrentHashMap;
42  
43  import javax.servlet.http.HttpServletRequest;
44  import javax.servlet.http.HttpSession;
45  
46  import org.apache.commons.fileupload.FileItem;
47  import org.apache.commons.lang3.StringUtils;
48  
49  import fr.paris.lutece.portal.service.i18n.I18nService;
50  import fr.paris.lutece.portal.service.spring.SpringContextService;
51  import fr.paris.lutece.portal.service.util.AppException;
52  import fr.paris.lutece.util.filesystem.UploadUtil;
53  
54  public class AsynchronousUploadHandler extends AbstractAsynchronousUploadHandler
55  {
56  
57      private static final String BEAN_NAME = "asynchronous-upload.asynchronousUploadHandler";
58      private static final String HANDLER_NAME = "asynchronousUploadHandler";
59  
60      // Error messages
61      private static final String ERROR_MESSAGE_UNKNOWN_ERROR = "asynchronousupload.message.unknownError";
62  
63      /** contains uploaded file items */
64      private static Map<String, Map<String, List<FileItem>>> _mapAsynchronousUpload = new ConcurrentHashMap<>( );
65  
66      /**
67       * Get the handler
68       * 
69       * @return the handler
70       */
71      public static AsynchronousUploadHandler getHandler( )
72      {
73          return SpringContextService.getBean( BEAN_NAME );
74      }
75  
76      @Override
77      public String canUploadFiles( HttpServletRequest request, String strFieldName, List<FileItem> listFileItemsToUpload, Locale locale )
78      {
79          if ( StringUtils.isNotBlank( strFieldName ) )
80          {
81              String sessionId = getCustomSessionId( request.getSession( ) );
82              initMap( sessionId, strFieldName );
83  
84              return null;
85          }
86          return I18nService.getLocalizedString( ERROR_MESSAGE_UNKNOWN_ERROR, locale );
87      }
88  
89      @Override
90      public List<FileItem> getListUploadedFiles( String strFieldName, HttpSession session )
91      {
92          if ( StringUtils.isBlank( strFieldName ) )
93          {
94              throw new AppException( "id field name is not provided for the current file upload" );
95          }
96  
97          String sessionId = getCustomSessionId( session );
98  
99          initMap( sessionId, strFieldName );
100 
101         // find session-related files in the map
102         Map<String, List<FileItem>> mapFileItemsSession = _mapAsynchronousUpload.get( sessionId );
103 
104         return mapFileItemsSession.get( strFieldName );
105     }
106 
107     @Override
108     public void addFileItemToUploadedFilesList( FileItem fileItem, String strFieldName, HttpServletRequest request )
109     {
110         // This is the name that will be displayed in the form. We keep
111         // the original name, but clean it to make it cross-platform.
112         String strFileName = UploadUtil.cleanFileName( fileItem.getName( ).trim( ) );
113 
114         String sessionId = getCustomSessionId( request.getSession( ) );
115         initMap( sessionId, strFieldName );
116 
117         // Check if this file has not already been uploaded
118         List<FileItem> uploadedFiles = getListUploadedFiles( strFieldName, request.getSession( ) );
119 
120         if ( uploadedFiles != null )
121         {
122             boolean bNew = true;
123 
124             if ( !uploadedFiles.isEmpty( ) )
125             {
126                 Iterator<FileItem> iterUploadedFiles = uploadedFiles.iterator( );
127 
128                 while ( bNew && iterUploadedFiles.hasNext( ) )
129                 {
130                     FileItem uploadedFile = iterUploadedFiles.next( );
131                     String strUploadedFileName = UploadUtil.cleanFileName( uploadedFile.getName( ).trim( ) );
132                     // If we find a file with the same name and the same
133                     // length, we consider that the current file has
134                     // already been uploaded
135                     bNew = !( StringUtils.equals( strUploadedFileName, strFileName ) && ( uploadedFile.getSize( ) == fileItem.getSize( ) ) );
136                 }
137             }
138 
139             if ( bNew )
140             {
141                 uploadedFiles.add( fileItem );
142             }
143         }
144     }
145 
146     @Override
147     public String getHandlerName( )
148     {
149         return HANDLER_NAME;
150     }
151 
152     private void initMap( String strSessionId, String strFieldName )
153     {
154         // find session-related files in the map
155         Map<String, List<FileItem>> mapFileItemsSession = _mapAsynchronousUpload.get( strSessionId );
156 
157         // create map if not exists
158         if ( mapFileItemsSession == null )
159         {
160             synchronized( this )
161             {
162                 // Ignore double check locking error : assignation and instanciation of objects are separated.
163                 mapFileItemsSession = _mapAsynchronousUpload.computeIfAbsent( strSessionId, s -> new ConcurrentHashMap<>( ) );
164             }
165         }
166 
167         mapFileItemsSession.computeIfAbsent( strFieldName, s -> new ArrayList<>( ) );
168     }
169 
170     @Override
171     public void removeSessionFiles( HttpSession session )
172     {
173         String sessionId = (String) session.getAttribute( PARAM_CUSTOM_SESSION_ID );
174         if ( sessionId != null )
175         {
176             _mapAsynchronousUpload.remove( sessionId );
177         }
178 
179     }
180 }