/**
* Copyright (c) 2000-2017 Liferay, Inc. All rights reserved.
*
* 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.
*/
package com.liferay.faces.util.render.internal;
import java.io.IOException;
import java.util.Map;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.ValueHolder;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.event.ComponentSystemEventListener;
import javax.faces.event.ListenerFor;
import javax.faces.event.PostAddToViewEvent;
import javax.faces.render.Renderer;
import javax.faces.render.RendererWrapper;
import com.liferay.faces.util.application.ResourceVerifier;
import com.liferay.faces.util.application.ResourceVerifierFactory;
import com.liferay.faces.util.logging.Logger;
import com.liferay.faces.util.logging.LoggerFactory;
/**
* This class wraps resources and uses {@link ResourceVerifier#isDependencySatisfied(FacesContext, UIComponent)} to
* determine if the resources should be rendered.
*
* @author Kyle Stiemann
*/
@ListenerFor(systemEventClass = PostAddToViewEvent.class)
public class ResourceRendererUtilImpl extends RendererWrapper implements ComponentSystemEventListener, StateHolder {
// Logger
private static final Logger logger = LoggerFactory.getLogger(ResourceRendererUtilImpl.class);
// Private Members
private boolean transientFlag;
private Renderer wrappedRenderer;
/**
* This zero-arg constructor is required by the {@link javax.faces.component.StateHolderSaver} class during the
* RESTORE_VIEW phase of the JSF lifecycle. The reason why this class is involved in restoring state is because the
* {@link javax.faces.component.UIComponent.ComponentSystemEventListenerAdapter} implements {@link
* javax.faces.component.StateHolder} and will attempt to restore the state of any class in the restored view that
* implements {@link ComponentSystemEventListener}. It does this first by instantiating the class with a zero-arg
* constructor, and then calls the {@link #restoreState(FacesContext, Object)} method.
*/
public ResourceRendererUtilImpl() {
// Defer initialization of wrappedRenderer until restoreState(FacesContext, Object) is called.
}
public ResourceRendererUtilImpl(Renderer wrappedRenderer) {
this.wrappedRenderer = wrappedRenderer;
}
@Override
public void encodeBegin(FacesContext facesContext, UIComponent uiComponent) throws IOException {
ExternalContext externalContext = facesContext.getExternalContext();
ResourceVerifier resourceVerifier = ResourceVerifierFactory.getResourceVerifierInstance(externalContext);
if (resourceVerifier.isDependencySatisfied(facesContext, uiComponent)) {
if (logger.isDebugEnabled()) {
Map<String, Object> componentResourceAttributes = uiComponent.getAttributes();
logger.debug(
"Resource dependency already satisfied: name=[{0}] library=[{1}] rendererType=[{2}] value=[{3}] className=[{4}]",
componentResourceAttributes.get("name"), componentResourceAttributes.get("library"),
uiComponent.getRendererType(), getComponentValue(uiComponent), uiComponent.getClass().getName());
}
}
else {
super.encodeBegin(facesContext, uiComponent);
}
}
@Override
public void encodeChildren(FacesContext facesContext, UIComponent uiComponent) throws IOException {
ExternalContext externalContext = facesContext.getExternalContext();
ResourceVerifier resourceVerifier = ResourceVerifierFactory.getResourceVerifierInstance(externalContext);
if (resourceVerifier.isDependencySatisfied(facesContext, uiComponent)) {
if (logger.isDebugEnabled()) {
Map<String, Object> componentResourceAttributes = uiComponent.getAttributes();
logger.debug(
"Resource dependency already satisfied: name=[{0}] library=[{1}] rendererType=[{2}] value=[{3}] className=[{4}]",
componentResourceAttributes.get("name"), componentResourceAttributes.get("library"),
uiComponent.getRendererType(), getComponentValue(uiComponent), uiComponent.getClass().getName());
}
}
else {
super.encodeChildren(facesContext, uiComponent);
}
}
@Override
public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException {
ExternalContext externalContext = facesContext.getExternalContext();
ResourceVerifier resourceVerifier = ResourceVerifierFactory.getResourceVerifierInstance(externalContext);
if (resourceVerifier.isDependencySatisfied(facesContext, uiComponent)) {
if (logger.isDebugEnabled()) {
Map<String, Object> componentResourceAttributes = uiComponent.getAttributes();
logger.debug(
"Resource dependency already satisfied: name=[{0}] library=[{1}] rendererType=[{2}] value=[{3}] className=[{4}]",
componentResourceAttributes.get("name"), componentResourceAttributes.get("library"),
uiComponent.getRendererType(), getComponentValue(uiComponent), uiComponent.getClass().getName());
}
}
else {
super.encodeEnd(facesContext, uiComponent);
}
}
@Override
public Renderer getWrapped() {
return wrappedRenderer;
}
@Override
public boolean isTransient() {
return transientFlag;
}
/**
* Since the Mojarra {@link com.sun.faces.renderkit.html_basic.ScriptStyleBaseRenderer} class implements {@link
* ComponentSystemEventListener}, this class must implement that interface too, since this is a wrapper type of
* class. Mojarra uses this method to intercept {@link PostAddToViewEvent} in order to add script and link resources
* to the head (if the target attribute has a value of "head").
*/
@Override
public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
// If the wrapped renderer has the ability to listen to component system events, then invoke the event
// processing on the wrapped renderer. This is necessary when wrapping the Mojarra ScriptRenderer or
// StylesheetRenderer because they extend ScriptStyleBaseRenderer which attempts to add the component
// associated with the specified event as a resource on the view root.
if (wrappedRenderer instanceof ComponentSystemEventListener) {
ComponentSystemEventListener wrappedListener = (ComponentSystemEventListener) wrappedRenderer;
wrappedListener.processEvent(event);
}
else {
logger.debug("Wrapped renderer=[{0}] does not implement ComponentSystemEventListener", wrappedRenderer);
}
}
@Override
public void restoreState(FacesContext facesContext, Object state) {
if (wrappedRenderer == null) {
try {
String wrappedRendererClassName = (String) state;
Class<?> wrappedRendererClass = Class.forName(wrappedRendererClassName);
wrappedRenderer = (Renderer) wrappedRendererClass.newInstance();
}
catch (Exception e) {
logger.error(e);
}
}
}
@Override
public Object saveState(FacesContext facesContext) {
return wrappedRenderer.getClass().getName();
}
@Override
public void setTransient(boolean transientFlag) {
this.transientFlag = transientFlag;
}
private String getComponentValue(UIComponent componentResource) {
String componentResourceValue = null;
if (componentResource instanceof ValueHolder) {
ValueHolder valueHolder = (ValueHolder) componentResource;
Object valueAsObject = valueHolder.getValue();
if (valueAsObject != null) {
componentResourceValue = valueAsObject.toString();
}
}
return componentResourceValue;
}
}