SiteMapApp.java

/*
 * Copyright (c) 2002-2022, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.portal.web.xpages;

import fr.paris.lutece.portal.business.XmlContent;
import fr.paris.lutece.portal.business.page.Page;
import fr.paris.lutece.portal.business.page.PageHome;
import fr.paris.lutece.portal.business.portalcomponent.PortalComponentHome;
import fr.paris.lutece.portal.business.style.ModeHome;
import fr.paris.lutece.portal.business.stylesheet.StyleSheet;
import fr.paris.lutece.portal.service.html.XmlTransformerService;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.plugin.Plugin;
import fr.paris.lutece.portal.service.portal.PortalService;
import fr.paris.lutece.portal.service.security.LuteceUser;
import fr.paris.lutece.portal.service.security.SecurityService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPathService;
import fr.paris.lutece.portal.web.admin.AdminPageJspBean;
import fr.paris.lutece.util.xml.XmlUtil;

import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

/**
 * This class provides the map of the pages on the site
 */
public class SiteMapApp implements XPageApplication
{
    /**
     * Serial version UID
     */
    private static final long serialVersionUID = 1799476496018552101L;
    private static final int PORTAL_COMPONENT_SITE_MAP_ID = 6;
    private static final int MODE_NORMAL = 0;
    private static final int MODE_ADMIN = 1;
    private static final String PARAMETER_SITE_PATH = "site-path";
    private static final String MARKER_TARGET = "target";
    private static final String TARGET_TOP = "target='_top'";
    private static final String PROPERTY_SERVICE_NAME = "portal.site.serviceName.siteMapService";
    private static final String PROPERTY_PATH_LABEL = "portal.site.site_map.pathLabel";
    private static final String PROPERTY_PAGE_TITLE = "portal.site.site_map.pageTitle";

    /**
     * Creates a new SiteMapPage object
     */
    public SiteMapApp( )
    {
        // Ctor
    }

    /**
     * Returns the localized service name
     * 
     * @param locale
     *            The locale
     * @return The localized service name
     */
    public String getName( Locale locale )
    {
        return I18nService.getLocalizedString( PROPERTY_SERVICE_NAME, locale );
    }

    /**
     * Build or get in the cache the page which contains the site map depending on the mode
     *
     * @param request
     *            The Http request
     * @param nMode
     *            The selected mode
     * @param plugin
     *            The plugin
     * @return The content of the site map
     */
    @Override
    public XPage getPage( HttpServletRequest request, int nMode, Plugin plugin )
    {
        XPage page = new XPage( );
        String strKey = getKey( nMode, request );

        Locale locale = request.getLocale( );

        SiteMapCacheService siteMapCacheService = SiteMapCacheService.getInstance( );

        // Check the key in the cache
        String strCachedPage = siteMapCacheService.isCacheEnable( ) ? (String) siteMapCacheService.getFromCache( strKey ) : null;

        if ( strCachedPage == null )
        {
            // Build the HTML document
            String strPage = buildPageContent( nMode, request );

            // Add it to the cache
            if ( siteMapCacheService.isCacheEnable( ) )
            {
                synchronized( strKey )
                {
                    siteMapCacheService.putInCache( strKey, strPage );
                }
            }

            page.setPathLabel( I18nService.getLocalizedString( PROPERTY_PATH_LABEL, locale ) );
            page.setTitle( I18nService.getLocalizedString( PROPERTY_PAGE_TITLE, locale ) );
            page.setContent( strPage );

            return page;
        }

        // The document exist in the cache
        page.setPathLabel( I18nService.getLocalizedString( PROPERTY_PATH_LABEL, locale ) );
        page.setTitle( I18nService.getLocalizedString( PROPERTY_PAGE_TITLE, locale ) );
        page.setContent( strCachedPage );

        return page;
    }

    /**
     * Gets the cache key
     * 
     * @param nMode
     *            The mode
     * @param request
     *            The HTTP request
     * @return The key
     */
    private String getKey( int nMode, HttpServletRequest request )
    {
        String strRoles = "-";

        if ( SecurityService.isAuthenticationEnable( ) )
        {
            LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );

            if ( ( user != null ) && ( user.getRoles( ) != null ) )
            {
                String [ ] roles = user.getRoles( );
                Arrays.sort( roles );
                strRoles = StringUtils.join( roles, ',' );
            }
        }

        return "[m:" + nMode + "][roles:" + strRoles + "]";
    }

    /**
     * Build an XML document containing the arborescence of the site pages and transform it with the stylesheet combined with the mode
     * 
     * @param nMode
     *            The selected mode
     * @param request
     *            The HttpServletRequest
     * @return The content of the site map
     */
    private String buildPageContent( int nMode, HttpServletRequest request )
    {
        StringBuffer strArborescenceXml = new StringBuffer( );
        strArborescenceXml.append( XmlUtil.getXmlHeader( ) );

        int nLevel = 0;
        findPages( strArborescenceXml, PortalService.getRootPageId( ), nLevel, new HashSet<>( ), request );

        // Added in v1.3
        // Use the same stylesheet for normal or admin mode
        StyleSheet xslSource;

        switch( nMode )
        {
            case MODE_NORMAL:
            case MODE_ADMIN:
                xslSource = PortalComponentHome.getXsl( PORTAL_COMPONENT_SITE_MAP_ID, MODE_NORMAL );

                break;

            default:
                xslSource = PortalComponentHome.getXsl( PORTAL_COMPONENT_SITE_MAP_ID, nMode );

                break;
        }

        // Added in v1.3
        // Add a path param for choose url to use in admin or normal mode
        Map<String, String> mapParamRequest = new HashMap<>( );

        if ( nMode != MODE_ADMIN )
        {
            mapParamRequest.put( PARAMETER_SITE_PATH, AppPathService.getPortalUrl( ) );
        }
        else
        {
            mapParamRequest.put( PARAMETER_SITE_PATH, AppPathService.getAdminPortalUrl( ) );
            mapParamRequest.put( MARKER_TARGET, TARGET_TOP );
        }

        Properties outputProperties = ModeHome.getOuputXslProperties( nMode );

        XmlTransformerService xmlTransformerService = new XmlTransformerService( );

        return xmlTransformerService.transformBySourceWithXslCache( strArborescenceXml.toString( ), xslSource, mapParamRequest, outputProperties );
    }

    /**
     * Build recursively the XML document containing the arborescence of the site pages
     * 
     * @param strXmlArborescence
     *            The buffer in which adding the current page of the arborescence
     * @param nPageId
     *            The current page of the recursive course
     * @param nLevel
     *            The depth level of the page in the arborescence
     * @param request
     *            The HttpServletRequest
     */
    private void findPages( StringBuffer strXmlArborescence, int nPageId, int nLevel, Set<Integer> seenPages, HttpServletRequest request )
    {
        if ( !seenPages.add( nPageId ) )
        {
            AppLogService.error( "SiteMapApp : A cycle exists in pages; page id {} was already processed", nPageId );
            return;
        }
        Page page = PageHome.getPage( nPageId );

        if ( page.isVisible( request ) )
        {
            XmlUtil.beginElement( strXmlArborescence, XmlContent.TAG_PAGE );
            XmlUtil.addElement( strXmlArborescence, XmlContent.TAG_PAGE_ID, page.getId( ) );
            XmlUtil.addElementHtml( strXmlArborescence, XmlContent.TAG_PAGE_NAME, page.getName( ) );
            XmlUtil.addElement( strXmlArborescence, XmlContent.TAG_PAGE_DESCRIPTION, page.getDescription( ) );
            XmlUtil.addElement( strXmlArborescence, XmlContent.TAG_PAGE_LEVEL, nLevel );

            AdminPageJspBean adminPage = new AdminPageJspBean( );

            if ( page.getImageContent( ) != null )
            {
                int nImageLength = page.getImageContent( ).length;

                if ( nImageLength >= 1 )
                {
                    String strPageId = Integer.toString( page.getId( ) );
                    XmlUtil.addElement( strXmlArborescence, XmlContent.TAG_PAGE_IMAGE, adminPage.getResourceImagePage( page, strPageId ) );
                }
            }

            XmlUtil.beginElement( strXmlArborescence, XmlContent.TAG_CHILD_PAGES_LIST );

            for ( Page pageChild : PageHome.getChildPagesMinimalData( nPageId ) )
            {
                findPages( strXmlArborescence, pageChild.getId( ), nLevel + 1, seenPages, request );
            }

            XmlUtil.endElement( strXmlArborescence, XmlContent.TAG_CHILD_PAGES_LIST );
            XmlUtil.endElement( strXmlArborescence, XmlContent.TAG_PAGE );
        }
    }
}