org.ajax4jsf.context.ViewResources.java Source code

Java tutorial

Introduction

Here is the source code for org.ajax4jsf.context.ViewResources.java

Source

/**
 * License Agreement.
 *
 *  JBoss RichFaces - Ajax4jsf Component Library
 *
 * Copyright (C) 2007  Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * 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.ajax4jsf.context;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.Renderer;

import org.ajax4jsf.component.QueueRegistry;
import org.ajax4jsf.io.SAXResponseWriter;
import org.ajax4jsf.renderkit.HeaderResourceProducer;
import org.ajax4jsf.renderkit.HeaderResourceProducer2;
import org.ajax4jsf.renderkit.ProducerContext;
import org.ajax4jsf.renderkit.ProducerContextImpl;
import org.ajax4jsf.renderkit.UserResourceRenderer;
import org.ajax4jsf.renderkit.UserResourceRenderer2;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.ajax4jsf.resource.InternetResource;
import org.ajax4jsf.resource.InternetResourceBase;
import org.ajax4jsf.resource.InternetResourceBuilder;
import org.ajax4jsf.resource.ResourceNotFoundException;
import org.ajax4jsf.resource.ResourceRenderer;
import org.ajax4jsf.resource.URIInternetResource;
import org.ajax4jsf.util.ELUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.richfaces.skin.Skin;
import org.richfaces.skin.SkinFactory;
import org.richfaces.skin.SkinNotFoundException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

/**
 * @author Nick Belaevski
 */

public class ViewResources {

    private static final String INIT_PARAMETER_PREFIX = "_init_parameter_";
    private static final Object NULL = new Object();

    private String scriptStrategy;

    private String styleStrategy;

    private boolean useStdControlsSkinning;

    private boolean useStdControlsSkinningClasses;

    private HeadResponseWriter componentWriter;

    private HeadResponseWriter userWriter;

    private RenderKit renderKit;

    private Node[] headNodes = null;

    private static final String EXTENDED_SKINNING_ON_NO_SCRIPTS_INFO_KEY = ViewResources.class.getName()
            + "EXTENDED_SKINNING_ON_NO_SCRIPTS_INFO_KEY";

    private static final InternetResource EXTENDED_SKINNING_ON_RESOURCE = new InternetResourceBase() {

        private final String RESOURCE_KEY = this.getClass().getName();

        @Override
        public void encode(FacesContext context, Object data) throws IOException {

            encode(context, data, Collections.EMPTY_MAP);
        }

        @Override
        public void encode(FacesContext context, Object data, Map<String, Object> attributes) throws IOException {

            Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
            if (requestMap.get(RESOURCE_KEY) == null) {
                ResponseWriter writer = context.getResponseWriter();
                writer.startElement(HTML.SCRIPT_ELEM, null);

                if (!attributes.containsKey(HTML.TYPE_ATTR)) {
                    writer.writeAttribute(HTML.TYPE_ATTR, "text/javascript", null);
                }

                for (Map.Entry<String, Object> entry : attributes.entrySet()) {
                    writer.writeAttribute(entry.getKey(), entry.getValue(), null);
                }

                writer.writeText("window.RICH_FACES_EXTENDED_SKINNING_ON=true;", null);

                writer.endElement(HTML.SCRIPT_ELEM);

                requestMap.put(RESOURCE_KEY, Boolean.TRUE);
            }
        }
    };

    public void setScriptStrategy(String scriptsStrategy) {
        this.scriptStrategy = scriptsStrategy;
    }

    public void setStyleStrategy(String stylesStrategy) {
        this.styleStrategy = stylesStrategy;
    }

    public void setUseStdControlsSkinning(boolean stdControlsSkinning) {
        this.useStdControlsSkinning = stdControlsSkinning;
    }

    public void setUseStdControlsSkinningClasses(boolean stdControlsSkinningClasses) {
        this.useStdControlsSkinningClasses = stdControlsSkinningClasses;
    }

    public void setExtendedSkinningAllowed(boolean extendedSkinningAllowed) {
        this.extendedSkinningAllowed = extendedSkinningAllowed;
    }

    private static String SCRIPT = HTML.SCRIPT_ELEM;
    private static String SCRIPT_UC = SCRIPT.toUpperCase(Locale.US);

    private static String SRC = HTML.src_ATTRIBUTE;
    private static String SRC_UC = SRC.toUpperCase(Locale.US);

    private void mergeHeadResourceNode(List<Node> nodes, Set<String> renderedScripts, Node node) {
        boolean shouldAdd = true;

        String nodeName = node.getNodeName();
        if (SCRIPT.equals(nodeName) || SCRIPT_UC.equals(nodeName)) {
            if (node.getFirstChild() == null) {
                //no text content etc.

                NamedNodeMap attributes = node.getAttributes();
                if (attributes != null) {
                    Node item = attributes.getNamedItem(SRC);
                    if (item == null) {
                        attributes.getNamedItem(SRC_UC);
                    }

                    if (item != null) {
                        String src = item.getNodeValue();
                        if (src != null) {
                            if (renderedScripts.contains(src)) {
                                shouldAdd = false;
                            } else {
                                renderedScripts.add(src);
                            }
                        }
                    }
                }
            }
        }

        if (shouldAdd) {
            nodes.add(node);
        }
    }

    private Node[] mergeHeadResourceNodes() {
        List<Node> result = new ArrayList<Node>();

        Set<String> scripts = new HashSet<String>();

        for (Node node : componentWriter.getNodes()) {
            mergeHeadResourceNode(result, scripts, node);
        }

        for (Node node : userWriter.getNodes()) {
            mergeHeadResourceNode(result, scripts, node);
        }

        return result.toArray(new Node[result.size()]);
    }

    public Node[] getHeadEvents() {
        if (headNodes == null) {
            headNodes = mergeHeadResourceNodes();
        }

        return headNodes;
    }

    private static final Log log = LogFactory.getLog(ViewResources.class);

    //todo

    private static final Map<String, Object> EXTENDED_SKINNING = new HashMap<String, Object>(1);

    static {
        EXTENDED_SKINNING.put(HTML.media_ATTRIBUTE, "rich-extended-skinning");
    }

    public static final String SKINNING_STYLES_PATH = "/org/richfaces/renderkit/html/css/";

    public static final String QUEUE_SCRIPT_RESOURCE = "org.ajax4jsf.renderkit.html.scripts.QueueScript";

    private boolean extendedSkinningAllowed;

    private boolean processScripts;

    private boolean processStyles;

    private boolean useSkinning;

    private InternetResourceBuilder resourceBuilder;

    private boolean ajaxRequest;

    private ProducerContext producerContext;

    public static final String COMPONENT_RESOURCE_LINK_CLASS = "component";

    public static final String USER_RESOURCE_LINK_CLASS = "user";

    class HeadResponseWriter extends SAXResponseWriter {

        public Node[] getNodes() {
            return ((ResponseWriterContentHandler) getXmlConsumer()).getNodes();
        }

        public HeadResponseWriter(String linkClass) {
            super(new ResponseWriterContentHandler(linkClass));

        }
    }

    private void encodeResources(FacesContext context, ResourceRenderer renderer, Set<String> set)
            throws IOException {

        if (set != null) {
            URIInternetResource resourceImpl = new URIInternetResource();

            for (String uri : set) {
                resourceImpl.setUri(uri);
                renderer.encode(resourceImpl, context, null);
            }
        }
    }

    private boolean encodeSkinningResources(FacesContext context, InternetResourceBuilder resourceBuilder)
            throws IOException, FacesException {
        String resourceSuffix = null;

        if (useStdControlsSkinning) {
            if (useStdControlsSkinningClasses) {
                resourceSuffix = "_both.xcss";
            } else {
                resourceSuffix = ".xcss";
            }
        } else {
            if (useStdControlsSkinningClasses) {
                resourceSuffix = "_classes.xcss";
            } else {
                //no resources
            }
        }

        if (resourceSuffix != null) {
            resourceBuilder.createResource(this, SKINNING_STYLES_PATH.concat("basic").concat(resourceSuffix))
                    .encode(context, null);

            if (extendedSkinningAllowed) {
                resourceBuilder.createResource(this, SKINNING_STYLES_PATH.concat("extended").concat(resourceSuffix))
                        .encode(context, null, EXTENDED_SKINNING);
            }

            return true;
        }

        return false;
    }

    protected void processComponent(FacesContext context, UIComponent component)
            throws IOException, FacesException {
        Renderer renderer = getRenderer(context, component);
        if (null != renderer) {
            ResponseWriter oldResponseWriter = context.getResponseWriter();
            try {
                if ((processScripts || processStyles) && (renderer instanceof HeaderResourceProducer2
                        || renderer instanceof HeaderResourceProducer)) {

                    context.setResponseWriter(componentWriter);

                    if (renderer instanceof HeaderResourceProducer2) {
                        HeaderResourceProducer2 producer = (HeaderResourceProducer2) renderer;

                        if (producerContext == null) {
                            producerContext = new ProducerContextImpl(processScripts, processStyles);
                        }

                        producer.encodeToHead(context, component, producerContext);
                    } else if (renderer instanceof HeaderResourceProducer) {
                        HeaderResourceProducer producer = (HeaderResourceProducer) renderer;

                        if (processScripts) {
                            encodeResources(context, resourceBuilder.getScriptRenderer(),
                                    producer.getHeaderScripts(context, component));
                        }
                        if (processStyles) {
                            encodeResources(context, resourceBuilder.getStyleRenderer(),
                                    producer.getHeaderStyles(context, component));
                        }
                    }
                } else if (renderer instanceof UserResourceRenderer2) {
                    context.setResponseWriter(userWriter);

                    UserResourceRenderer2 producer = (UserResourceRenderer2) renderer;
                    producer.encodeToHead(context, component);
                } else if (renderer instanceof UserResourceRenderer) {
                    context.setResponseWriter(userWriter);

                    UserResourceRenderer producer = (UserResourceRenderer) renderer;

                    encodeResources(context, resourceBuilder.getScriptRenderer(),
                            producer.getHeaderScripts(context, component));

                    encodeResources(context, resourceBuilder.getStyleRenderer(),
                            producer.getHeaderStyles(context, component));
                }
            } finally {
                context.setResponseWriter(oldResponseWriter);
            }
        }

    }

    /**
     * Find renderer for given component.
     * 
     * @param context
     * @param comp
     * @param renderKit
     * @return
     */
    private Renderer getRenderer(FacesContext context, UIComponent comp) {

        String rendererType = comp.getRendererType();
        if (rendererType != null) {
            return (renderKit.getRenderer(comp.getFamily(), rendererType));
        } else {
            return (null);
        }
    }

    protected void traverse(FacesContext context, UIComponent component) throws IOException, FacesException {
        if (component != null) {
            processComponent(context, component);

            if (component.getChildCount() > 0) {
                for (UIComponent child : component.getChildren()) {
                    traverse(context, child);
                }
            }

            if (component.getFacetCount() > 0) {
                for (UIComponent child : component.getFacets().values()) {
                    traverse(context, child);
                }
            }
        }
    }

    public void processHeadResources(FacesContext context) throws FacesException {

        RenderKitFactory rkFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
        renderKit = rkFactory.getRenderKit(context, context.getViewRoot().getRenderKitId());

        boolean scriptsOff = false;

        processStyles = true;
        processScripts = true;
        useSkinning = false;

        ajaxRequest = AjaxContext.getCurrentInstance(context).isAjaxRequest(context);

        if (log.isDebugEnabled()) {
            log.debug("Process component tree for collect used scripts and styles");
        }

        String skinStyleSheetUri = null;
        String skinExtendedStyleSheetUri = null;

        Skin skin = null;
        try {
            skin = SkinFactory.getInstance().getSkin(context);
            // For a "NULL" skin, do not collect components stylesheets
            if ("false".equals(skin.getParameter(context, Skin.loadStyleSheets))) {
                processStyles = false;
            }
            // Set default style sheet for current skin.
            skinStyleSheetUri = (String) skin.getParameter(context, Skin.generalStyleSheet);
            // Set default style sheet for current skin.
            skinExtendedStyleSheetUri = (String) skin.getParameter(context, Skin.extendedStyleSheet);
        } catch (SkinNotFoundException e) {
            if (log.isWarnEnabled()) {
                log.warn("Current Skin is not found", e);
            }
        }

        resourceBuilder = InternetResourceBuilder.getInstance();

        ResponseWriter oldResponseWriter = context.getResponseWriter();

        componentWriter = new HeadResponseWriter("component");
        userWriter = new HeadResponseWriter("user");

        try {
            componentWriter.startDocument();
            userWriter.startDocument();

            context.setResponseWriter(componentWriter);

            // Check init parameters for a resources processing.
            if (null != scriptStrategy) {
                if (InternetResourceBuilder.LOAD_NONE.equals(scriptStrategy)) {
                    scriptsOff = true;
                    processScripts = false;
                } else if (InternetResourceBuilder.LOAD_ALL.equals(scriptStrategy)) {
                    processScripts = false;
                    // For an "ALL" strategy, it is not necessary to load scripts in the ajax request
                    if (!ajaxRequest) {
                        try {
                            resourceBuilder.createResource(this, InternetResourceBuilder.COMMON_FRAMEWORK_SCRIPT)
                                    .encode(context, null);
                            resourceBuilder.createResource(this, InternetResourceBuilder.COMMON_UI_SCRIPT)
                                    .encode(context, null);

                        } catch (ResourceNotFoundException e) {
                            if (log.isWarnEnabled()) {
                                log.warn("No aggregated javaScript library found " + e.getMessage());
                            }
                        }

                    }
                }
            }

            if (InternetResourceBuilder.LOAD_NONE.equals(styleStrategy)) {
                processStyles = false;
            } else if (InternetResourceBuilder.LOAD_ALL.equals(styleStrategy)) {
                processStyles = false;
                // For an "ALL" strategy, it is not necessary to load styles
                // in the ajax request
                if (!ajaxRequest) {

                    try {
                        useSkinning = encodeSkinningResources(context, resourceBuilder);

                        resourceBuilder.createResource(this, InternetResourceBuilder.COMMON_STYLE).encode(context,
                                null);

                    } catch (ResourceNotFoundException e) {
                        if (log.isWarnEnabled()) {
                            log.warn("No stylesheet found " + e.getMessage());
                        }
                    }

                }
            } else {
                useSkinning = encodeSkinningResources(context, resourceBuilder);
            }

            //traverse components
            traverse(context, context.getViewRoot());

            context.setResponseWriter(componentWriter);

            QueueRegistry queueRegistry = QueueRegistry.getInstance(context);
            if (Boolean.valueOf(getInitParameterValue(context, "org.richfaces.queue.global.enabled"))) {
                queueRegistry.setShouldCreateDefaultGlobalQueue();
            }

            if (queueRegistry.hasQueuesToEncode()) {
                InternetResource queueScriptResource = resourceBuilder.getResource(QUEUE_SCRIPT_RESOURCE);
                queueScriptResource.encode(context, null);
            }

            // Append Skin StyleSheet after a
            if (null != skinStyleSheetUri) {
                String resourceURL = context.getApplication().getViewHandler().getResourceURL(context,
                        skinStyleSheetUri);

                URIInternetResource resourceImpl = new URIInternetResource();
                resourceImpl.setUri(resourceURL);
                resourceImpl.setRenderer(resourceBuilder.getStyleRenderer());
                resourceImpl.encode(context, null);

                useSkinning = true;
            }

            if (null != skinExtendedStyleSheetUri && extendedSkinningAllowed) {
                String resourceURL = context.getApplication().getViewHandler().getResourceURL(context,
                        skinExtendedStyleSheetUri);

                URIInternetResource resourceImpl = new URIInternetResource();
                resourceImpl.setUri(resourceURL);
                resourceImpl.setRenderer(resourceBuilder.getStyleRenderer());
                resourceImpl.encode(context, null, EXTENDED_SKINNING);

                useSkinning = true;
            }

            if (useSkinning && extendedSkinningAllowed) {
                if (!ajaxRequest) {
                    if (!scriptsOff) {
                        //skinning levels aren't dynamic, page-level setting cannot be changed 
                        //by AJAX request
                        EXTENDED_SKINNING_ON_RESOURCE.encode(context, null);
                    } else {

                        Map<String, Object> applicationMap = context.getExternalContext().getApplicationMap();
                        if (applicationMap.get(EXTENDED_SKINNING_ON_NO_SCRIPTS_INFO_KEY) == null) {
                            //do it once per application life - strategies can be changed dynamically
                            ResponseWriter writer = context.getResponseWriter();
                            try {
                                StringWriter stringWriter = new StringWriter();

                                if (oldResponseWriter != null) {
                                    context.setResponseWriter(oldResponseWriter.cloneWithWriter(stringWriter));
                                } else {
                                    context.setResponseWriter(this.renderKit.createResponseWriter(stringWriter,
                                            "text/html", "US-ASCII"));
                                }

                                EXTENDED_SKINNING_ON_RESOURCE.encode(context, null);

                                stringWriter.flush();

                                if (log.isInfoEnabled()) {
                                    log.info(
                                            "Extended skinning is on and NONE scripts loading strategy was detected. "
                                                    + "Do not forget that one of "
                                                    + InternetResourceBuilder.SKINNING_SCRIPT + " or "
                                                    + InternetResourceBuilder.COMMON_FRAMEWORK_SCRIPT
                                                    + " resources should be presented "
                                                    + "on the page together with the following code: \n"
                                                    + stringWriter.getBuffer().toString()
                                                    + "\nfor extended level of skinning to work.");
                                }
                            } finally {
                                if (writer != null) {
                                    context.setResponseWriter(writer);
                                }
                            }

                            applicationMap.put(EXTENDED_SKINNING_ON_NO_SCRIPTS_INFO_KEY, Boolean.TRUE);
                        }
                    }
                }

                if (processScripts) {
                    InternetResource resource = resourceBuilder.createResource(null,
                            InternetResourceBuilder.SKINNING_SCRIPT);

                    resource.encode(context, null);
                }
            }

            componentWriter.endDocument();
            userWriter.endDocument();
        } catch (IOException e) {
            throw new FacesException(e.getLocalizedMessage(), e);
        } finally {
            if (oldResponseWriter != null) {
                context.setResponseWriter(oldResponseWriter);
            }
        }
    }

    private static String getInitParameterValue(FacesContext context, String parameterName) {

        String key = INIT_PARAMETER_PREFIX + parameterName;

        ExternalContext externalContext = context.getExternalContext();
        Map<String, Object> applicationMap = externalContext.getApplicationMap();
        Object mutex = externalContext.getRequest();
        Object parameterValue = null;

        synchronized (mutex) {
            parameterValue = applicationMap.get(key);

            if (parameterValue == null) {

                String initParameter = externalContext.getInitParameter(parameterName);
                if (initParameter != null) {

                    if (ELUtils.isValueReference(initParameter)) {
                        Application application = context.getApplication();
                        ExpressionFactory expressionFactory = application.getExpressionFactory();

                        parameterValue = expressionFactory.createValueExpression(context.getELContext(),
                                initParameter, String.class);
                    } else {
                        parameterValue = initParameter;
                    }

                } else {
                    parameterValue = NULL;
                }

                applicationMap.put(key, parameterValue);
            }
        }

        return evaluate(context, parameterValue);
    }

    private static String evaluate(FacesContext context, Object parameterValue) {
        if (parameterValue == NULL || parameterValue == null) {
            return null;
        } else if (parameterValue instanceof ValueExpression) {
            ValueExpression expression = (ValueExpression) parameterValue;

            return (String) expression.getValue(context.getELContext());
        } else {
            return parameterValue.toString();
        }
    }

    public void initialize(FacesContext context) {
        boolean extendedSkinningAllowed = true;
        String skinningLevel = getInitParameterValue(context, InternetResourceBuilder.CONTROL_SKINNING_LEVEL);
        if (skinningLevel != null && skinningLevel.length() > 0) {
            if (InternetResourceBuilder.BASIC.equals(skinningLevel)) {
                extendedSkinningAllowed = false;
            } else if (!InternetResourceBuilder.EXTENDED.equals(skinningLevel)) {
                throw new IllegalArgumentException("Value: " + skinningLevel + " of "
                        + InternetResourceBuilder.CONTROL_SKINNING_LEVEL + " init parameter is invalid! Only "
                        + InternetResourceBuilder.EXTENDED + ", " + InternetResourceBuilder.BASIC + " can be used");
            }
        }

        this.setExtendedSkinningAllowed(extendedSkinningAllowed);

        this.setScriptStrategy(getInitParameterValue(context, InternetResourceBuilder.LOAD_SCRIPT_STRATEGY_PARAM));

        boolean useStdControlsSkinning = false;

        String stdControlsSkinning = getInitParameterValue(context,
                InternetResourceBuilder.STD_CONTROLS_SKINNING_PARAM);
        if (stdControlsSkinning != null) {
            useStdControlsSkinning = InternetResourceBuilder.ENABLE.equals(stdControlsSkinning);
        }

        this.setUseStdControlsSkinning(useStdControlsSkinning);

        boolean useStdControlsSkinningClasses = true;

        String stdControlsSkinningClasses = getInitParameterValue(context,
                InternetResourceBuilder.STD_CONTROLS_SKINNING_CLASSES_PARAM);
        if (stdControlsSkinningClasses != null) {
            useStdControlsSkinningClasses = InternetResourceBuilder.ENABLE.equals(stdControlsSkinningClasses);
        }

        this.setUseStdControlsSkinningClasses(useStdControlsSkinningClasses);

        this.setStyleStrategy(getInitParameterValue(context, InternetResourceBuilder.LOAD_STYLE_STRATEGY_PARAM));

    }

}