Package org.jboss.seam.wiki.core.wikitext.renderer.jsf

Source Code of org.jboss.seam.wiki.core.wikitext.renderer.jsf.MacroIncludeTextRenderer

/*
* JBoss, Home of Professional Open Source
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.seam.wiki.core.wikitext.renderer.jsf;

import org.jboss.seam.wiki.core.wikitext.renderer.NullWikiTextRenderer;
import org.jboss.seam.wiki.core.plugin.PluginRegistry;
import org.jboss.seam.wiki.core.plugin.WikiPluginMacro;
import org.jboss.seam.wiki.core.plugin.metamodel.MacroPluginModule;
import org.jboss.seam.wiki.core.model.WikiTextMacro;
import org.jboss.seam.core.Events;
import org.jboss.seam.core.ResourceLoader;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.Component;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.log.Log;
import org.jboss.seam.log.Logging;
import org.ajax4jsf.component.html.HtmlLoadStyle;

import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
import java.io.IOException;
import java.net.URL;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.el.VariableMapperWrapper;
import com.sun.facelets.tag.jsf.ComponentSupport;

import javax.faces.component.UIComponent;
import javax.el.VariableMapper;

/**
* Creates the included macro template components as first-class components in the JSF view
* as children of the <tt>UIWikiFormattedText</tt> component.
*
* <p>
* This routine parses the wiki text and for each encountered wiki macro, it tries to
* include an XHTML template. If no template is found, we do nothing. If a template is
* found, we also include its CSS into the document header, then add it to the parent
* component, the <tt>UIWikiFormattedText</tt> we are handling. This parent component
* keeps a map of <tt>WikiMacro</tt> instances, keyed by position in the rendered
* wiki text. This map of macros can be pulled out later, when we render the JSF view
* tree.
* </p>
* <p>
* Macros are never reentrant, that means a macro can not render itself. To avoid this,
* we push a macro onto a stack before including it in the component tree, after checking if
* it is already present on the stack. If it is already present, we log a warning and don't do
* anything. After rendering, we pop the stack. The stack is held in the PAGE context.
* </p>
* <p>
* This code is complicated, because Facelets is complicated. Do not touch it unless you
* are absolutely sure you know what you are doing.
* </p>
*
* @author Christian Bauer
*/
public class MacroIncludeTextRenderer extends NullWikiTextRenderer {

    private Log log = Logging.getLog(WikiFormattedTextHandler.class);

    public static final String MACRO_STACK_PAGE_VARIABLE = "macroStack";

    // A collection of all macros (whether they have templates or not) that we found in this piece of wiki text
    private Set<String> macrosFoundInWikiText = new HashSet<String>();

    UIWikiFormattedText parent;
    FaceletContext context;
    boolean enableTransientMacros;

    public MacroIncludeTextRenderer(UIWikiFormattedText parent, FaceletContext context, boolean enableTransientMacros) {
        this.parent = parent;
        this.context = context;
        this.enableTransientMacros = enableTransientMacros;
    }

    @Override
    public String renderMacro(WikiTextMacro wikiTextMacro) {
        log.debug("=== found macro in wiki text: " + wikiTextMacro);

        // Check reentrancy
        if (!isMacroOnPageStack(wikiTextMacro)) {
            log.debug("adding macro to page macro stack");
            getPageMacroStack().push(wikiTextMacro);
        } else {
            log.warn("macros are not reentrant, duplicate macro on page stack: " + wikiTextMacro);
            return null;
        }

        // Check if the wikiTextMacro actually is registered, we don't build unknown macros
        WikiPluginMacro pluginMacro = PluginRegistry.instance().createWikiPluginMacro(wikiTextMacro);
        if (pluginMacro == null) {
            log.info("macro is not bound in plugin registry: " + wikiTextMacro);
            getPageMacroStack().pop();
            return null;
        }

        // Check if we can find the template to include for this wikiTextMacro
        String macroIncludePath = getMacroIncludePath(pluginMacro);
        if (macroIncludePath == null) {
            getPageMacroStack().pop();
            return null;
        }

        // Before we build the nested components, set the WikiMacro instance in the PAGE context under a
        // unique name, so we can use a VariableMapper later and alias this as 'currentMacro'
        String macroPageVariableName = pluginMacro.getPageVariableName();
        log.debug("setting WikiMacro instance in PAGE context as variable named: " + macroPageVariableName);
        Contexts.getPageContext().set(macroPageVariableName, pluginMacro);

        // Whoever wants to do something before we finally build the XHTML template
        log.debug("firing VIEW_BUILD macro event");
        Events.instance().raiseEvent(pluginMacro.getCallbackEventName(WikiPluginMacro.CallbackEvent.VIEW_BUILD), pluginMacro);

        // This is where the magic happens... the UIWikiFormattedText component should have one child after that, a UIMacro
        includeMacroFacelet(pluginMacro, macroIncludePath, context, parent);

        // Now get the identifier of the newly created UIMacro instance and set it for future use
        Object macroId = parent.getAttributes().get(UIMacro.NEXT_MACRO);
        if (macroId != null) {
            pluginMacro.setClientId(macroId.toString());
            parent.getAttributes().remove(UIMacro.NEXT_MACRO);
        } else {
            // Best guess based wikiTextMacro renderer, needed during reRendering when we don't build the child
            // - only then is NEXT_MACRO set by the MacroComponentHandler
            macroId =
                    parent.getChildren().get(
                            parent.getChildCount() - 1
                    ).getClientId(context.getFacesContext());
            pluginMacro.setClientId(macroId.toString());
        }

        // Put an optional CSS include in the header of the wiki document we are rendering in.
        // (This needs to happen after the clientId is set, as CSS resource path rendering needs to
        // know if it occurs in a JSF request (clientId present) or not.
        includeMacroCSS(pluginMacro, parent);

        // We need to make the UIMacro child transient if we run in the wiki text editor preview. The reason
        // is complicated: If we don't make it transient, all value expressions inside the wikiTextMacro templates that
        // use 'currentMacro' will refer to the "old" saved ValueExpression and then of course to the "old"
        // VariableMapper. In other words: We need to make sure that the subtree is completely fresh every
        // time the wiki text preview is reRendered, otherwise we never get a 'currentMacro' binding updated.
        // This also means that VariableMapper is a completely useless construct, because it is basically an
        // alias that is evaluated just once.
        // Note: This means we can't click on form elements of any plugin/wikiTextMacro template in the preview. This
        // should be solved by not showing/ghosting any form elements during preview.
        if (enableTransientMacros) {
            log.debug("setting macro to transient rendering, not storing its state between renderings: " + pluginMacro);
            UIMacro uiMacro = (UIMacro) ComponentSupport.findChild(parent, macroId.toString());
            uiMacro.setTransient(true);
        }

        // Finally, pop the wikiTextMacro stack of the page, then transport the finished WikiMacro instance into
        // the UIWikiFormattedText component for rendering - we are done building the component tree at this
        // point.
        getPageMacroStack().pop();
        parent.addMacroWithTemplate(pluginMacro);

        // Well, we don't render anything here...
        return null;
    }

    private String getMacroIncludePath(WikiPluginMacro pluginMacro) {

        // Check singleton configuration
        if (pluginMacro.getMetadata().isRenderOptionSet(MacroPluginModule.RenderOption.SINGLETON) &&
                macrosFoundInWikiText.contains(pluginMacro.getName())) {
            log.warn("macro is a SINGLETON, can not be used twice in the same document area: " + pluginMacro);
            return null;
        } else {
            macrosFoundInWikiText.add(pluginMacro.getName());
        }

        // Check skin configuration
        String currentSkin = (String) Component.getInstance("skin");
        if (!pluginMacro.getMetadata().isAvailableForSkin(currentSkin)) {
            log.warn("macro is not available for skin '" + currentSkin + "': " + pluginMacro);
            return null;
        }

        // Try to get an XHTML template, our source for building nested components
        // Fun with slashes: For some reason, Facelets really needs a slash at the start, otherwise
        // it doesn't use my custom ResourceResolver...
        String includePath = "/" + pluginMacro.getMetadata().getPlugin().getPackageDefaultTemplatePath(pluginMacro.getName());
        URL faceletURL = ResourceLoader.instance().getResource(includePath);
        if (faceletURL == null) {
            log.debug("macro has no default include file, not building any components: " + pluginMacro);
            return null;
        } else {
            log.debug("using default template include as a resource from package: " + includePath);
        }

        return includePath;
    }

    private void includeMacroFacelet(WikiPluginMacro pluginMacro, String includePath, FaceletContext ctx, UIComponent parent) {
        VariableMapper orig = ctx.getVariableMapper();
        try {
            log.debug("setting 'currentMacro' as an EL variable, resolves dynamically to WikiMacro instance in PAGE context");
            ctx.setVariableMapper(new VariableMapperWrapper(orig));
            ctx.getVariableMapper().setVariable(
                    WikiPluginMacro.CURRENT_MACRO_EL_VARIABLE,
                    Expressions.instance().createValueExpression("#{" + pluginMacro.getPageVariableName() + "}").toUnifiedValueExpression()
            );

            log.debug("including macro facelets file from path: " + includePath);
            ctx.includeFacelet(parent, includePath);

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            ctx.setVariableMapper(orig);
        }
    }

    private void includeMacroCSS(WikiPluginMacro pluginMacro, UIComponent cmp) {

        String cssPath = "/" + pluginMacro.getMetadata().getPlugin().getPackageCSSPath() + "/" + pluginMacro.getName() + ".css";
        log.debug("trying to load CSS resource from classpath: " + cssPath);
        if (ResourceLoader.instance().getResource(cssPath) != null) {
            String cssRequestURIPath = pluginMacro.getRequestStylesheetPath() + "/" + pluginMacro.getName() + ".css";
            log.debug("including macro CSS file, rendering URI for document head: " + cssRequestURIPath);

            // Use Ajax4JSF loader, it can do what we want - add a CSS <link> to the HTML <head>
            HtmlLoadStyle style = new HtmlLoadStyle();
            style.setSrc(cssRequestURIPath);

            cmp.getChildren().add(style);
            // Clear these out in the next build phase
            ComponentSupport.markForDeletion(style);
        } else {
            log.debug("no CSS resource found for macro");
        }
    }

    private Stack<WikiTextMacro> getPageMacroStack() {
        if (Contexts.getPageContext().get(MACRO_STACK_PAGE_VARIABLE) == null) {
            log.debug("macro page stack is null, creating new stack for this page");
            Contexts.getPageContext().set(MACRO_STACK_PAGE_VARIABLE, new Stack<WikiTextMacro>());
        }
        return (Stack<WikiTextMacro>)Contexts.getPageContext().get(MACRO_STACK_PAGE_VARIABLE);
    }

    private boolean isMacroOnPageStack(WikiTextMacro macro) {
        Stack<WikiTextMacro> macroStack = getPageMacroStack();
        for (WikiTextMacro macroOnPageStack : macroStack) {
            if (macroOnPageStack.getName().equals(macro.getName())) return true;
        }
        return false;
    }

}
TOP

Related Classes of org.jboss.seam.wiki.core.wikitext.renderer.jsf.MacroIncludeTextRenderer

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.