View Javadoc
1   /*
2    * Copyright (c) 2002-2022, 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.genericattributes.service.upload;
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  import java.util.regex.Matcher;
43  import java.util.regex.Pattern;
44  
45  import javax.servlet.http.HttpServletRequest;
46  import javax.servlet.http.HttpSession;
47  
48  import org.apache.commons.fileupload.FileItem;
49  import org.apache.commons.lang3.StringUtils;
50  
51  import fr.paris.lutece.plugins.asynchronousupload.service.AbstractAsynchronousUploadHandler;
52  import fr.paris.lutece.plugins.genericattributes.business.Entry;
53  import fr.paris.lutece.plugins.genericattributes.business.EntryHome;
54  import fr.paris.lutece.plugins.genericattributes.business.GenericAttributeError;
55  import fr.paris.lutece.plugins.genericattributes.service.entrytype.EntryTypeServiceManager;
56  import fr.paris.lutece.plugins.genericattributes.service.entrytype.IEntryTypeService;
57  import fr.paris.lutece.portal.service.i18n.I18nService;
58  import fr.paris.lutece.portal.service.util.AppException;
59  import fr.paris.lutece.util.filesystem.UploadUtil;
60  
61  /**
62   * Abstract class to manage uploaded files for generic attributes entries of type files
63   */
64  public abstract class AbstractGenAttUploadHandler extends AbstractAsynchronousUploadHandler
65  {
66      private static final String PREFIX_ENTRY_ID = IEntryTypeService.PREFIX_ATTRIBUTE;
67      private static final Pattern PATTERN_PREFIX_ENTRY_ID = Pattern.compile( "[^0-9]+([0-9]+)$" );
68  
69      // Error messages
70      private static final String ERROR_MESSAGE_UNKNOWN_ERROR = "genericattributes.message.unknownError";
71  
72      /** <sessionId,<fieldName,fileItems>> */
73      /** contains uploaded file items */
74      private static Map<String, Map<String, List<FileItem>>> _mapAsynchronousUpload = new ConcurrentHashMap<>( );
75  
76      /**
77       * {@inheritDoc}
78       */
79      @Override
80      public String canUploadFiles( HttpServletRequest request, String strFieldName, List<FileItem> listFileItemsToUpload, Locale locale )
81      {
82          if ( StringUtils.isNotBlank( strFieldName ) && ( strFieldName.length( ) > PREFIX_ENTRY_ID.length( ) ) )
83          {
84              String sessionId = getCustomSessionId( request.getSession( ) );
85              initMap( sessionId, strFieldName );
86  
87              String strIdEntry = getEntryIdFromFieldName( strFieldName );
88  
89              if ( StringUtils.isEmpty( strIdEntry ) || !StringUtils.isNumeric( strIdEntry ) )
90              {
91                  return I18nService.getLocalizedString( ERROR_MESSAGE_UNKNOWN_ERROR, locale );
92              }
93  
94              int nIdEntry = Integer.parseInt( strIdEntry );
95              Entry entry = EntryHome.findByPrimaryKey( nIdEntry );
96  
97              List<FileItem> listUploadedFileItems = getListUploadedFiles( strFieldName, request.getSession( ) );
98  
99              if ( entry != null )
100             {
101                 GenericAttributeError error = EntryTypeServiceManager.getEntryTypeService( entry ).canUploadFiles( entry, listUploadedFileItems,
102                         listFileItemsToUpload, locale );
103 
104                 if ( error != null )
105                 {
106                     return error.getErrorMessage( );
107                 }
108 
109                 return null;
110             }
111         }
112 
113         return I18nService.getLocalizedString( ERROR_MESSAGE_UNKNOWN_ERROR, locale );
114     }
115 
116     /**
117      * {@inheritDoc}
118      */
119     @Override
120     public List<FileItem> getListUploadedFiles( String strFieldName, HttpSession session )
121     {
122         if ( StringUtils.isBlank( strFieldName ) )
123         {
124             throw new AppException( "id field name is not provided for the current file upload" );
125         }
126 
127         String sessionId = getCustomSessionId( session );
128 
129         initMap( sessionId, strFieldName );
130 
131         // find session-related files in the map
132         Map<String, List<FileItem>> mapFileItemsSession = _mapAsynchronousUpload.get( sessionId );
133 
134         return mapFileItemsSession.get( strFieldName );
135     }
136 
137     /**
138      * {@inheritDoc}
139      */
140     @Override
141     public void addFileItemToUploadedFilesList( FileItem fileItem, String strFieldName, HttpServletRequest request )
142     {
143         // This is the name that will be displayed in the form. We keep
144         // the original name, but clean it to make it cross-platform.
145         String strFileName = UploadUtil.cleanFileName( fileItem.getName( ).trim( ) );
146 
147         String sessionId = getCustomSessionId( request.getSession( ) );
148         initMap( sessionId, strFieldName );
149 
150         // Check if this file has not already been uploaded
151         List<FileItem> uploadedFiles = getListUploadedFiles( strFieldName, request.getSession( ) );
152 
153         if ( uploadedFiles != null )
154         {
155             boolean bNew = true;
156 
157             if ( !uploadedFiles.isEmpty( ) )
158             {
159                 Iterator<FileItem> iterUploadedFiles = uploadedFiles.iterator( );
160 
161                 while ( bNew && iterUploadedFiles.hasNext( ) )
162                 {
163                     FileItem uploadedFile = iterUploadedFiles.next( );
164                     String strUploadedFileName = UploadUtil.cleanFileName( uploadedFile.getName( ).trim( ) );
165                     // If we find a file with the same name and the same
166                     // length, we consider that the current file has
167                     // already been uploaded
168                     bNew = !( StringUtils.equals( strUploadedFileName, strFileName ) && ( uploadedFile.getSize( ) == fileItem.getSize( ) ) );
169                 }
170             }
171 
172             if ( bNew )
173             {
174                 uploadedFiles.add( fileItem );
175             }
176         }
177     }
178 
179     @Override
180     public void removeSessionFiles( HttpSession session )
181     {
182         String sessionId = (String) session.getAttribute( PARAM_CUSTOM_SESSION_ID );
183         if ( sessionId != null )
184         {
185             _mapAsynchronousUpload.remove( sessionId );
186         }
187 
188     }
189 
190     /**
191      * Build the field name from a given id entry i.e. : form_1
192      * 
193      * @param strIdEntry
194      *            the id entry
195      * @return the field name
196      */
197     protected String buildFieldName( String strIdEntry )
198     {
199         return PREFIX_ENTRY_ID + strIdEntry;
200     }
201 
202     /**
203      * Get the id of the entry associated with a given field name
204      * 
205      * @param strFieldName
206      *            The name of the field
207      * @return The id of the entry
208      */
209     protected String getEntryIdFromFieldName( String strFieldName )
210     {
211         if ( StringUtils.isEmpty( strFieldName ) || ( strFieldName.length( ) < PREFIX_ENTRY_ID.length( ) ) )
212         {
213             return null;
214         }
215 
216         Matcher matcher = PATTERN_PREFIX_ENTRY_ID.matcher( strFieldName );
217         if ( matcher.find( ) )
218         {
219             return matcher.group( 1 );
220         }
221 
222         return null;
223     }
224 
225     /**
226      * Init the map
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.computeIfAbsent( strSessionId, s -> new ConcurrentHashMap<>( ) );
245             }
246         }
247 
248         mapFileItemsSession.computeIfAbsent( strFieldName, s -> new ArrayList<>( ) );
249     }
250 }