Package org.jtalks.jcommune.service.bb2htmlprocessors

Source Code of org.jtalks.jcommune.service.bb2htmlprocessors.BbCodeReviewProcessor

/**
* Copyright (C) 2011  JTalks.org Team
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

package org.jtalks.jcommune.service.bb2htmlprocessors;

import com.google.common.annotations.VisibleForTesting;
import org.jtalks.jcommune.service.TopicModificationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.kefirsf.bb.TextProcessor;
import org.kefirsf.bb.TextProcessorAdapter;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Processor for bb2html which saves inner "[/code]" tags in code review posts. This is required because Code Reviews
* initially have surrounding [code], but user himself also might have put there same string, without this processor all
* this stuff is treated as bb codes and is replaced with HTML.<br/> Consider such situation: <ul> <li>User creates code
* review which itself means that all the text is surrounded with [code]. This results in {@code
* [code][-code][/code][/code]}</li> <li>We replace internal [/code] with [-code] inside of this pre-processor and place
* that information inside of the {@link #REPLACE_HISTORY_LIST_ATTRIBUTE} attribute like this {@code false, true} which
* means that internal [-code] is user input and not the work of our pre-processor [/code]</li> <li>In the {@link
* #postProcess(String)} we find this attribute and we know what second [-code] should be replaced back to [/code]</li>
* </ul>
*
* @author Evgeny Kapinos
* @see <a href="http://jira.jtalks.org/browse/JC-1261">JIRA</a> for high-level details.
*/
public class BbCodeReviewProcessor extends TextProcessorAdapter implements TextProcessor, TextPostProcessor {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static final String CODE_JAVA_BBCODE_END_REPLACEMENT = "[-code]";
    private static final String CODE_JAVA_BBCODE_END_REPLACEMENT_PATTERN = "\\[-code\\]";
    /**
     * This is an attribute in the http request which contains a list of bb-codes that should or should not be replaced
     * back.
     */
    @VisibleForTesting
    protected static final String REPLACE_HISTORY_LIST_ATTRIBUTE = "BBCodeReviewPreprocessor_replaceHistoryList";

    /**
     * Process incoming encoded text and replacing [/code] tags to [-code]
     *
     * @param bbEncodedText BB encoded text to process
     * @return processed text
     */
    @Override
    public String process(String bbEncodedText) {
        HttpServletRequest httpServletRequest = getServletRequest();
        httpServletRequest.removeAttribute(REPLACE_HISTORY_LIST_ATTRIBUTE);

        if (!isCodeReviewPost(httpServletRequest) || !isValidCodeReviewBbCodeString(bbEncodedText)) {
            return bbEncodedText;
        }

        List<Boolean> replaceHistoryList = getReplaceHistoryList(bbEncodedText);
        if (replaceHistoryList.isEmpty()) {
            return bbEncodedText;
        }

        httpServletRequest.setAttribute(REPLACE_HISTORY_LIST_ATTRIBUTE, replaceHistoryList);

        return substituteCloseCodeTagsWithTemporaryReplacementInEncodedText(bbEncodedText);
    }

    /**
     * Process incoming encoded text with replacing [/code] tags to [-code]
     *
     * @param bbEncodedText BB encoded text to process
     * @return processed text
     */
    @Override
    public CharSequence process(CharSequence bbEncodedText) {
        String result = process(bbEncodedText.toString());
        return new StringBuilder(result).subSequence(0, result.length());
    }

    /**
     * Process incoming decoded text by replacing [-code] tags to [/code].
     *
     * @param bbDecodedText text returned after to BBCode processor
     * @return resultant text
     */
    @Override
    public String postProcess(String bbDecodedText) {
        HttpServletRequest httpServletRequest = getServletRequest();

        if (!isCodeReviewPost(httpServletRequest)) {
            return bbDecodedText;
        }

        @SuppressWarnings("unchecked")
        List<Boolean> replaceHistoryList =
                (List<Boolean>) httpServletRequest.getAttribute(REPLACE_HISTORY_LIST_ATTRIBUTE);
        if (replaceHistoryList == null) {
            return bbDecodedText;
        }

        httpServletRequest.removeAttribute(REPLACE_HISTORY_LIST_ATTRIBUTE);

        return removeTemporaryReplacementSubstitutionFromDecodedText(bbDecodedText, replaceHistoryList);
    }

    /**
     * Gets current request
     *
     * @return native {@link HttpServletRequest}
     */
    @VisibleForTesting
    protected HttpServletRequest getServletRequest() {
        RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
        return ((ServletRequestAttributes) attributes).getRequest();
    }

    /**
     * Checks in current request attributes Code Review token
     *
     * @return {@code true, false}
     */
    private boolean isCodeReviewPost(HttpServletRequest httpServletRequest) {
        String isCodeReviewPost = (String) httpServletRequest.getAttribute("isCodeReviewPost");
        return (isCodeReviewPost != null);
    }

    /**
     * Checks regular wrap [code]...[/code] in Code Review text
     *
     * @return {@code true, false}
     */
    private boolean isValidCodeReviewBbCodeString(String bbEncodedText) {
        // We use Pattern.DOTALL flag for extend ".*" pattern behavior to line terminators too
        Pattern pattern = Pattern.compile(TopicModificationService.CODE_JAVA_BBCODE_START_PATTERN + ".*"
                + TopicModificationService.CODE_JAVA_BBCODE_END_PATTERN, Pattern.DOTALL);
        Matcher matcher = pattern.matcher(bbEncodedText);
        if (!matcher.matches()) {
            logger.warn("BbCodeReviewProcessor called, but target encoded text \"" + bbEncodedText
                    + "\" doesn't wrapped with " + TopicModificationService.CODE_JAVA_BBCODE_START + "..."
                    + TopicModificationService.CODE_JAVA_BBCODE_END
                    + " BBCodes. Check \"isCodeReviewPost\" request attribute");
            return false;
        }
        return true;
    }

    /**
     * Removes regular wrap [code]...[/code] in Code Review text
     *
     * @return user text from full Code Review with wrap
     */
    private String getUserCodeReviewText(String bbEncodedText) {
        return bbEncodedText.substring(TopicModificationService.CODE_JAVA_BBCODE_START.length(),
                bbEncodedText.length() -
                        TopicModificationService.CODE_JAVA_BBCODE_END.length());
    }

    /**
     * Analyze Code Review text and creates replace history list
     *
     * @return replace history list
     */
    private List<Boolean> getReplaceHistoryList(String bbEncodedText) {
        String textOnly = getUserCodeReviewText(bbEncodedText);

        List<Boolean> replaceHistoryList = new ArrayList<Boolean>();

        Pattern pattern = Pattern.compile(TopicModificationService.CODE_JAVA_BBCODE_END_PATTERN + "|"
                + CODE_JAVA_BBCODE_END_REPLACEMENT_PATTERN);
        Matcher matcher = pattern.matcher(textOnly);
        while (matcher.find()) {
            replaceHistoryList.add(matcher.group().equals(TopicModificationService.CODE_JAVA_BBCODE_END));
        }
        return replaceHistoryList;
    }

    /**
     * Creates new BBcode string without [/code] tags in user part (replaced by [-code] tag)
     *
     * @return safety BBcode string with temporary replacements
     */
    private String substituteCloseCodeTagsWithTemporaryReplacementInEncodedText(String bbEncodedText) {
        return TopicModificationService.CODE_JAVA_BBCODE_START
                + getUserCodeReviewText(bbEncodedText).replaceAll(TopicModificationService.CODE_JAVA_BBCODE_END_PATTERN,
                CODE_JAVA_BBCODE_END_REPLACEMENT)
                + TopicModificationService.CODE_JAVA_BBCODE_END;
    }

    /**
     * Returns back original tags into already processed string. To be precise: [-code] tags are replaced back with
     * [code] tags
     *
     * @param bbDecodedText      the text after it was processed into HTML, it yet contains [-code] tags
     * @param replaceHistoryList true in the list means that [-code] should be replaced with [/code], false would mean
     *                           that [-code] is the original text written by user and it shouldn't be replaced with
     *                           [/code]
     * @return BBcode string without temporary replacements
     */
    private String removeTemporaryReplacementSubstitutionFromDecodedText(String bbDecodedText,
                                                                         List<Boolean> replaceHistoryList) {
        int index = 0;
        Pattern pattern = Pattern.compile(CODE_JAVA_BBCODE_END_REPLACEMENT_PATTERN);
        Matcher matcher = pattern.matcher(bbDecodedText);
        StringBuffer sb = new StringBuffer();
        try {
            while (matcher.find()) {
                if (replaceHistoryList.get(index)) {
                    matcher.appendReplacement(sb, TopicModificationService.CODE_JAVA_BBCODE_END);
                }
                index++;
            }
            matcher.appendTail(sb);
            return sb.toString();
        } catch (IndexOutOfBoundsException e) {
            logger.warn("BbCodeReviewProcessor called, but target decoded text \"" + bbDecodedText
                    + "\" doesn't contain " + replaceHistoryList.size() + " expected temporary replacement elements "
                    + CODE_JAVA_BBCODE_END_REPLACEMENT);
            return bbDecodedText;
        }
    }
}
TOP

Related Classes of org.jtalks.jcommune.service.bb2htmlprocessors.BbCodeReviewProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.