/**
* License Agreement.
*
* Rich Faces - Natural Ajax for Java Server Faces (JSF)
*
* 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.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
import javax.faces.application.StateManager.SerializedView;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.AbortProcessingException;
import javax.faces.render.RenderKit;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.ajax4jsf.Messages;
import org.ajax4jsf.application.AjaxViewHandler;
import org.ajax4jsf.component.AjaxContainer;
import org.ajax4jsf.component.AjaxViewRoot;
import org.ajax4jsf.renderkit.AjaxContainerRenderer;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.ajax4jsf.renderkit.RendererUtils;
import org.ajax4jsf.renderkit.UserResourceRenderer;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.ajax4jsf.resource.InternetResourceBuilder;
import org.ajax4jsf.webapp.BaseFilter;
import org.ajax4jsf.webapp.FilterServletResponseWrapper;
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;
/**
* This class incapsulated
*
* @author asmirnov@exadel.com (latest modification by $Author: alexsmirnov $)
* @version $Revision: 1.1.2.7 $ $Date: 2007/02/08 19:07:16 $
*
*/
public class AjaxContextImpl extends AjaxContext {
public static final String RESOURCES_PROCESSED = "org.ajax4jsf.framework.HEADER_PROCESSED";
private static final Log log = LogFactory.getLog(AjaxContext.class);
private static ComponentInvoker invoker;
private static Map contextClasses = new HashMap();
Set ajaxAreasToRender = new HashSet();
Set ajaxRenderedAreas = new LinkedHashSet();
boolean ajaxRequest = false;
boolean ajaxRequestSet = false;
boolean selfRender = false;
Integer viewSequence = new Integer(1);
String submittedRegionClientId = null;
boolean submittedRegionSet = false;
ViewIdHolder viewIdHolder = null;
Map responseDataMap = new HashMap();
Map commonAjaxParameters = new HashMap();
Object oncomplete = null;
static {
try {
// Attempt to create JSF1.2 specific invoker.
invoker = new JsfOneOneInvoker();
} catch (Exception e) {
invoker = new JsfOneOneInvoker();
}
}
public void release() {
ajaxAreasToRender = new HashSet();
ajaxRenderedAreas = new LinkedHashSet();
ajaxRequest = false;
ajaxRequestSet = false;
selfRender = false;
viewSequence = new Integer(1);
submittedRegionClientId = null;
submittedRegionSet = false;
viewIdHolder = null;
responseDataMap = new HashMap();
commonAjaxParameters = new HashMap();
}
/**
* @param root
* @param context
* @param callback
* @param regionId
* @return
* @see org.ajax4jsf.context.JsfOneOneInvoker#invokeOnComponent(javax.faces.component.UIComponent,
* javax.faces.context.FacesContext,
* org.ajax4jsf.context.InvokerCallback, java.lang.String)
*/
public static boolean invokeOnComponent(UIComponent root,
FacesContext context, InvokerCallback callback, String regionId) {
return invoker.invokeOnComponent(root, context, callback, regionId);
}
/**
* @param viewRoot
* @param context
* @param callback
* @see org.ajax4jsf.context.JsfOneOneInvoker#invokeOnRegionOrRoot(org.ajax4jsf.component.AjaxViewRoot,
* javax.faces.context.FacesContext,
* org.ajax4jsf.context.InvokerCallback)
*/
public static void invokeOnRegionOrRoot(AjaxViewRoot viewRoot,
FacesContext context, InvokerCallback callback) {
invoker.invokeOnRegionOrRoot(viewRoot, context, callback);
}
private InvokerCallback _ajaxInvoker = new InvokerCallback() {
public void invoke(FacesContext context, UIComponent component) {
if (component instanceof AjaxContainer) {
AjaxContainer ajax = (AjaxContainer) component;
renderAjaxRegion(context, component, true);
} else {
// Container not found, use Root for encode.
renderAjaxRegion(context, context.getViewRoot(), true);
}
}
public void invokeRoot(FacesContext context) {
renderAjaxRegion(context, context.getViewRoot(), true);
}
};
public void renderSubmittedAjaxRegion(FacesContext context) {
renderSubmittedAjaxRegion(context, true);
}
public void renderSubmittedAjaxRegion(FacesContext context,
final boolean useFilterWriter) {
InvokerCallback ajaxInvoker = new InvokerCallback() {
public void invoke(FacesContext context, UIComponent component) {
if (component instanceof AjaxContainer) {
renderAjaxRegion(context, component, useFilterWriter);
} else {
// Container not found, use Root for encode.
renderAjaxRegion(context, context.getViewRoot(),
useFilterWriter);
}
}
public void invokeRoot(FacesContext context) {
renderAjaxRegion(context, context.getViewRoot(),
useFilterWriter);
}
};
if (!invokeOnComponent(context.getViewRoot(), context, ajaxInvoker,
getSubmittedRegionClientId(context))) {
renderAjaxRegion(context, context.getViewRoot(), useFilterWriter);
}
}
/**
* @param context
* @param useFilterWriter
* TODO
* @throws AbortProcessingException
*/
public void renderAjaxRegion(FacesContext context, UIComponent component,
boolean useFilterWriter) throws FacesException {
if (log.isDebugEnabled()) {
log.debug(Messages.getMessage(Messages.RENDER_AJAX_REQUEST,
component.getId()));
}
try {
setSelfRender(true);
// create response writer.
ExternalContext extContext = context.getExternalContext();
RenderKit renderKit = context.getRenderKit();
String encoding;
// Depends if we talk about servlets, portlets, ...
if (extContext.getRequest() instanceof ServletRequest) {
ServletRequest request = (ServletRequest) extContext
.getRequest();
ServletResponse response = (ServletResponse) extContext
.getResponse();
// HACK - bypass MyFaces ( and other ) extensions filter.
// Setup encoding and content type
String contentType = "text/xml";
// get the encoding - must be setup by faces context or filter.
encoding = request.getCharacterEncoding();
if (encoding == null) {
encoding = "UTF-8";
}
response.setContentType(contentType + ";charset=" + encoding);
} else
encoding = "UTF-8";
PrintWriter servletWriter;
if (useFilterWriter
&& extContext.getRequestMap().containsKey(
BaseFilter.RESPONSE_WRAPPER_ATTRIBUTE)) {
// HACK - Special case for MyFaces, since <f:view don't call
// encode methods,
// encode response as for self-rendered region directly to
// filter response wrpper.
// to avoid exceptions, inform wrapper to ignore illegal states
// for getWriter/Stream.
ServletResponse servletResponse = (ServletResponse) extContext
.getRequestMap().get(
BaseFilter.RESPONSE_WRAPPER_ATTRIBUTE);
servletResponse.resetBuffer();
servletWriter = servletResponse.getWriter();
((FilterServletResponseWrapper) servletResponse)
.setUseNullStream(true);
} else {
servletWriter = getWriter(extContext);
}
ResponseWriter writer = renderKit.createResponseWriter(
servletWriter, null, encoding);
context.setResponseWriter(writer);
// make response
writer.startDocument();
encodeAjaxBegin(context, component);
component.encodeBegin(context);
((AjaxContainer) component).encodeAjax(context);
component.encodeEnd(context);
saveViewState(context);
encodeAjaxEnd(context, component);
writer.endDocument();
writer.flush();
writer.close();
servletWriter.close();
// Save tree state.
} catch (IOException e) {
throw new FacesException(Messages.getMessage(
Messages.RENDERING_AJAX_REGION_ERROR, component
.getClientId(context)), e);
} finally {
context.responseComplete();
// component.setRendererType(defaultRenderer);
}
}
/**
* Encode declaration for AJAX response. Render <html><body>
*
* @param context
* @param component
* @throws IOException
*/
public void encodeAjaxBegin(FacesContext context, UIComponent component)
throws IOException {
// AjaxContainer ajax = (AjaxContainer) component;
ResponseWriter out = context.getResponseWriter();
// DebugUtils.traceView("ViewRoot in AJAX Page encode begin");
out.startElement(HTML.HTML_ELEMENT, component);
// TODO - html attributes. lang - from current locale ?
Locale locale = context.getViewRoot().getLocale();
out.writeAttribute(HTML.lang_ATTRIBUTE, locale.toString(), "lang");
out.startElement(HTML.BODY_ELEMENT, component);
}
/**
* End encoding of AJAX response. Render tag with included areas and close
* </body></html>
*
* @param context
* @param component
* @throws IOException
*/
public void encodeAjaxEnd(FacesContext context, UIComponent component)
throws IOException {
// AjaxContainer ajax = (AjaxContainer) component;
ResponseWriter out = context.getResponseWriter();
// DebugUtils.traceView("ViewRoot in AJAX Page encode begin");
out.endElement(HTML.BODY_ELEMENT);
out.endElement(HTML.HTML_ELEMENT);
}
/**
* @param context
* @param root
* @throws FacesException
*/
public void processHeadResources(FacesContext context)
throws FacesException {
ExternalContext externalContext = context.getExternalContext();
Map requestMap = externalContext.getRequestMap();
if (!Boolean.TRUE.equals(requestMap.get(RESOURCES_PROCESSED))) {
if (null != requestMap.get(BaseFilter.RESPONSE_WRAPPER_ATTRIBUTE)) {
if (log.isDebugEnabled()) {
log
.debug("Process component tree for collect used scripts and styles");
}
UIViewRoot root = context.getViewRoot();
ViewResources viewResources = new ViewResources();
try {
Skin skin = SkinFactory.getInstance().getSkin(
context);
// Set default style sheet for current skin.
String styleSheetUri = (String) skin.getParameter(context,
Skin.generalStyleSheet);
if(null != styleSheetUri){
String resourceURL = context.getApplication()
.getViewHandler().getResourceURL(context,
styleSheetUri);
viewResources.addStyle(resourceURL);
}
// For a "NULL" skin, do not collect components stylesheets
if("false".equals(skin.getParameter(context, Skin.loadStyleSheets))){
viewResources.setProcessStyles(false);
}
} catch (SkinNotFoundException e) {
log.warn("Current Skin is not found", e);
}
InternetResourceBuilder internetResourceBuilder = InternetResourceBuilder.getInstance();
// Check init parameters for a resources processing.
String scriptStrategy = externalContext.getInitParameter(InternetResourceBuilder.LOAD_SCRIPT_STRATEGY_PARAM);
if(null != scriptStrategy){
if(InternetResourceBuilder.LOAD_NONE.equals(scriptStrategy)){
viewResources.setProcessScripts(false);
} else if (InternetResourceBuilder.LOAD_ALL.equals(scriptStrategy)) {
viewResources.setProcessScripts(false);
viewResources.addScript(internetResourceBuilder.createResource(this, InternetResourceBuilder.COMMON_FRAMEWORK_SCRIPT).getUri(context, null));
viewResources.addScript(internetResourceBuilder.createResource(this, InternetResourceBuilder.COMMON_UI_SCRIPT).getUri(context, null));
}
}
String styleStrategy = externalContext.getInitParameter(InternetResourceBuilder.LOAD_STYLE_STRATEGY_PARAM);
if(null != styleStrategy){
if(InternetResourceBuilder.LOAD_NONE.equals(styleStrategy)){
viewResources.setProcessStyles(false);
} else if (InternetResourceBuilder.LOAD_ALL.equals(styleStrategy)) {
viewResources.setProcessStyles(false);
viewResources.addStyle(internetResourceBuilder.createResource(this, InternetResourceBuilder.COMMON_STYLE).getUri(context, null));
}
}
viewResources.collect(context);
Set scripts = viewResources.getScripts();
if (scripts.size() > 0) {
if (log.isDebugEnabled()) {
StringBuffer buff = new StringBuffer(
"Scripts for insert into head : \n");
for (Iterator iter = scripts.iterator(); iter.hasNext();) {
String script = (String) iter.next();
buff.append(script).append("\n");
}
log.debug(buff.toString());
}
requestMap.put(SCRIPTS_PARAMETER, scripts);
}
Set styles = viewResources.getStyles();
if (styles.size() > 0) {
if (log.isDebugEnabled()) {
StringBuffer buff = new StringBuffer(
"Styles for insert into head : \n");
for (Iterator iter = styles.iterator(); iter.hasNext();) {
String style = (String) iter.next();
buff.append(style).append("\n");
}
log.debug(buff.toString());
}
requestMap.put(STYLES_PARAMETER, styles);
}
// Mark as processed.
requestMap.put(RESOURCES_PROCESSED, Boolean.TRUE);
// Save viewId for a parser selection
requestMap.put(AjaxViewHandler.VIEW_ID_KEY, root.getViewId());
}
}
}
public void saveViewState(FacesContext context) throws IOException {
// TODO - for facelets environment, we need to remove transient
// components.
try {
Application.class.getMethod("getExpressionFactory", null);
} catch (NoSuchMethodException e) {
// JSF 1.1 !
}
ResponseWriter writer = context.getResponseWriter();
StateManager stateManager = context.getApplication().getStateManager();
SerializedView serializedView = stateManager
.saveSerializedView(context);
if (null != writer && null != serializedView) {
StringWriter bufWriter = new StringWriter();
ResponseWriter cloneWithWriter = writer.cloneWithWriter(bufWriter);
context.setResponseWriter(cloneWithWriter);
stateManager.writeState(context, serializedView);
cloneWithWriter.flush();
if (bufWriter.getBuffer().length() > 0) {
context.getExternalContext().getRequestMap().put(
AjaxViewHandler.SERIALIZED_STATE_KEY,
bufWriter.toString());
}
// Restore original writer.
context.setResponseWriter(writer);
}
}
/**
* @return Returns the ajaxRequest.
*/
public boolean isAjaxRequest() {
return isAjaxRequest(FacesContext.getCurrentInstance());
}
/**
* @return Returns the ajaxRequest.
*/
public boolean isAjaxRequest(FacesContext context) {
if (!this.ajaxRequestSet) {
ajaxRequest = null != getSubmittedRegionClientId(context);
ajaxRequestSet = true;
}
return ajaxRequest;
}
/**
* @param ajaxRequest
* The ajaxRequest to set.
*/
public void setAjaxRequest(boolean ajaxRequest) {
this.ajaxRequest = ajaxRequest;
this.ajaxRequestSet = true;
}
/**
* @return Returns the ajaxAreasToRender.
*/
public Set getAjaxAreasToRender() {
return this.ajaxAreasToRender;
}
/**
* Add affected regions's ID to ajaxView component.
*
* @param component
*/
public void addRegionsFromComponent(UIComponent component) {
// First step - find parent ajax view
Set ajaxRegions = AjaxRendererUtils.getAjaxAreas(component);
// if (ajaxRegions == null){
// FacesContext context = FacesContext.getCurrentInstance();
// ajaxRegions = AjaxRendererUtils.getAbsoluteId(context,component);
// }
if (log.isDebugEnabled()) {
log.debug(Messages.getMessage(Messages.INVOKE_AJAX_REGION_LISTENER,
component.getId()));
}
if (ajaxRegions != null) {
for (Iterator iter = ajaxRegions.iterator(); iter.hasNext();) {
String id = iter.next().toString();
ajaxAreasToRender.add(convertId(component, id));
}
}
}
public void addComponentToAjaxRender(UIComponent component) {
this.ajaxAreasToRender.add(AjaxRendererUtils.getAbsoluteId(component));
}
public void addComponentToAjaxRender(UIComponent base, String id) {
this.ajaxAreasToRender.add(convertId(base, id));
}
/**
* Test for relative id of target components. Attempt convert to absolute.
* For use as argument for
* {@link RendererUtils#findComponentFor(UIComponent, String)}
*
* @param component
* @param id
* @return
*/
private String convertId(UIComponent component, String id) {
if (id.charAt(0) == NamingContainer.SEPARATOR_CHAR) {
return id;
}
if (null == component) {
throw new NullPointerException(
"Base component for search re-rendered compnnent is null");
}
UIComponent target = RendererUtils.getInstance().
findComponentFor(component, id);
if (null != target) {
return AjaxRendererUtils.getAbsoluteId(target);
}
log.warn("Target component for id " + id + " not found");
return id;
}
/**
* @return Returns the ajaxRenderedAreas.
*/
public Set getAjaxRenderedAreas() {
return ajaxRenderedAreas;
}
public void addRenderedArea(String id) {
ajaxRenderedAreas.add(id);
}
public boolean removeRenderedArea(String id) {
return ajaxRenderedAreas.remove(id);
}
/**
* @return Returns the submittedClientId.
*/
public String getSubmittedRegionClientId(FacesContext context) {
if (!this.submittedRegionSet) {
ExternalContext externalContext = context.getExternalContext();
if (null == externalContext.getRequestMap().get(
"javax.servlet.error.exception")) {
Map requestParameterMap = externalContext
.getRequestParameterMap();
this.submittedRegionClientId = (String) requestParameterMap
.get(AjaxContainerRenderer.AJAX_PARAMETER_NAME);
} else {
// Error page, always parsed as non-ajax request.
this.submittedRegionClientId = null;
}
this.submittedRegionSet = true;
if (!this.ajaxRequestSet) {
setAjaxRequest(this.submittedRegionClientId != null);
}
}
return this.submittedRegionClientId;
}
/**
* @param submittedClientId
* The submittedClientId to set.
*/
public void setSubmittedRegionClientId(String submittedClientId) {
this.submittedRegionClientId = submittedClientId;
this.submittedRegionSet = true;
}
/**
* @return Returns the selfRender.
*/
public boolean isSelfRender() {
return selfRender;
}
/**
* @param selfRender
* The selfRender to set.
*/
public void setSelfRender(boolean selfRender) {
this.selfRender = selfRender;
}
/**
* @return the vievIdHolder
*/
public ViewIdHolder getViewIdHolder() {
return viewIdHolder;
}
/**
* @param viewIdHolder
* the vievIdHolder to set
*/
public void setViewIdHolder(ViewIdHolder viewIdHolder) {
this.viewIdHolder = viewIdHolder;
}
/**
* @return the responseData
*/
public Object getResponseData() {
return responseDataMap.get(RESPONSE_DATA_KEY);
}
/**
* @param responseData
* the responseData to set
*/
public void setResponseData(Object responseData) {
this.responseDataMap.put(RESPONSE_DATA_KEY, responseData);
}
/**
* @return the responseDataMap
*/
public Map getResponseDataMap() {
return responseDataMap;
}
/**
* Gives back the writer of a Response object.
*
* @param extContext
* The external context.
* @return The writer of the response.
* @throws FacesException
* If the response object has no getWriter() method.
*/
protected PrintWriter getWriter(ExternalContext extContext)
throws FacesException {
PrintWriter writer = null;
Object response = extContext.getResponse();
try {
Method gW = response.getClass()
.getMethod("getWriter", new Class[0]);
writer = (PrintWriter) gW.invoke(response, new Object[0]);
} catch (Exception e) {
throw new FacesException(e);
}
return writer;
}
public String getAjaxActionURL() {
return getAjaxActionURL(FacesContext.getCurrentInstance());
}
public String getAjaxActionURL(FacesContext context) {
// Check arguments
if (null == context) {
throw new NullPointerException(
"Faces context for build AJAX Action URL is null");
}
UIViewRoot viewRoot = context.getViewRoot();
if (null == viewRoot) {
throw new NullPointerException(
"Faces view tree for build AJAX Action URL is null");
}
String viewId = viewRoot.getViewId();
if (null == viewId) {
throw new NullPointerException(
"View id for build AJAX Action URL is null");
}
if (!viewId.startsWith("/")) {
throw new IllegalArgumentException(
"Illegal view Id for build AJAX Action URL: " + viewId);
}
ViewHandler viewHandler = context.getApplication().getViewHandler();
String actionURL = viewHandler.getActionURL(context, viewId);
// Mark Ajax action url as transparent with jsf-portlet bridge.
actionURL = actionURL + ((actionURL.lastIndexOf('?')>0)?"&":"?")+"javax.portlet.faces.DirectLink=true";
return context.getExternalContext().encodeActionURL(
actionURL);
}
/**
* @return the commonAjaxParameters
*/
public Map getCommonAjaxParameters() {
return commonAjaxParameters;
}
/**
* @return the oncomplete
*/
public Object getOncomplete() {
return oncomplete;
}
/**
* @param oncomplete the oncomplete to set
*/
public void setOncomplete(Object oncomplete) {
this.oncomplete = oncomplete;
}
}