View Javadoc
1   /*
2    * Copyright (c) 2002-2023, 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.document.web;
35  
36  import fr.paris.lutece.plugins.document.business.Document;
37  import fr.paris.lutece.plugins.document.business.DocumentHome;
38  import fr.paris.lutece.plugins.document.business.DocumentResource;
39  import fr.paris.lutece.plugins.document.service.DocumentEvent;
40  import fr.paris.lutece.plugins.document.service.DocumentEventListener;
41  import fr.paris.lutece.plugins.document.service.DocumentException;
42  import fr.paris.lutece.plugins.document.utils.IntegerUtils;
43  import fr.paris.lutece.portal.business.resourceenhancer.ResourceEnhancer;
44  import fr.paris.lutece.portal.service.resource.ExtendableResourceActionHit;
45  import fr.paris.lutece.portal.service.util.AppPropertiesService;
46  
47  import java.io.IOException;
48  import java.io.OutputStream;
49  
50  import javax.servlet.ServletException;
51  import javax.servlet.http.HttpServlet;
52  import javax.servlet.http.HttpServletRequest;
53  import javax.servlet.http.HttpServletResponse;
54  
55  
56  /**
57   * Servlet serving document file resources
58   */
59  public class DocumentResourceServlet extends HttpServlet implements DocumentEventListener
60  {
61      private static final long serialVersionUID = -7512201287826936428L;
62      private static final String PARAMETER_DOCUMENT_ID = "id";
63      private static final String PARAMETER_ATTRIBUTE_ID = "id_attribute";
64      private static final String PARAMETER_WORKING_CONTENT = "working_content";
65      private static final String PARAMETER_NOCACHE = "nocache";
66      private static final String DEFAULT_EXPIRES_DELAY = "180000";
67      private static final String PROPERTY_RESOURCE_TYPE = "document";
68      private static final String PROPERTY_EXPIRES_DELAY = "document.resourceServlet.cacheControl.expires";
69      private static final String KEY_DOC_BEGIN = "[doc:";
70      private static final String KEY_ATTR_BEGIN = "[attr:";
71      private static final String KEY_ITEM_CLOSE = "]";
72      private static final String STRING_DELAY_IN_SECOND = AppPropertiesService.getProperty( PROPERTY_EXPIRES_DELAY,
73              DEFAULT_EXPIRES_DELAY );
74      private static final Long LONG_DELAY_IN_MILLISECOND = Long.parseLong( STRING_DELAY_IN_SECOND ) * 1000;
75      private static final ResourceServletCache/ResourceServletCache.html#ResourceServletCache">ResourceServletCache _cache = new ResourceServletCache(  );
76  
77      /**
78       * Processes request HTTP <code>GET if-modified-since</code> methods
79       * @param request The HTTP request
80       * @return document last modified date
81       */
82      @Override
83      public long getLastModified( HttpServletRequest request )
84      {
85          long lLastModified = -1;
86          String strDocumentId = request.getParameter( PARAMETER_DOCUMENT_ID );
87          String strAttributeId = request.getParameter( PARAMETER_ATTRIBUTE_ID );
88  
89          if ( ( strDocumentId != null ) && ( strAttributeId != null ) )
90          {
91              int nDocumentId = IntegerUtils.convert( strDocumentId );
92              int nAttributeId = IntegerUtils.convert( strAttributeId );
93              String strKey = getCacheKey( nDocumentId, nAttributeId );
94  
95              ResourceValueObject resource = _cache.get( strKey );
96  
97              if ( _cache.isCacheEnable(  ) && ( _cache.get( strKey ) != null ) )
98              {
99                  return resource.getLastModified(  );
100             }
101 
102             Document document = DocumentHome.loadLastModifiedAttributes( nDocumentId );
103 
104             // Because Internet Explorer 6 has bogus behavior with PDF and proxy or HTTPS
105             if ( ( document != null ) &&
106                     !Document.CODE_DOCUMENT_TYPE_DOWNLOAD.equals( document.getCodeDocumentType(  ) ) &&
107                     ( document.getDateModification(  ) != null ) )
108             {
109                 lLastModified = document.getDateModification(  ).getTime(  );
110             }
111         }
112 
113         if ( lLastModified == -1 )
114         {
115             lLastModified = super.getLastModified( request );
116         }
117 
118         return lLastModified;
119     }
120 
121     /**
122      * Put the file in cache
123      * @param nDocumentId The document id
124      * @param nAttributeId The attribut id
125      */
126     public static void putInCache( int nDocumentId, int nAttributeId )
127     {
128         if ( !_cache.isCacheEnable(  ) )
129         {
130             return;
131         }
132 
133         DocumentResource resource = DocumentHome.getValidatedResource( nDocumentId, nAttributeId );
134         ResourceValueObject/web/ResourceValueObject.html#ResourceValueObject">ResourceValueObject res = new ResourceValueObject( resource );
135         Document document = DocumentHome.loadLastModifiedAttributes( nDocumentId );
136         long lLastModified = document.getDateModification(  ).getTime(  );
137         res.setLastModified( lLastModified );
138         _cache.put( getCacheKey( nDocumentId, nAttributeId ), res );
139     }
140 
141     /**
142      * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
143      * @param request servlet request
144      * @param response servlet response
145      * @throws ServletException the servlet Exception
146      * @throws IOException the io exception
147      */
148     protected void processRequest( HttpServletRequest request, HttpServletResponse response )
149         throws ServletException, IOException
150     {
151         String strDocumentId = request.getParameter( PARAMETER_DOCUMENT_ID );
152         int nDocumentId = IntegerUtils.convert( strDocumentId );
153         String strAttributeId = request.getParameter( PARAMETER_ATTRIBUTE_ID );
154         String strNoCache = request.getParameter( PARAMETER_NOCACHE );
155 
156         int nAttributeId = IntegerUtils.convert( strAttributeId );
157         Boolean bWorkingContent = ( request.getParameter( PARAMETER_WORKING_CONTENT ) != null );
158 
159         String strCacheKey = getCacheKey( nDocumentId, nAttributeId );
160         ResourceValueObject res;
161 
162         if ( !bWorkingContent && _cache.isCacheEnable(  ) && ( _cache.get( strCacheKey ) != null ) )
163         {
164             res = _cache.get( strCacheKey );
165         }
166         else
167         {
168             DocumentResource resource = getResource( nDocumentId, nAttributeId, bWorkingContent );
169 
170             if ( resource == null )
171             {
172                 return;
173             }
174 
175             res = new ResourceValueObject( resource );
176 
177             if ( _cache.isCacheEnable(  ) && !bWorkingContent )
178             {
179                 Document document = DocumentHome.loadLastModifiedAttributes( nDocumentId );
180                 long lLastModified = document.getDateModification(  ).getTime(  );
181                 res.setLastModified( lLastModified );
182                 _cache.put( strCacheKey, res );
183             }
184         }
185 
186         ExtendableResourceActionHit.getInstance(  )
187                                    .notifyActionOnResource( strDocumentId, Document.PROPERTY_RESOURCE_TYPE,
188             ExtendableResourceActionHit.ACTION_DOWNLOAD );
189 
190         ResourceEnhancer.doDownloadResourceAddOn( request, PROPERTY_RESOURCE_TYPE, nDocumentId );
191 
192         // Sets content type and filename of the resource into the response
193         response.setContentType( res.getContentType(  ) );
194 
195         if ( !isGraphicalContent( res.getContentType(  ) ) )
196         {
197             // Add the filename only if the resource isn't a flash document or an image
198             response.setHeader( "Content-Disposition", "attachment;filename=\"" + res.getFilename(  ) + "\"" );
199         }
200 
201         // Add Cache Control HTTP header
202         if ( strNoCache != null )
203         {
204             response.setHeader( "Cache-Control", "no-cache" ); // HTTP 1.1
205             response.setDateHeader( "Expires", 0 ); // HTTP 1.0
206         }
207         else
208         {
209             response.setHeader( "Cache-Control", "max-age=" + STRING_DELAY_IN_SECOND ); // HTTP 1.1
210             response.setDateHeader( "Expires", System.currentTimeMillis(  ) + LONG_DELAY_IN_MILLISECOND ); // HTTP 1.0           
211         }
212 
213         response.setContentLength( res.getContent(  ).length ); // Keep Alive connexion
214 
215         // Write the resource content
216         OutputStream out = response.getOutputStream(  );
217         out.write( res.getContent(  ) );
218 
219         //out.flush( );        
220         //out.close(); Disabled : allow Keep Alive connexion
221     }
222 
223     /** Handles the HTTP <code>GET</code> method.
224      * @param request servlet request
225      * @param response servlet response
226      * @throws ServletException the servlet Exception
227      * @throws IOException the io exception
228      */
229     @Override
230     protected void doGet( HttpServletRequest request, HttpServletResponse response )
231         throws ServletException, IOException
232     {
233         processRequest( request, response );
234     }
235 
236     /** Handles the HTTP <code>POST</code> method.
237      * @param request servlet request
238      * @param response servlet response
239      * @throws ServletException the servlet Exception
240      * @throws IOException the io exception
241      */
242     @Override
243     protected void doPost( HttpServletRequest request, HttpServletResponse response )
244         throws ServletException, IOException
245     {
246         processRequest( request, response );
247     }
248 
249     /** Returns a short description of the servlet.
250      * @return message
251      */
252     @Override
253     public String getServletInfo(  )
254     {
255         return "Servlet serving file resources of documents";
256     }
257 
258     /**
259      * Caclculate the cache key
260      * @param nDocumentId The document id
261      * @param nAttributeId The attribute id
262      * @return The key
263      */
264     private static String getCacheKey( int nDocumentId, int nAttributeId )
265     {
266         StringBuilder sbKey = new StringBuilder(  );
267         sbKey.append( KEY_DOC_BEGIN ).append( nDocumentId ).append( KEY_ITEM_CLOSE ).append( KEY_ATTR_BEGIN )
268              .append( nAttributeId ).append( KEY_ITEM_CLOSE );
269 
270         return sbKey.toString(  );
271     }
272 
273     /**
274      * Get the document resource
275      * @param nDocumentId The document id
276      * @param nAttributeId The attribute id
277      * @param bWorkingContent is a working content
278      * @return The document resource
279      */
280     private DocumentResource getResource( int nDocumentId, int nAttributeId, boolean bWorkingContent )
281     {
282         DocumentResource resource;
283 
284         if ( nAttributeId != -1 )
285         {
286             if ( bWorkingContent )
287             {
288                 resource = DocumentHome.getWorkingResource( nDocumentId, nAttributeId );
289 
290                 if ( resource == null )
291                 {
292                     resource = DocumentHome.getValidatedResource( nDocumentId, nAttributeId );
293                 }
294             }
295             else
296             {
297                 resource = DocumentHome.getValidatedResource( nDocumentId, nAttributeId );
298             }
299         }
300         else
301         {
302             resource = DocumentHome.getResource( nDocumentId );
303         }
304 
305         return resource;
306     }
307 
308     /**
309      * Is the document an image or a flash object according the content type
310      * @param strContentType The content type
311      * @return True for an image or a flash object, otherwise false
312      */
313     private boolean isGraphicalContent( String strContentType )
314     {
315         return ( strContentType.equals( "image/jpeg" ) || strContentType.equals( "image/gif" ) ||
316         strContentType.equals( "image/png" ) || strContentType.equals( "application/x-shockwave-flash" ) );
317     }
318 
319     /**
320      * {@inheritDoc }
321      */
322     @Override
323     public void processDocumentEvent( DocumentEvent event )
324         throws DocumentException
325     {
326         String strKeyPattern = KEY_DOC_BEGIN + event.getDocument(  ).getId(  ) + KEY_ITEM_CLOSE;
327         _cache.removeFromKeyPattern( strKeyPattern );
328     }
329 }