/* * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Anahide Tchertchian */ package org.nuxeo.ecm.webapp.resources; import java.util.ArrayList; import java.util.List; import javax.faces.component.UIComponent; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import javax.faces.event.AbortProcessingException; import javax.faces.event.ComponentSystemEvent; import javax.faces.event.ComponentSystemEventListener; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; import org.nuxeo.ecm.web.resources.api.ResourceType; import org.nuxeo.ecm.web.resources.jsf.PageResourceRenderer; import org.nuxeo.ecm.web.resources.jsf.ResourceBundleRenderer; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.services.config.ConfigurationService; /** * Moves CSS files to the start of the head tag and reorders js resources. * * @since 7.10 */ public class NuxeoWebResourceDispatcher implements ComponentSystemEventListener { private static final Log log = LogFactory.getLog(NuxeoWebResourceDispatcher.class); protected static String TARGET_HEAD = "head"; protected static String SLOT_HEAD_START = "headstart"; private static String SLOT_BODY_START = "bodystart"; private static String SLOT_BODY_END = "bodyend"; private static String DEFER_JS_PROP = "nuxeo.jsf.deferJavaScriptLoading"; public void processEvent(ComponentSystemEvent event) throws AbortProcessingException { FacesContext ctx = FacesContext.getCurrentInstance(); UIViewRoot root = ctx.getViewRoot(); boolean ajaxRequest = ctx.getPartialViewContext().isAjaxRequest(); if (ajaxRequest) { // do not interfere with ajax scripts re-rendering logics List<UIComponent> resources = root.getComponentResources(ctx, TARGET_HEAD); String message = "Head resource %s on ajax request"; for (UIComponent r : resources) { logResourceInfo(r, message); } return; } List<UIComponent> cssResources = new ArrayList<UIComponent>(); List<UIComponent> otherResources = new ArrayList<UIComponent>(); List<UIComponent> resources = root.getComponentResources(ctx, TARGET_HEAD); for (UIComponent r : resources) { if (isCssResource(ctx, r)) { cssResources.add(r); } else { otherResources.add(r); } } moveResources(ctx, root, cssResources, TARGET_HEAD, SLOT_HEAD_START, "Pushing head resource %s at the beggining of head tag"); if (isDeferJavaScriptLoading()) { moveResources(ctx, root, otherResources, TARGET_HEAD, SLOT_BODY_START, "Pushing head resource %s at the beggining of body tag"); } } protected void moveResources(FacesContext ctx, UIViewRoot root, List<UIComponent> resources, String removeFrom, String addTo, String message) { // push target resources List<UIComponent> existing = new ArrayList<UIComponent>(root.getComponentResources(ctx, addTo)); for (UIComponent r : resources) { ComponentUtils.setRelocated(r); root.removeComponentResource(ctx, r, removeFrom); root.addComponentResource(ctx, r, addTo); logResourceInfo(r, message); } // add them back again for head resources to be still before them for (UIComponent r : existing) { root.addComponentResource(ctx, r, addTo); } } protected void logResourceInfo(UIComponent resource, String message) { if (log.isDebugEnabled()) { String name = getLogName(resource); if (StringUtils.isBlank(name)) { log.debug(String.format(message, resource)); } else { log.debug(String.format(message, name)); } } } protected String getLogName(UIComponent resource) { String name = (String) resource.getAttributes().get("name"); if (StringUtils.isBlank(name)) { return (String) resource.getAttributes().get("src"); } return name; } protected boolean isCssResource(FacesContext ctx, UIComponent r) { String rtype = r.getRendererType(); if ("javax.faces.resource.Stylesheet".equals(rtype)) { return true; } if (ResourceBundleRenderer.RENDERER_TYPE.equals(rtype) || PageResourceRenderer.RENDERER_TYPE.equals(rtype)) { String type = (String) r.getAttributes().get("type"); if (ResourceType.css.equals(type) || ResourceType.jsfcss.equals(type)) { return true; } return false; } String name = (String) r.getAttributes().get("name"); if (name == null) { return false; } name = name.toLowerCase(); if (name.contains(".css") || name.contains(".ecss")) { return true; } return false; } public boolean isDeferJavaScriptLoading() { ConfigurationService cs = Framework.getService(ConfigurationService.class); return cs.isBooleanPropertyTrue(DEFER_JS_PROP); } public String getHeadStartTarget() { return SLOT_HEAD_START; } public String getBodyStartTarget() { return SLOT_BODY_START; } public String getBodyEndTarget() { return SLOT_BODY_END; } public String getHeadJavaScriptTarget() { return isDeferJavaScriptLoading() ? SLOT_BODY_END : SLOT_BODY_START; } public String getBodyJavaScriptTarget() { return isDeferJavaScriptLoading() ? SLOT_BODY_END : null; } }