View Javadoc
1   /*
2    * Copyright (c) 2002-2020, 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.participatoryideation.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.lang.StringUtils;
48  
49  import fr.paris.lutece.plugins.asynchronousupload.service.AbstractAsynchronousUploadHandler;
50  import fr.paris.lutece.portal.service.i18n.I18nService;
51  import fr.paris.lutece.portal.service.util.AppException;
52  import fr.paris.lutece.portal.service.util.AppPropertiesService;
53  import fr.paris.lutece.util.filesystem.UploadUtil;
54  
55  public class IdeationUploadHandler extends AbstractAsynchronousUploadHandler
56  {
57      public static final String BEAN_NAME = "participatoryideation.ProposalAsynchronousHandler";
58  
59      private static final String HANDLER_NAME = "ideationProposalUploadHandler";
60  
61      // Error messages
62      private static final String ERROR_MESSAGE_UPLOAD_PJS_MAX_SIZE = "participatoryideation.upload.message.uploadPjsMaxSize";
63      private static final String ERROR_MESSAGE_UPLOAD_NB_PJS_MAX = "participatoryideation.upload.message.uploadNbPjsMax";
64      private static final String ERROR_MESSAGE_IMAGE_MIME_TYPE_AUTORIZED = "participatoryideation.upload.message.imageMimeTypeAuthorized";
65      private static final String ERROR_MESSAGE_DOC_MIME_TYPE_AUTORIZED = "participatoryideation.upload.message.docMimeTypeAuthorized";
66      // PROPERTIES
67      private static final String PROPERTY_UPLOAD_PJS_MAX_SIZE = "participatoryideation.uploadPjsMaxSize";
68      private static final String PROPERTY_UPLOAD_NB_PJS_MAX = "participatoryideation.uploadNbPjsMax";
69  
70      private static final String PROPERTY_UPLOAD_IMAGE_MIME_TYPE_AUTORIZED = "participatoryideation.uploadImageMimeTypeAuthorized";
71      private static final String PROPERTY_UPLOAD_DOC_MIME_TYPE_AUTORIZED = "participatoryideation.uploadDocMimeTypeAuthorized";
72      // PARAMETERS
73      private static final String PARAMETER_IMGS = "imgs";
74      private static final String PARAMETER_DOCS = "docs";
75  
76      private static final int DEFAULT_MAX_FILE_SIZE = 2097152;
77      private static final int DEFAULT_MAX_PJS = 5;
78      // Could be just "images" and "docs"
79      /* <sessionId,<fieldName,fileItems>> */
80      /* contains uploaded file items */
81      private static Map<String, Map<String, List<FileItem>>> _mapAsynchronousUpload = new ConcurrentHashMap<String, Map<String, List<FileItem>>>( );
82  
83      @Override
84      public void addFileItemToUploadedFilesList( FileItem fileItem, String strFieldName, HttpServletRequest request )
85      {
86          // This is the name that will be displayed in the form. We keep
87          // the original name, but clean it to make it cross-platform.
88          String strFileName = UploadUtil.cleanFileName( fileItem.getName( ).trim( ) );
89  
90          initMap( request.getSession( ).getId( ), strFieldName );
91  
92          // Check if this file has not already been uploaded
93          List<FileItem> uploadedFiles = getListUploadedFiles( strFieldName, request.getSession( ) );
94  
95          if ( uploadedFiles != null )
96          {
97              boolean bNew = true;
98  
99              if ( !uploadedFiles.isEmpty( ) )
100             {
101                 Iterator<FileItem> iterUploadedFiles = uploadedFiles.iterator( );
102 
103                 while ( bNew && iterUploadedFiles.hasNext( ) )
104                 {
105                     FileItem uploadedFile = iterUploadedFiles.next( );
106                     String strUploadedFileName = UploadUtil.cleanFileName( uploadedFile.getName( ).trim( ) );
107                     // If we find a file with the same name and the same
108                     // length, we consider that the current file has
109                     // already been uploaded
110                     bNew = !( StringUtils.equals( strUploadedFileName, strFileName ) && ( uploadedFile.getSize( ) == fileItem.getSize( ) ) );
111                 }
112             }
113 
114             if ( bNew )
115             {
116                 uploadedFiles.add( fileItem );
117             }
118         }
119     }
120 
121     @Override
122     public String canUploadFiles( HttpServletRequest request, String strFieldName, List<FileItem> listFileItemsToUpload, Locale locale )
123     {
124         int nMaxSize = AppPropertiesService.getPropertyInt( PROPERTY_UPLOAD_PJS_MAX_SIZE, DEFAULT_MAX_FILE_SIZE );
125         String strMimeTypeAuthorized = AppPropertiesService
126                 .getProperty( PARAMETER_IMGS.equals( strFieldName ) ? PROPERTY_UPLOAD_IMAGE_MIME_TYPE_AUTORIZED : PROPERTY_UPLOAD_DOC_MIME_TYPE_AUTORIZED );
127         int nMaxPjs = AppPropertiesService.getPropertyInt( PROPERTY_UPLOAD_NB_PJS_MAX, DEFAULT_MAX_PJS );
128 
129         int nFileSize = 0;
130 
131         // Check if this file has not already been uploaded
132 
133         List<FileItem> uploadedFilesTmp = new ArrayList<>( );
134 
135         uploadedFilesTmp.addAll( getListUploadedFiles( PARAMETER_DOCS, request.getSession( ) ) );
136         uploadedFilesTmp.addAll( getListUploadedFiles( PARAMETER_IMGS, request.getSession( ) ) );
137         uploadedFilesTmp.addAll( listFileItemsToUpload );
138 
139         if ( uploadedFilesTmp.size( ) > nMaxPjs )
140         {
141             return I18nService.getLocalizedString( ERROR_MESSAGE_UPLOAD_NB_PJS_MAX, request.getLocale( ) );
142         }
143 
144         if ( uploadedFilesTmp != null )
145         {
146             for ( FileItem fileUpload : uploadedFilesTmp )
147             {
148                 nFileSize += fileUpload.getSize( );
149             }
150         }
151 
152         if ( nFileSize > nMaxSize )
153         {
154             return I18nService.getLocalizedString( ERROR_MESSAGE_UPLOAD_PJS_MAX_SIZE, request.getLocale( ) );
155         }
156 
157         for ( FileItem fileItem : listFileItemsToUpload )
158         {
159 
160             if ( !StringUtils.isEmpty( strMimeTypeAuthorized ) )
161             {
162                 boolean bMimeTypeNotAutorized = true;
163 
164                 for ( String strMimeType : strMimeTypeAuthorized.split( "," ) )
165                 {
166                     if ( fileItem.getContentType( ).equals( strMimeType ) )
167                     {
168 
169                         bMimeTypeNotAutorized = false;
170                     }
171 
172                 }
173 
174                 if ( bMimeTypeNotAutorized )
175                 {
176                     return I18nService.getLocalizedString(
177                             PARAMETER_IMGS.equals( strFieldName ) ? ERROR_MESSAGE_IMAGE_MIME_TYPE_AUTORIZED : ERROR_MESSAGE_DOC_MIME_TYPE_AUTORIZED,
178                             request.getLocale( ) );
179                 }
180             }
181 
182         }
183 
184         return null;
185 
186     }
187 
188     @Override
189     public String getHandlerName( )
190     {
191         return HANDLER_NAME;
192     }
193 
194     @Override
195     public List<FileItem> getListUploadedFiles( String strFieldName, HttpSession session )
196     {
197         if ( StringUtils.isBlank( strFieldName ) )
198         {
199             throw new AppException( "id field name is not provided for the current file upload" );
200         }
201 
202         initMap( session.getId( ), strFieldName );
203 
204         // find session-related files in the map
205         Map<String, List<FileItem>> mapFileItemsSession = _mapAsynchronousUpload.get( session.getId( ) );
206 
207         return mapFileItemsSession.get( strFieldName );
208     }
209 
210     @Override
211     public void removeFileItem( String strFieldName, HttpSession session, int nIndex )
212     {
213         // Remove the file (this will also delete the file physically)
214         List<FileItem> uploadedFiles = getListUploadedFiles( strFieldName, session );
215 
216         if ( ( uploadedFiles != null ) && !uploadedFiles.isEmpty( ) && ( uploadedFiles.size( ) > nIndex ) )
217         {
218             // Remove the object from the Hashmap
219             FileItem fileItem = uploadedFiles.remove( nIndex );
220             fileItem.delete( );
221         }
222     }
223 
224     /**
225      * Init the map Copy paste from genericAttribute AbstractGenAttUploadHandler from
226      * http://wiki.lutece.paris.fr/lutece/jsp/site/Portal.jsp?page=wiki&page_name=asynchronous_upload&view=page
227      * 
228      * @param strSessionId
229      *            the session id
230      * @param strFieldName
231      *            the field name
232      */
233     private void initMap( String strSessionId, String strFieldName )
234     {
235         // find session-related files in the map
236         Map<String, List<FileItem>> mapFileItemsSession = _mapAsynchronousUpload.get( strSessionId );
237 
238         // create map if not exists
239         if ( mapFileItemsSession == null )
240         {
241             synchronized( this )
242             {
243                 // Ignore double check locking error : assignation and instanciation of objects are separated.
244                 mapFileItemsSession = _mapAsynchronousUpload.get( strSessionId );
245 
246                 if ( mapFileItemsSession == null )
247                 {
248                     mapFileItemsSession = new ConcurrentHashMap<String, List<FileItem>>( );
249                     _mapAsynchronousUpload.put( strSessionId, mapFileItemsSession );
250                 }
251             }
252         }
253 
254         List<FileItem> listFileItems = mapFileItemsSession.get( strFieldName );
255 
256         if ( listFileItems == null )
257         {
258             listFileItems = new ArrayList<FileItem>( );
259             mapFileItemsSession.put( strFieldName, listFileItems );
260         }
261     }
262 
263     /**
264      * Removes all files associated to the session
265      * 
266      * @param strSessionId
267      *            the session id
268      */
269     public void removeSessionFiles( String strSessionId )
270     {
271         _mapAsynchronousUpload.remove( strSessionId );
272     }
273 }