ExtendableContentPostProcessor.java

/*
 * Copyright (c) 2002-2021, 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.plugins.extend.service.content;

import fr.paris.lutece.plugins.extend.business.extender.ResourceExtenderDTO;
import fr.paris.lutece.plugins.extend.service.converter.IStringMapper;
import fr.paris.lutece.plugins.extend.service.extender.IResourceExtenderService;
import fr.paris.lutece.portal.business.page.Page;
import fr.paris.lutece.portal.service.content.ContentPostProcessor;
import fr.paris.lutece.portal.service.template.AppTemplateService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPathService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.util.html.HtmlTemplate;

import org.apache.commons.lang3.StringUtils;

import org.springframework.beans.factory.InitializingBean;

import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;

import javax.servlet.http.HttpServletRequest;

/**
 *
 * This content post processor replace all macro of type @Extender[idResource,resourceType,extenderType,parameters]@ to the associated extender content.
 *
 */
public class ExtendableContentPostProcessor implements ContentPostProcessor, InitializingBean
{
    /**
     * Name of this bean
     */
    public static final String BEAN_NAME = "extend.extendableContentPostProcessor";
    private static final String NAME = "Extend content processor";
    private static final String END_BODY = "</body>";
    private static final String EXTEND_PARAMETERED_ID = "ExtendParameteredId";

    // PROPERTIES
    private static final String PROPERTY_CLIENT_SIDE = "extend.contentPostProcessor.clientSide";

    // MARKS
    private static final String MARK_REGEX_PATTERN = "extendRegexPattern";
    private static final String MARK_BASE_URL = "baseUrl";
    private static final String PARAM_PAGE = "page";
    private static final String PARAM_PORTLET_ID = "portlet_id";

    // TEMPLATES
    private static final String TEMPLATE_CONTENT_POST_PROCESSOR = "skin/plugins/extend/extendable_content_post_processor.html";
    @Inject
    private IResourceExtenderService _extenderService;
    @Inject
    private IStringMapper<ResourceExtenderDTO> _mapper;
    private String _strRegexPattern;
    private Pattern _regexPattern;
    private String _strExtenderParameterRegexPattern;
    private Pattern _extendedParameterRegexPattern;

    /**
     * Sets the regex pattern.
     *
     * @param strRegexPattern
     *            the new regex pattern
     */
    public void setRegexPattern( String strRegexPattern )
    {
        _strRegexPattern = strRegexPattern;
        if ( _regexPattern == null || !_regexPattern.pattern( ).equals( strRegexPattern ) )
        {
            Pattern pattern = Pattern.compile( strRegexPattern );
            _regexPattern = pattern;
        }
    }

    /**
     * Sets the regex pattern.
     *
     * @param strExtenderParameterRegexPattern
     *            the new regex pattern
     */
    public void setExtenderParameterRegexPattern( String strExtenderParameterRegexPattern )
    {
        _strExtenderParameterRegexPattern = strExtenderParameterRegexPattern;
        if ( _extendedParameterRegexPattern == null || !_extendedParameterRegexPattern.pattern( ).equals( strExtenderParameterRegexPattern ) )
        {
            Pattern pattern = Pattern.compile( strExtenderParameterRegexPattern );
            _extendedParameterRegexPattern = pattern;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getName( )
    {
        return NAME;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String process( HttpServletRequest request, String strContent )
    {
        String strHtmlContent = strContent;

        // Check if the process is carried out in client or server side
        boolean bClientSide = Boolean.valueOf( AppPropertiesService.getProperty( PROPERTY_CLIENT_SIDE, "false" ) );

        if ( bClientSide )
        {
            // CLIENT SIDE
            int nPos = strHtmlContent.indexOf( END_BODY );

            if ( nPos < 0 )
            {
                AppLogService.error( "ExtendableContentPostProcessor Service : no BODY end tag found" );

                return strHtmlContent;
            }

            Map<String, Object> model = new HashMap<String, Object>( );
            model.put( MARK_BASE_URL, AppPathService.getBaseUrl( request ) );
            model.put( MARK_REGEX_PATTERN, _strRegexPattern );

            HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_CONTENT_POST_PROCESSOR, request.getLocale( ), model );

            StringBuilder sb = new StringBuilder( );
            sb.append( strHtmlContent.substring( 0, nPos ) );
            sb.append( template.getHtml( ) );
            sb.append( strHtmlContent.substring( nPos ) );
            strHtmlContent = sb.toString( );
        }
        else
        {
            // SERVER SIDE

            /**
             * Replace all makers @Extender[<idResource>,<resourceType>,<extenderType>,<params>]@ to the correct HTML content of the extender. 1) First parse
             * the content of the markers 2) Get all information (idResource, resourceType, extenderType, params) 3) Get the html content from the given
             * information 4) Replace the markers by the html content
             */

            // 1) First parse the content of the markers
            Matcher match = _regexPattern.matcher( strHtmlContent );
            Matcher parameterMatch = null;
            StringBuffer strResultHTML = new StringBuffer( strHtmlContent.length( ) );
            while ( match.find( ) )
            {
                String strMarker = match.group( );

                // 2) Get all information (idResource, resourceType, extenderType, params)
                ResourceExtenderDTO resourceExtender = _mapper.map( match.group( 1 ) );
                boolean bParameteredId = StringUtils.equalsIgnoreCase( resourceExtender.getIdExtendableResource( ), EXTEND_PARAMETERED_ID );

                if ( bParameteredId )
                {
                    if ( parameterMatch == null )
                    {
                        parameterMatch = _extendedParameterRegexPattern.matcher( strHtmlContent );
                    }
                    else
                    {
                        parameterMatch.reset( );
                    }

                    while ( parameterMatch.find( ) )
                    {
                        ResourceExtenderDTO realResourceExtender = _mapper.map( parameterMatch.group( 1 ) );

                        if ( StringUtils.equals( realResourceExtender.getExtendableResourceType( ), resourceExtender.getExtendableResourceType( ) )
                                && StringUtils.equals( realResourceExtender.getExtenderType( ), resourceExtender.getExtenderType( ) ) )
                        {
                            resourceExtender.setIdExtendableResource( realResourceExtender.getIdExtendableResource( ) );

                            break;
                        }
                    }
                }

                String strHtml = StringUtils.EMPTY;

                if ( !bParameteredId || !StringUtils.equalsIgnoreCase( resourceExtender.getIdExtendableResource( ), EXTEND_PARAMETERED_ID ) )
                {
                    // 3) Get the html content from the given information
                    if ( !StringUtils.equals( resourceExtender.getExtendableResourceType( ), Page.RESOURCE_TYPE )
                            || ( StringUtils.isBlank( request.getParameter( PARAM_PAGE ) )
                                    && StringUtils.isBlank( request.getParameter( PARAM_PORTLET_ID ) ) ) )
                    {
                        strHtml = _extenderService.getContent( resourceExtender.getIdExtendableResource( ), resourceExtender.getExtendableResourceType( ),
                                resourceExtender.getExtenderType( ), resourceExtender.getParameters( ), request );
                    }
                }

                // 4) Replace the markers by the html content
                match.appendReplacement( strResultHTML, Matcher.quoteReplacement( strHtml ) );
            }
            match.appendTail( strResultHTML );
            strHtmlContent = strResultHTML.toString( );
        }

        if ( StringUtils.isNotBlank( _strExtenderParameterRegexPattern ) )
        {
            strHtmlContent = _extendedParameterRegexPattern.matcher( strHtmlContent ).replaceAll( "" );
        }

        return strHtmlContent;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterPropertiesSet( ) throws Exception
    {
        Assert.notNull( _strRegexPattern, "The property 'regexPattern' is required." );
    }
}