/*
* (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;
}
}