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