PortalMenuService.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.service.portal;

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.cache.AbstractCacheableService;
import fr.paris.lutece.portal.service.html.XmlTransformerService;
import fr.paris.lutece.portal.service.page.PageEvent;
import fr.paris.lutece.portal.service.page.PageEventListener;
import fr.paris.lutece.portal.service.page.PageService;
import fr.paris.lutece.portal.service.security.LuteceUser;
import fr.paris.lutece.portal.service.security.SecurityService;
import fr.paris.lutece.util.xml.XmlUtil;

import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.servlet.http.HttpServletRequest;

/**
 * This Service build the portal menu
 */
public final class PortalMenuService extends AbstractCacheableService implements PageEventListener
{
    public static final int MENU_INIT = 0;
    public static final int MENU_MAIN = 1;
    public static final int MODE_NORMAL = 0;
    public static final int MODE_ADMIN = 1;
    private static final int PORTAL_COMPONENT_MENU_INIT_ID = 3;
    private static final int PORTAL_COMPONENT_MAIN_MENU_ID = 4;
    private static final String SERVICE_NAME = "PortalMenuService";

    // Menus cache
    private static PortalMenuService _singleton;

    /** Creates a new instance of PortalMenuService */
    private PortalMenuService( )
    {
        initCache( getName( ) );
        PageService.addPageEventListener( this );
    }

    /**
     * Get the unique instance of the service
     * 
     * @return The unique instance
     */
    public static synchronized PortalMenuService getInstance( )
    {
        if ( _singleton == null )
        {
            _singleton = new PortalMenuService( );
        }

        return _singleton;
    }

    /**
     * Returns the service name
     * 
     * @return The service name
     */
    public String getName( )
    {
        return SERVICE_NAME;
    }

    /**
     * Returns the menu bar from the cache or builds it if it not stored in it
     * 
     * @param request
     *            The HTTP request
     * @param nMode
     *            The selected mode
     * @param nPart
     *            The part of the menu to build
     * @param nCurrentPageId
     *            The current page ID
     * @return The list of the menus layed out with the stylesheet correpsonding to the mode
     */
    public String getMenuContent( int nCurrentPageId, int nMode, int nPart, HttpServletRequest request )
    {
        String strKey = getKey( nMode, nPart, request );
        String strMenu = (String) getFromCache( strKey );

        // Seek for the key in the cache
        if ( strMenu == null )
        {
            // Builds the HTML document
            strMenu = buildMenuContent( nCurrentPageId, nMode, nPart, request );

            // Add it in the cache
            putInCache( strKey, strMenu );

            return strMenu;
        }

        // The document exist in the cache
        return strMenu;
    }

    /**
     * Builds the menu bar
     *
     * @param nCurrentPageId
     *            The current page ID
     * @param nMode
     *            The selected mode
     * @param nPart
     *            The part of the menu to build
     * @param request
     *            The HttpServletRequest
     * @return The list of the menus layed out with the stylesheet of the mode
     */
    private String buildMenuContent( int nCurrentPageId, int nMode, int nPart, HttpServletRequest request )
    {
        Collection<Page> listPagesMenu = PageHome.getChildPagesMinimalData( PortalService.getRootPageId( ) );

        StringBuffer strXml = new StringBuffer( );
        strXml.append( XmlUtil.getXmlHeader( ) );
        XmlUtil.beginElement( strXml, XmlContent.TAG_MENU_LIST );

        int nMenuIndex = 1;

        for ( Page menuPage : listPagesMenu )
        {
            if ( ( menuPage.isVisible( request ) ) || ( nMode == MODE_ADMIN ) )
            {
                buildPageXml( menuPage, strXml, nMode, nMenuIndex, nCurrentPageId, request );

                nMenuIndex++;
            }
        }

        XmlUtil.endElement( strXml, XmlContent.TAG_MENU_LIST );

        // Added in v1.3
        StyleSheet xslSource = getMenuXslSource( nMode, nPart );

        Properties outputProperties = ModeHome.getOuputXslProperties( nMode );

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

        XmlTransformerService xmlTransformerService = new XmlTransformerService( );

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

    private void buildPageXml( Page menuPage, StringBuffer strXml, int nMode, int nMenuIndex, int nCurrentPageId, HttpServletRequest request )
    {
        XmlUtil.beginElement( strXml, XmlContent.TAG_MENU );
        XmlUtil.addElement( strXml, XmlContent.TAG_MENU_INDEX, nMenuIndex );
        XmlUtil.addElement( strXml, XmlContent.TAG_PAGE_ID, menuPage.getId( ) );
        XmlUtil.addElementHtml( strXml, XmlContent.TAG_PAGE_NAME, menuPage.getName( ) );
        XmlUtil.addElementHtml( strXml, XmlContent.TAG_CURRENT_PAGE_ID, String.valueOf( nCurrentPageId ) );

        Collection<Page> listSubLevelMenuPages = PageHome.getChildPagesMinimalData( menuPage.getId( ) );

        // add element submenu-list only if list not empty
        if ( !listSubLevelMenuPages.isEmpty( ) )
        {
            // Seek of the sub-menus
            XmlUtil.beginElement( strXml, XmlContent.TAG_SUBLEVEL_MENU_LIST );

            int nSubLevelMenuIndex = 1;

            for ( Page subLevelMenuPage : listSubLevelMenuPages )
            {
                if ( ( subLevelMenuPage.isVisible( request ) ) || ( nMode == MODE_ADMIN ) )
                {
                    XmlUtil.beginElement( strXml, XmlContent.TAG_SUBLEVEL_MENU );
                    XmlUtil.addElement( strXml, XmlContent.TAG_MENU_INDEX, nMenuIndex );
                    XmlUtil.addElement( strXml, XmlContent.TAG_SUBLEVEL_INDEX, nSubLevelMenuIndex );
                    XmlUtil.addElement( strXml, XmlContent.TAG_PAGE_ID, subLevelMenuPage.getId( ) );
                    XmlUtil.addElementHtml( strXml, XmlContent.TAG_PAGE_NAME, subLevelMenuPage.getName( ) );
                    XmlUtil.endElement( strXml, XmlContent.TAG_SUBLEVEL_MENU );
                    XmlUtil.addElementHtml( strXml, XmlContent.TAG_CURRENT_PAGE_ID, String.valueOf( nCurrentPageId ) );
                }
            }

            XmlUtil.endElement( strXml, XmlContent.TAG_SUBLEVEL_MENU_LIST );
        }

        XmlUtil.endElement( strXml, XmlContent.TAG_MENU );
    }

    private StyleSheet getMenuXslSource( int nMode, int nPart )
    {
        // Use the same stylesheet for normal or admin mode
        StyleSheet xslSource;

        // Selection of the XSL stylesheet
        switch( nMode )
        {
            case MODE_NORMAL:
            case MODE_ADMIN:
                xslSource = PortalComponentHome.getXsl( PORTAL_COMPONENT_MAIN_MENU_ID, MODE_NORMAL );

                break;

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

                break;
        }

        if ( nPart == MENU_INIT )
        {
            switch( nMode )
            {
                case MODE_NORMAL:
                case MODE_ADMIN:
                    xslSource = PortalComponentHome.getXsl( PORTAL_COMPONENT_MENU_INIT_ID, MODE_NORMAL );

                    break;

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

                    break;
            }
        }
        return xslSource;
    }

    /**
     * Returns the key corresponding to the part according to the selected mode
     *
     * @param nMode
     *            The mode
     * @param nPart
     *            the part
     * @param request
     *            The HTTP request
     * @return The key as a String
     */
    private String getKey( int nMode, int nPart, HttpServletRequest request )
    {
        String strRoles = "-";

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

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

        StringBuilder sbKey = new StringBuilder( );
        sbKey.append( "[menu:" ).append( nPart ).append( "][m:" ).append( nMode ).append( "][roles:" ).append( strRoles ).append( ']' );

        return sbKey.toString( );
    }

    @Override
    public void processPageEvent( PageEvent event )
    {
        // page was added, removed or updated; clear cache
        resetCache( );
    }
}