package com.psddev.cms.db; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import javax.servlet.ServletRequest; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.BodyTagSupport; /** * For setting contexts that can change all renderers within. * * <p>For example, given the following {@code Article.java}:</p> * * <blockquote><pre><code data-type="java"> {@literal @}Renderer.Path(value = "article.jsp") class Article { Image mainImage; } * </code></pre></blockquote> * * <p>And {@code Image.java}:</p> * * <blockquote><pre><code data-type="java"> {@literal @}Renderer.Paths({ {@literal @}Renderer.Path(value = "image-in-article.jsp", context = "image"), {@literal @}Renderer.path(value = "image.jsp") }) class Image { ... } * </code></pre></blockquote> * * <p>The {@code cms:render} in the following {@code article.jsp} would * use {@code image-in-article.jsp} to render the {@code Image} instance:</p> * * <blockquote><pre><code data-type="jsp">{@literal <cms:context name="article"> <cms:render value="${article.mainImage}" /> </cms:context> * }</code></pre></blockquote> * * @see Renderer */ @SuppressWarnings("serial") public class ContextTag extends BodyTagSupport { private static final String ATTRIBUTE_PREFIX = ContextTag.class.getName(); private static final String CONTEXTS_ATTRIBUTE = ATTRIBUTE_PREFIX + "contexts"; private String name; public void setName(String name) { this.name = name; } // --- BodyTagSupport support --- @Override public int doStartTag() throws JspException { Static.pushContext(pageContext.getRequest(), name); return EVAL_BODY_INCLUDE; } @Override public int doEndTag() throws JspException { Static.popContext(pageContext.getRequest()); return EVAL_PAGE; } /** * {@link ContextTag} utility methods. */ public static final class Static { /** * Returns the deque of all contexts associated with the gieven * {@code request} so far. * * @param request Can't be {@code null}. * @return Never {@code null}. Mutable. */ @SuppressWarnings("unchecked") public static Deque<String> getContexts(ServletRequest request) { Deque<String> contexts = (Deque<String>) request.getAttribute(CONTEXTS_ATTRIBUTE); if (contexts == null) { contexts = new ArrayDeque<String>(); request.setAttribute(CONTEXTS_ATTRIBUTE, contexts); } return contexts; } /** * Pushes the given {@code context} to be current in the given * {@code request}. * * @param request Can't be {@code null}. * @param context May be {@code null}. */ public static void pushContext(ServletRequest request, String context) { getContexts(request).addLast(context); request.setAttribute("context", context); } /** * Pops the current context out of the given {@code request}. * * @param request Can't be {@code null}. */ public static String popContext(ServletRequest request) { Deque<String> contexts = getContexts(request); String popped = contexts.removeLast(); request.setAttribute("context", contexts.peekLast()); return popped; } /** * Returns {@code true} if the given {@code request} is in the given * {@code context}. * * @param context If {@code null}, returns {@code false}. */ public static boolean isInContext(ServletRequest request, String context) { if (context != null) { for (Iterator<String> i = getContexts(request).descendingIterator(); i.hasNext();) { if (context.equals(i.next())) { return true; } } } return false; } } }