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.service;
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.DocumentType;
39  import fr.paris.lutece.plugins.document.business.DocumentTypeHome;
40  import fr.paris.lutece.plugins.document.business.portlet.DocumentListPortletHome;
41  import fr.paris.lutece.plugins.document.business.publication.DocumentPublication;
42  import fr.paris.lutece.plugins.document.service.publishing.PublishingService;
43  import fr.paris.lutece.plugins.document.utils.IntegerUtils;
44  import fr.paris.lutece.portal.business.page.Page;
45  import fr.paris.lutece.portal.business.page.PageHome;
46  import fr.paris.lutece.portal.business.portlet.AliasPortlet;
47  import fr.paris.lutece.portal.business.portlet.AliasPortletHome;
48  import fr.paris.lutece.portal.business.portlet.Portlet;
49  import fr.paris.lutece.portal.business.portlet.PortletHome;
50  import fr.paris.lutece.portal.business.resourceenhancer.ResourceEnhancer;
51  import fr.paris.lutece.portal.business.style.ModeHome;
52  import fr.paris.lutece.portal.service.cache.ICacheKeyService;
53  import fr.paris.lutece.portal.service.content.ContentService;
54  import fr.paris.lutece.portal.service.content.PageData;
55  import fr.paris.lutece.portal.service.html.XmlTransformerService;
56  import fr.paris.lutece.portal.service.message.SiteMessageException;
57  import fr.paris.lutece.portal.service.page.PortletCacheService;
58  import fr.paris.lutece.portal.service.portal.PortalService;
59  import fr.paris.lutece.portal.service.security.LuteceUser;
60  import fr.paris.lutece.portal.service.security.SecurityService;
61  import fr.paris.lutece.portal.service.security.UserNotSignedException;
62  import fr.paris.lutece.portal.service.spring.SpringContextService;
63  import fr.paris.lutece.portal.service.template.AppTemplateService;
64  import fr.paris.lutece.portal.service.util.AppLogService;
65  import fr.paris.lutece.portal.service.util.AppPathService;
66  import fr.paris.lutece.portal.service.util.AppPropertiesService;
67  import fr.paris.lutece.portal.web.PortalJspBean;
68  import fr.paris.lutece.portal.web.constants.Parameters;
69  import fr.paris.lutece.util.ReferenceList;
70  import fr.paris.lutece.util.date.DateUtil;
71  import fr.paris.lutece.util.html.HtmlTemplate;
72  import fr.paris.lutece.util.xml.XmlUtil;
73  
74  import net.sf.ehcache.CacheException;
75  import net.sf.ehcache.Ehcache;
76  import net.sf.ehcache.Element;
77  import net.sf.ehcache.event.CacheEventListener;
78  
79  import org.apache.commons.lang3.StringUtils;
80  
81  import java.io.FileInputStream;
82  
83  import java.util.ArrayList;
84  import java.util.Enumeration;
85  import java.util.HashMap;
86  import java.util.Hashtable;
87  import java.util.List;
88  import java.util.Map;
89  import java.util.Properties;
90  import java.util.concurrent.ConcurrentHashMap;
91  import java.util.concurrent.ConcurrentMap;
92  
93  import javax.servlet.http.HttpServletRequest;
94  
95  import javax.xml.transform.Source;
96  import javax.xml.transform.stream.StreamSource;
97  
98  
99  /**
100  *
101  */
102 public final class DocumentContentService extends ContentService implements CacheEventListener
103 {
104     ///////////////////////////////////////////////////////////////////////////////////////////////////
105     // Constants
106     private static final String CONTENT_SERVICE_NAME = "Document Content Service";
107     private static final String SLASH = "/";
108     private static final int MODE_ADMIN = 1;
109     private static final String CONSTANT_DEFAULT_PORTLET_DOCUMENT_LIST_XSL = "WEB-INF/xsl/normal/portlet_document_list.xsl";
110     private static final String DOCUMENT_STYLE_PREFIX_ID = "document-";
111     private static final String LOCALE_EN = "en";
112     private static final String LOCALE_FR = "fr";
113 
114     // XML tags
115     private static final String XML_TAG_CONTENT = "content";
116     private static final String XML_TAG_SITE_LOCALE = "site_locale";
117 
118     // Parameters
119     private static final String PARAMETER_DOCUMENT_ID = "document_id";
120     private static final String PARAMETER_SITE_PATH = "site-path";
121     private static final String PARAMETER_PUBLICATION_DATE = "publication-date";
122     private static final String PARAMETER_SITE_LOCALE = "site_locale";
123 
124     // Markers
125     private static final String MARK_PUBLICATION = "publication";
126     private static final String MARK_DOCUMENT = "document";
127     private static final String MARK_PORTLET = "portlet";
128     private static final String MARK_CATEGORY = "categories";
129     private static final String MARK_DOCUMENT_ID = "document_id";
130     private static final String MARK_PORTLET_ID = "portlet_id";
131     private static final String MARK_PORTLET_ID_LIST = "portlet_id_list";
132     private static final String MARK_DOCUMENT_CATEGORIES_LIST = "document_categories_list";
133     private static final String MARK_URL_LOGIN = "url_login";
134     private static final String MARKER_TARGET = "target";
135     private static final String MARK_IS_EXTEND_INSTALLED = "isExtendInstalled";
136 
137     // Templates
138     private static final String TEMPLATE_DOCUMENT_PAGE_DEFAULT = "/skin/plugins/document/document_content_service.html";
139     private static final String TEMPLATE_DOCUMENT_CATEGORIES = "/skin/plugins/document/document_categories.html";
140 
141     //Properties
142     private static final String PROPERTY_DEFAULT_PORTLET_DOCUMENT_LIST_XSL = "document.contentService.defaultPortletDocumentListXSL";
143     private static final String PROPERTY_CACHE_ENABLED = "document.cache.enabled";
144     private static final String TARGET_TOP = "target=_top";
145     private static final String PROPERTY_RESOURCE_TYPE = "document";
146 
147     // Performance patch
148     private static ConcurrentMap<String, String> _keyMemory = new ConcurrentHashMap<String, String>(  );
149 
150     //Portlet cache
151     //Should be equal to PortletCacheService.CACHE_PORTLET_PREFIX without the final semicolon
152     private static final String PARAMETER_PORTLET = "portlet";
153     private static final String PORTLET_CACHE_KEY_SUFFIX = "[documentContentService]";
154     private boolean _bInit;
155     private PortletCacheService _cachePortlets;
156     private ICacheKeyService _cksPortlet;
157 
158     /**
159      * Returns the document page for a given document and a given portlet. The
160      * page is built from XML data or retrieved
161      * from the cache if it's enable and the document in it.
162      *
163      * @param request The HTTP request.
164      * @param nMode The current mode.
165      * @return The HTML code of the page as a String.
166      * @throws UserNotSignedException If the user is not signed
167      * @throws SiteMessageException occurs when a site message need to be
168      *             displayed
169      */
170     public String getPage( HttpServletRequest request, int nMode )
171         throws UserNotSignedException, SiteMessageException
172     {
173         if ( !_bInit )
174         {
175             init(  );
176         }
177 
178         String strDocumentId = request.getParameter( PARAMETER_DOCUMENT_ID );
179         String strPortletId = request.getParameter( Parameters.PORTLET_ID );
180         String strSiteLocale = request.getParameter( PARAMETER_SITE_LOCALE );
181 
182         if ( ( strSiteLocale == null ) || !strSiteLocale.equalsIgnoreCase( LOCALE_EN ) )
183         {
184             strSiteLocale = LOCALE_FR;
185         }
186 
187         String strKey = getKey( strDocumentId, strPortletId, strSiteLocale, nMode );
188         String strPage = (String) getFromCache( strKey );
189 
190         if ( strPage == null )
191         {
192             // only one thread can evaluate the page
193             synchronized ( strKey )
194             {
195                 // can be useful if an other thread had evaluate the page
196                 strPage = (String) getFromCache( strKey );
197 
198                 // ignore CheckStyle, this double verification is useful when page cache has been created when thread is
199                 // blocked on synchronized
200                 if ( strPage == null )
201                 {
202                     AppLogService.debug( " -- Page generation " + strKey + " : doc=" + strDocumentId + " portletid=" +
203                         strPortletId + "site_locale=" + strSiteLocale + "nMode=" + nMode );
204                     strPage = buildPage( request, strDocumentId, strPortletId, strSiteLocale, nMode );
205 
206                     if ( IntegerUtils.isNumeric( strDocumentId ) )
207                     {
208                         int nDocumentId = IntegerUtils.convert( strDocumentId );
209                         Document document = DocumentHome.findByPrimaryKeyWithoutBinaries( nDocumentId );
210 
211                         if ( ( document != null ) )
212                         {
213                             putInCache( strKey, strPage );
214                         }
215                     }
216                 }
217                 else
218                 {
219                     AppLogService.debug( "Page read from cache after synchronisation " + strKey );
220                 }
221             }
222         }
223         else
224         {
225             AppLogService.debug( "Page read from cache " + strKey );
226         }
227 
228         return strPage;
229     }
230 
231     /**
232      * Initializes the service
233      */
234     private void init(  )
235     {
236         // Initialize the cache according property value. 
237         // If the property isn't found the default is true
238         String strCache = AppPropertiesService.getProperty( PROPERTY_CACHE_ENABLED, "true" );
239 
240         if ( strCache.equalsIgnoreCase( "true" ) )
241         {
242             initCache( getName(  ) );
243         }
244 
245         //initCache(  ) by the core in PageService
246         _cachePortlets = SpringContextService.getBean( "portletCacheService" );
247 
248         _cksPortlet = SpringContextService.getBean( "portletCacheKeyService" );
249 
250         _bInit = true;
251     }
252 
253     /**
254      * Build the document page
255      * @param request The HTTP Request
256      * @param strDocumentId The document ID
257      * @param strPortletId The portlet ID
258      * @param strSiteLocale the site locale code
259      * @param nMode The current mode
260      * @return
261      * @throws fr.paris.lutece.portal.service.security.UserNotSignedException
262      * @throws fr.paris.lutece.portal.service.message.SiteMessageException
263      */
264     private String buildPage( HttpServletRequest request, String strDocumentId, String strPortletId,
265         String strSiteLocale, int nMode ) throws UserNotSignedException, SiteMessageException
266     {
267         int nPortletId;
268         int nDocumentId;
269         boolean bPortletExist = false;
270         Map<String, String> mapXslParams = new HashMap<String, String>(  );
271 
272         try
273         {
274             nPortletId = Integer.parseInt( strPortletId );
275             nDocumentId = Integer.parseInt( strDocumentId );
276         }
277         catch ( NumberFormatException nfe )
278         {
279             return PortalService.getDefaultPage( request, nMode );
280         }
281 
282         Document document = DocumentHome.findByPrimaryKeyWithoutBinaries( nDocumentId );
283 
284         if ( ( document == null ) || ( !document.isValid(  ) ) )
285         {
286             return PortalService.getDefaultPage( request, nMode );
287         }
288 
289         DocumentType type = DocumentTypeHome.findByPrimaryKey( document.getCodeDocumentType(  ) );
290         DocumentPublication documentPublication = PublishingService.getInstance(  )
291                                                                    .getDocumentPublication( nPortletId, nDocumentId );
292 
293         Map<String, Object> model = new HashMap<String, Object>(  );
294 
295         if ( documentPublication != null )
296         {
297             // Check if portlet is an alias portlet
298             boolean bIsAlias = DocumentListPortletHome.checkIsAliasPortlet( documentPublication.getPortletId(  ) );
299 
300             if ( bIsAlias && ( documentPublication.getPortletId(  ) != nPortletId ) )
301             {
302                 AliasPortlet alias = (AliasPortlet) AliasPortletHome.findByPrimaryKey( nPortletId );
303                 nPortletId = alias.getAliasId(  );
304                 strPortletId = Integer.toString( nPortletId );
305             }
306 
307             if ( ( documentPublication.getPortletId(  ) == nPortletId ) &&
308                     ( documentPublication.getStatus(  ) == DocumentPublication.STATUS_PUBLISHED ) )
309             {
310                 bPortletExist = true;
311             }
312 
313             // The publication informations are available in Xsl (only publication date) and in template (full DocumentPublication object)
314             mapXslParams.put( PARAMETER_PUBLICATION_DATE,
315                 DateUtil.getDateString( documentPublication.getDatePublishing(  ), request.getLocale(  ) ) );
316             model.put( MARK_PUBLICATION, documentPublication );
317         }
318 
319         if ( bPortletExist )
320         {
321             // Fill a PageData structure for those elements
322             PageData data = new PageData(  );
323             data.setName( document.getTitle(  ) );
324             data.setPagePath( PortalService.getXPagePathContent( document.getTitle(  ), 0, request ) );
325 
326             Portlet portlet = PortletHome.findByPrimaryKey( nPortletId );
327             Page page = PageHome.getPage( portlet.getPageId(  ) );
328             String strRole = page.getRole(  );
329 
330             if ( !strRole.equals( Page.ROLE_NONE ) && SecurityService.isAuthenticationEnable(  ) )
331             {
332                 LuteceUser user = SecurityService.getInstance(  ).getRegisteredUser( request );
333 
334                 if ( ( user == null ) && ( !SecurityService.getInstance(  ).isExternalAuthentication(  ) ) )
335                 {
336                     // The user is not registered and identify itself with the Portal authentication
337                     String strAccessControledTemplate = SecurityService.getInstance(  ).getAccessControledTemplate(  );
338                     HashMap<String, Object> modelAccessControledTemplate = new HashMap<String, Object>(  );
339                     String strLoginUrl = SecurityService.getInstance(  ).getLoginPageUrl(  );
340                     modelAccessControledTemplate.put( MARK_URL_LOGIN, strLoginUrl );
341 
342                     HtmlTemplate tAccessControled = AppTemplateService.getTemplate( strAccessControledTemplate,
343                             request.getLocale(  ), modelAccessControledTemplate );
344                     data.setContent( tAccessControled.getHtml(  ) );
345 
346                     return PortalService.buildPageContent( data, nMode, request );
347                 }
348 
349                 if ( !SecurityService.getInstance(  ).isUserInRole( request, strRole ) )
350                 {
351                     // The user doesn't have the correct role
352                     String strAccessDeniedTemplate = SecurityService.getInstance(  ).getAccessDeniedTemplate(  );
353                     HtmlTemplate tAccessDenied = AppTemplateService.getTemplate( strAccessDeniedTemplate,
354                             request.getLocale(  ) );
355                     data.setContent( tAccessDenied.getHtml(  ) );
356 
357                     return PortalService.buildPageContent( data, nMode, request );
358                 }
359             }
360 
361             // Get request paramaters and store them in a hashtable
362             Enumeration<?> enumParam = request.getParameterNames(  );
363             Hashtable<String, String> htParamRequest = new Hashtable<String, String>(  );
364             String paramName = "";
365 
366             while ( enumParam.hasMoreElements(  ) )
367             {
368                 paramName = (String) enumParam.nextElement(  );
369                 htParamRequest.put( paramName, request.getParameter( paramName ) );
370             }
371 
372             XmlTransformerService xmlTransformerService = new XmlTransformerService(  );
373             StringBuffer strXml = new StringBuffer(  );
374             XmlUtil.beginElement( strXml, XML_TAG_CONTENT );
375             XmlUtil.addElement( strXml, XML_TAG_SITE_LOCALE, strSiteLocale );
376             strXml.append( document.getXmlValidatedContent(  ) );
377             XmlUtil.endElement( strXml, XML_TAG_CONTENT );
378 
379             String strDocument = xmlTransformerService.transformBySourceWithXslCache( strXml.toString(  ),
380                     type.getContentServiceXslSource(  ), DOCUMENT_STYLE_PREFIX_ID + type.getStyleSheetId( nMode ),
381                     htParamRequest, null );
382 
383             model.put( MARK_DOCUMENT, strDocument );
384             
385             if ( !document.isSkipPortlet() )
386             {
387             	model.put( MARK_PORTLET, getPortlet( request, strPortletId, nMode ) );
388             }
389             
390             if ( !document.isSkipCategories() )
391             {
392             	model.put( MARK_CATEGORY, getRelatedDocumentsPortlet( request, document, nPortletId, nMode ) );
393             }
394             
395             model.put( MARK_DOCUMENT_ID, strDocumentId );
396             model.put( MARK_PORTLET_ID, strPortletId );
397             model.put( MARK_IS_EXTEND_INSTALLED, PortalService.isExtendActivated(  ) );
398 
399             // Additional page info
400             ResourceEnhancer.buildPageAddOn( model, PROPERTY_RESOURCE_TYPE, nDocumentId, strPortletId, request );
401 
402             HtmlTemplate template = AppTemplateService.getTemplate( getTemplatePage( document ), request.getLocale(  ),
403                     model );
404 
405             data.setContent( template.getHtml(  ) );
406 
407             return PortalService.buildPageContent( data, nMode, request );
408         }
409 
410         //portlet does not exists
411         return PortalService.getDefaultPage( request, nMode );
412     }
413 
414     /**
415      * Analyzes request parameters to see if the request should be handled by
416      * the current Content Service
417      *
418      * @param request The HTTP request
419      * @return true if this ContentService should handle this request
420      */
421     public boolean isInvoked( HttpServletRequest request )
422     {
423         String strDocumentId = request.getParameter( PARAMETER_DOCUMENT_ID );
424         String strIdPortlet = request.getParameter( Parameters.PORTLET_ID );
425 
426         if ( ( strDocumentId != null ) && ( strDocumentId.length(  ) > 0 ) && ( strIdPortlet != null ) &&
427                 ( strIdPortlet.length(  ) > 0 ) )
428         {
429             return true;
430         }
431 
432         return false;
433     }
434 
435     /**
436      * Returns the Content Service name
437      *
438      * @return The name as a String
439      */
440     public String getName(  )
441     {
442         return CONTENT_SERVICE_NAME;
443     }
444 
445     /**
446      * Return the template page
447      * @param document
448      * @return the template
449      */
450     private String getTemplatePage( Document document )
451     {
452         if ( document.getPageTemplateDocumentId(  ) != 0 )
453         {
454             String strPageTemplateDocument = DocumentHome.getPageTemplateDocumentPath( document.getPageTemplateDocumentId(  ) );
455 
456             return strPageTemplateDocument;
457         }
458         else
459         {
460             return TEMPLATE_DOCUMENT_PAGE_DEFAULT;
461         }
462     }
463 
464     ///////////////////////////////////////////////////////////////////////////////////////////////////////////
465     // Comments implementation
466     /**
467      * Gets the documents list portlet containing the document
468      *
469      * @param strPortletId The ID of the documents list portlet where the
470      *            document has been published.
471      * @param nMode The current mode.
472      * @param request The Http request
473      * @return The HTML code of the documents list portlet as a String
474      * @throws SiteMessageException IF a message need to be displayed
475      */
476     private String getPortlet( HttpServletRequest request, String strPortletId, int nMode )
477         throws SiteMessageException
478     {
479         try
480         {
481             int nPortletId = Integer.parseInt( strPortletId );
482 
483             Portlet portlet = PortletHome.findByPrimaryKey( nPortletId );
484 
485             // Selection of the XSL stylesheet
486             // byte[] baXslSource = portlet.getXslSource( nMode );
487 
488             //FIXME Temporary solution (see LUTECE-824)
489             String strFilePath = AppPropertiesService.getProperty( PROPERTY_DEFAULT_PORTLET_DOCUMENT_LIST_XSL,
490                     CONSTANT_DEFAULT_PORTLET_DOCUMENT_LIST_XSL );
491 
492             if ( strFilePath == null )
493             {
494                 return StringUtils.EMPTY;
495             }
496 
497             if ( !strFilePath.startsWith( SLASH ) )
498             {
499                 strFilePath = SLASH + strFilePath;
500             }
501 
502             String strFileName = strFilePath.substring( strFilePath.lastIndexOf( SLASH ) + 1 );
503             strFilePath = strFilePath.substring( 0, strFilePath.lastIndexOf( SLASH ) + 1 );
504 
505             FileInputStream fis = AppPathService.getResourceAsStream( strFilePath, strFileName );
506             Source xslSource = new StreamSource( fis );
507 
508             // Get request paramaters and store them in a hashtable
509             Enumeration<?> enumParam = request.getParameterNames(  );
510             Hashtable<String, String> htParamRequest = new Hashtable<String, String>(  );
511             String paramName = "";
512 
513             while ( enumParam.hasMoreElements(  ) )
514             {
515                 paramName = (String) enumParam.nextElement(  );
516                 htParamRequest.put( paramName, request.getParameter( paramName ) );
517             }
518 
519             Properties outputProperties = ModeHome.getOuputXslProperties( nMode );
520 
521             // Add a path param for choose url to use in admin or normal mode
522             if ( nMode != MODE_ADMIN )
523             {
524                 htParamRequest.put( PARAMETER_SITE_PATH, AppPathService.getPortalUrl(  ) );
525             }
526             else
527             {
528                 htParamRequest.put( PARAMETER_SITE_PATH, AppPathService.getAdminPortalUrl(  ) );
529                 htParamRequest.put( MARKER_TARGET, TARGET_TOP );
530             }
531 
532             if ( _cachePortlets.isCacheEnable(  ) )
533             {
534                 LuteceUser user = null;
535 
536                 if ( SecurityService.isAuthenticationEnable(  ) )
537                 {
538                     user = SecurityService.getInstance(  ).getRegisteredUser( request );
539                 }
540 
541                 boolean bCanBeCached = ( user != null ) ? ( portlet.canBeCachedForConnectedUsers(  ) )
542                                                         : ( portlet.canBeCachedForAnonymousUsers(  ) );
543 
544                 if ( bCanBeCached )
545                 {
546                     //To delete keys when portlets are modified through _cachePortlets implementing PortletEventListener
547                     htParamRequest.put( PARAMETER_PORTLET, String.valueOf( portlet.getId(  ) ) );
548 
549                     //Add [documentContentService] to not clash with PageService keys because we don't synchronize
550                     String strKey = getKey( _cksPortlet.getKey( htParamRequest, nMode, user ) +
551                             PORTLET_CACHE_KEY_SUFFIX );
552 
553                     // get portlet from cache
554                     String strPortlet = (String) _cachePortlets.getFromCache( strKey );
555 
556                     if ( strPortlet == null )
557                     {
558                         // only one thread can evaluate the page
559                         synchronized ( strKey )
560                         {
561                             // can be useful if an other thread had evaluate the
562                             // porlet
563                             strPortlet = (String) _cachePortlets.getFromCache( strKey );
564 
565                             // ignore checkstyle, this double verification is useful
566                             // when page cache has been created when thread is
567                             // blocked on synchronized
568                             if ( strPortlet == null )
569                             {
570                                 String strXml = portlet.getXmlDocument( request );
571 
572                                 XmlTransformerService xmlTransformerService = new XmlTransformerService(  );
573                                 String strXslUniquePrefix = DOCUMENT_STYLE_PREFIX_ID + strFilePath + strFileName;
574 
575                                 strPortlet = xmlTransformerService.transformBySourceWithXslCache( strXml, xslSource,
576                                         strXslUniquePrefix, htParamRequest, outputProperties );
577 
578                                 _cachePortlets.putInCache( strKey, strPortlet );
579                             }
580                         }
581                     }
582 
583                     return strPortlet;
584                 }
585             }
586 
587             String strXml = portlet.getXmlDocument( request );
588 
589             XmlTransformerService xmlTransformerService = new XmlTransformerService(  );
590             String strXslUniquePrefix = DOCUMENT_STYLE_PREFIX_ID + strFilePath + strFileName;
591 
592             return xmlTransformerService.transformBySourceWithXslCache( strXml, xslSource, strXslUniquePrefix,
593                 htParamRequest, outputProperties );
594         }
595         catch ( NumberFormatException e )
596         {
597             return null;
598         }
599     }
600 
601     /**
602      * Gets the category list portlet linked with the document
603      *
604      * @param request The Http request
605      * @param document The document
606      * @param nPortletId The ID of the documents list portlet where the document
607      *            has been published.
608      * @param nMode The current mode.
609      * @return The HTML code of the categories list portlet as a String
610      */
611     private String getRelatedDocumentsPortlet( HttpServletRequest request, Document document, int nPortletId, int nMode )
612     {
613         if ( ( nMode != MODE_ADMIN ) && ( document.getCategories(  ) != null ) &&
614                 ( document.getCategories(  ).size(  ) > 0 ) )
615         {
616             HashMap<String, Object> model = new HashMap<String, Object>(  );
617             List<Document> listRelatedDocument = DocumentHome.findByRelatedCategories( document, request.getLocale(  ) );
618 
619             List<Document> listDocument = new ArrayList<Document>(  );
620             ReferenceList listDocumentPortlet = new ReferenceList(  );
621 
622             // Create list of related documents from the specified categories of input document 
623             for ( Document relatedDocument : listRelatedDocument )
624             {
625                 // Get list of portlets for each document
626                 for ( Portlet portlet : PublishingService.getInstance(  )
627                                                          .getPortletsByDocumentId( Integer.toString( 
628                             relatedDocument.getId(  ) ) ) )
629                 {
630                     // Check if document and portlet are published and document is not the input document 
631                     if ( ( PublishingService.getInstance(  ).isPublished( relatedDocument.getId(  ), portlet.getId(  ) ) ) &&
632                             ( portlet.getStatus(  ) == Portlet.STATUS_PUBLISHED ) && ( relatedDocument.isValid(  ) ) &&
633                             ( relatedDocument.getId(  ) != document.getId(  ) ) )
634                     {
635                         listDocumentPortlet.addItem( Integer.toString( relatedDocument.getId(  ) ),
636                             Integer.toString( portlet.getId(  ) ) );
637                         listDocument.add( relatedDocument );
638 
639                         break;
640                     }
641                 }
642             }
643 
644             model.put( MARK_DOCUMENT_CATEGORIES_LIST, listDocument );
645             model.put( MARK_PORTLET_ID_LIST, listDocumentPortlet );
646 
647             HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_DOCUMENT_CATEGORIES,
648                     request.getLocale(  ), model );
649 
650             return template.getHtml(  );
651         }
652         else
653         {
654             return StringUtils.EMPTY;
655         }
656     }
657 
658     /**
659      * @see net.sf.ehcache.event.CacheEventListener#dispose()
660      */
661     public void dispose(  )
662     {
663     }
664 
665     /**
666      * @see net.sf.ehcache.event.CacheEventListener#notifyElementEvicted(net.sf.ehcache.Ehcache,
667      *      net.sf.ehcache.Element)
668      */
669     public void notifyElementEvicted( Ehcache cache, Element element )
670     {
671         _keyMemory.remove( element.getKey(  ) );
672     }
673 
674     /**
675      * @see net.sf.ehcache.event.CacheEventListener#notifyElementExpired(net.sf.ehcache.Ehcache,
676      *      net.sf.ehcache.Element)
677      */
678     public void notifyElementExpired( Ehcache cache, Element element )
679     {
680         _keyMemory.remove( element.getKey(  ) );
681     }
682 
683     /**
684      * @see net.sf.ehcache.event.CacheEventListener#notifyElementPut(net.sf.ehcache.Ehcache,
685      *      net.sf.ehcache.Element)
686      */
687     public void notifyElementPut( Ehcache cache, Element element )
688         throws CacheException
689     {
690     }
691 
692     /**
693      * @see net.sf.ehcache.event.CacheEventListener#notifyElementRemoved(net.sf.ehcache.Ehcache,
694      *      net.sf.ehcache.Element)
695      */
696     public void notifyElementRemoved( Ehcache cache, Element element )
697         throws CacheException
698     {
699         _keyMemory.remove( element.getKey(  ) );
700     }
701 
702     /**
703      * @see net.sf.ehcache.event.CacheEventListener#notifyElementUpdated(net.sf.ehcache.Ehcache,
704      *      net.sf.ehcache.Element)
705      */
706     public void notifyElementUpdated( Ehcache cache, Element element )
707         throws CacheException
708     {
709     }
710 
711     /**
712      * @see net.sf.ehcache.event.CacheEventListener#notifyRemoveAll(net.sf.ehcache.Ehcache)
713      */
714     public void notifyRemoveAll( Ehcache cache )
715     {
716         _keyMemory.clear(  );
717     }
718 
719     /**
720      * Build the Cache HashMap key for pages Goal is to be able to have a
721      * synchronized on the key but a synchronize only work with strict
722      * reference. So we manage an hashmap of string reference for cache keys to
723      * be able to get them back.
724      *
725      * @param strDocumentId The id of the document
726      * @param strPortletId The id of the portlet of the document
727      * @param strSiteLocale The site locale
728      * @param nMode The mode
729      * @return The HashMap key for articles pages as a String.
730      */
731     private String getKey( String strDocumentId, String strPortletId, String strSiteLocale, int nMode )
732     {
733         String key = "D" + strDocumentId + "P" + strPortletId + "L" + strSiteLocale + "M" + nMode;
734         String keyInMemory = _keyMemory.putIfAbsent( key, key );
735 
736         if ( keyInMemory != null )
737         {
738             return keyInMemory;
739         }
740 
741         return key;
742     }
743 
744     /**
745      * Same as above, for portlet keys. Use the same hashmap because they don't collide
746      * @param strDocumentId The id of the document
747      * @return The HashMap key for portlet as a String.
748      */
749     private String getKey( String strPortletKey )
750     {
751         String keyInMemory = _keyMemory.putIfAbsent( strPortletKey, strPortletKey );
752 
753         if ( keyInMemory != null )
754         {
755             return keyInMemory;
756         }
757 
758         return strPortletKey;
759     }
760 
761     /**
762      * @see java.lang.Object#clone()
763      */
764     @Override
765     public Object clone(  )
766     {
767         return new DocumentContentService(  );
768     }
769 
770     /**
771      * Remove a document from the cache
772      * @param strDocumentId the document id
773      * @param strPortletId the portlet id
774      */
775     public void removeFromCache( String strDocumentId, String strPortletId )
776     {
777         if ( getCache(  ) != null )
778         {
779             String strKey = getKey( strDocumentId, strPortletId, LOCALE_FR, PortalJspBean.MODE_HTML );
780 
781             getCache(  ).remove( strKey );
782 
783             strKey = getKey( strDocumentId, strPortletId, LOCALE_EN, PortalJspBean.MODE_HTML );
784 
785             getCache(  ).remove( strKey );
786         }
787     }
788 }