001/* 002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Anahide Tchertchian 018 */ 019package org.nuxeo.ecm.webapp.resources; 020 021import java.util.ArrayList; 022import java.util.List; 023 024import javax.faces.component.UIComponent; 025import javax.faces.component.UIViewRoot; 026import javax.faces.context.FacesContext; 027import javax.faces.event.AbortProcessingException; 028import javax.faces.event.ComponentSystemEvent; 029import javax.faces.event.ComponentSystemEventListener; 030 031import org.apache.commons.lang.StringUtils; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 035import org.nuxeo.ecm.web.resources.api.ResourceType; 036import org.nuxeo.ecm.web.resources.jsf.PageResourceRenderer; 037import org.nuxeo.ecm.web.resources.jsf.ResourceBundleRenderer; 038import org.nuxeo.runtime.api.Framework; 039import org.nuxeo.runtime.services.config.ConfigurationService; 040 041/** 042 * Moves CSS files to the start of the head tag and reorders js resources. 043 * 044 * @since 7.10 045 */ 046public class NuxeoWebResourceDispatcher implements ComponentSystemEventListener { 047 048 private static final Log log = LogFactory.getLog(NuxeoWebResourceDispatcher.class); 049 050 protected static String TARGET_HEAD = "head"; 051 052 protected static String SLOT_HEAD_START = "headstart"; 053 054 private static String SLOT_BODY_START = "bodystart"; 055 056 private static String SLOT_BODY_END = "bodyend"; 057 058 private static String DEFER_JS_PROP = "nuxeo.jsf.deferJavaScriptLoading"; 059 060 public void processEvent(ComponentSystemEvent event) throws AbortProcessingException { 061 FacesContext ctx = FacesContext.getCurrentInstance(); 062 UIViewRoot root = ctx.getViewRoot(); 063 boolean ajaxRequest = ctx.getPartialViewContext().isAjaxRequest(); 064 if (ajaxRequest) { 065 // do not interfere with ajax scripts re-rendering logics 066 List<UIComponent> resources = root.getComponentResources(ctx, TARGET_HEAD); 067 String message = "Head resource %s on ajax request"; 068 for (UIComponent r : resources) { 069 logResourceInfo(r, message); 070 } 071 return; 072 } 073 List<UIComponent> cssResources = new ArrayList<UIComponent>(); 074 List<UIComponent> otherResources = new ArrayList<UIComponent>(); 075 List<UIComponent> resources = root.getComponentResources(ctx, TARGET_HEAD); 076 for (UIComponent r : resources) { 077 if (isCssResource(ctx, r)) { 078 cssResources.add(r); 079 } else { 080 otherResources.add(r); 081 } 082 } 083 084 moveResources(ctx, root, cssResources, TARGET_HEAD, SLOT_HEAD_START, 085 "Pushing head resource %s at the beggining of head tag"); 086 if (isDeferJavaScriptLoading()) { 087 moveResources(ctx, root, otherResources, TARGET_HEAD, SLOT_BODY_START, 088 "Pushing head resource %s at the beggining of body tag"); 089 } 090 } 091 092 protected void moveResources(FacesContext ctx, UIViewRoot root, List<UIComponent> resources, String removeFrom, 093 String addTo, String message) { 094 // push target resources 095 List<UIComponent> existing = new ArrayList<UIComponent>(root.getComponentResources(ctx, addTo)); 096 for (UIComponent r : resources) { 097 ComponentUtils.setRelocated(r); 098 root.removeComponentResource(ctx, r, removeFrom); 099 root.addComponentResource(ctx, r, addTo); 100 logResourceInfo(r, message); 101 } 102 // add them back again for head resources to be still before them 103 for (UIComponent r : existing) { 104 root.addComponentResource(ctx, r, addTo); 105 } 106 } 107 108 protected void logResourceInfo(UIComponent resource, String message) { 109 if (log.isDebugEnabled()) { 110 String name = getLogName(resource); 111 if (StringUtils.isBlank(name)) { 112 log.debug(String.format(message, resource)); 113 } else { 114 log.debug(String.format(message, name)); 115 } 116 } 117 } 118 119 protected String getLogName(UIComponent resource) { 120 String name = (String) resource.getAttributes().get("name"); 121 if (StringUtils.isBlank(name)) { 122 return (String) resource.getAttributes().get("src"); 123 } 124 return name; 125 } 126 127 protected boolean isCssResource(FacesContext ctx, UIComponent r) { 128 String rtype = r.getRendererType(); 129 if ("javax.faces.resource.Stylesheet".equals(rtype)) { 130 return true; 131 } 132 if (ResourceBundleRenderer.RENDERER_TYPE.equals(rtype) || PageResourceRenderer.RENDERER_TYPE.equals(rtype)) { 133 String type = (String) r.getAttributes().get("type"); 134 if (ResourceType.css.equals(type) || ResourceType.jsfcss.equals(type)) { 135 return true; 136 } 137 return false; 138 } 139 String name = (String) r.getAttributes().get("name"); 140 if (name == null) { 141 return false; 142 } 143 name = name.toLowerCase(); 144 if (name.contains(".css") || name.contains(".ecss")) { 145 return true; 146 } 147 return false; 148 } 149 150 public boolean isDeferJavaScriptLoading() { 151 ConfigurationService cs = Framework.getService(ConfigurationService.class); 152 return cs.isBooleanPropertyTrue(DEFER_JS_PROP); 153 } 154 155 public String getHeadStartTarget() { 156 return SLOT_HEAD_START; 157 } 158 159 public String getBodyStartTarget() { 160 return SLOT_BODY_START; 161 } 162 163 public String getBodyEndTarget() { 164 return SLOT_BODY_END; 165 } 166 167 public String getHeadJavaScriptTarget() { 168 return isDeferJavaScriptLoading() ? SLOT_BODY_END : SLOT_BODY_START; 169 } 170 171 public String getBodyJavaScriptTarget() { 172 return isDeferJavaScriptLoading() ? SLOT_BODY_END : null; 173 } 174 175}