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