/**
* 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.FactoryFinder;
import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
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.faces.render.RenderKitFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.ajax4jsf.Messages;
import org.ajax4jsf.application.AjaxViewHandler;
import org.ajax4jsf.renderkit.AjaxContainerRenderer;
import org.ajax4jsf.renderkit.AjaxRendererUtils;
import org.ajax4jsf.renderkit.RendererUtils;
import org.ajax4jsf.renderkit.RendererUtils.HTML;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* 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 SERVLET_ERROR_EXCEPTION_ATTRIBUTE = "javax.servlet.error.exception";
private static final Log log = LogFactory.getLog(AjaxContext.class);
Set<String> ajaxAreasToRender = new HashSet<String>();
Set<String> ajaxAreasToProcess = null;
Set<String> ajaxRenderedAreas = new LinkedHashSet<String>();
boolean ajaxRequest = false;
boolean selfRender = false;
Integer viewSequence = new Integer(1);
String submittedRegionClientId = null;
String ajaxSingleClientId = null;
ViewIdHolder viewIdHolder = null;
Map<String, Object> responseDataMap = new HashMap<String, Object> ();
Map<String, Object> commonAjaxParameters = new HashMap<String, Object> ();
Object oncomplete = null;
public void release() {
ajaxAreasToRender = new HashSet<String>();
ajaxAreasToProcess = null;
ajaxRenderedAreas = new LinkedHashSet<String>();
ajaxRequest = false;
selfRender = false;
viewSequence = new Integer(1);
submittedRegionClientId = null;
viewIdHolder = null;
responseDataMap = new HashMap<String, Object>();
commonAjaxParameters = new HashMap<String, Object>();
}
/* (non-Javadoc)
* @see org.ajax4jsf.context.AjaxContext#decode(javax.faces.context.FacesContext)
*/
@Override
public void decode(FacesContext context) {
ExternalContext externalContext = context.getExternalContext();
if (null == externalContext.getRequestMap().get(
SERVLET_ERROR_EXCEPTION_ATTRIBUTE)) {
Map<String, String> requestParameterMap = externalContext
.getRequestParameterMap();
String ajaxRegionId = requestParameterMap
.get(AjaxContainerRenderer.AJAX_PARAMETER_NAME);
setSubmittedRegionClientId(ajaxRegionId);
setAjaxRequest(null != ajaxRegionId);
setAjaxSingleClientId(requestParameterMap.get(AjaxRendererUtils.AJAX_SINGLE_PARAMETER_NAME));
} else {
// Error page is always serviced as non-ajax.
setAjaxRequest(false);
setSubmittedRegionClientId(null);
setAjaxSingleClientId(null);
}
}
/**
* @param context
* @throws AbortProcessingException
*/
public void renderAjax(FacesContext context) throws FacesException {
if (log.isDebugEnabled()) {
log.debug(Messages.getMessage(Messages.RENDER_AJAX_REQUEST,
getSubmittedRegionClientId()));
}
try {
// Just in case...
setSelfRender(true);
setAjaxRequest(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();
// 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;
servletWriter = getWriter(extContext);
ResponseWriter writer = renderKit.createResponseWriter(
servletWriter, null, encoding);
context.setResponseWriter(writer);
// make response
writer.startDocument();
encodeAjaxBegin(context);
context.getViewRoot().encodeAll(context);
saveViewState(context);
encodeAjaxEnd(context);
writer.endDocument();
writer.flush();
writer.close();
servletWriter.close();
// Save tree state.
} catch (IOException e) {
throw new FacesException(Messages.getMessage(
Messages.RENDERING_AJAX_REGION_ERROR, getSubmittedRegionClientId()), e);
} finally {
context.responseComplete();
// component.setRendererType(defaultRenderer);
}
}
/**
* Encode declaration for AJAX response. Render <html><body>
*
* @param context
* @throws IOException
*/
public void encodeAjaxBegin(FacesContext context)
throws IOException {
UIViewRoot viewRoot = context.getViewRoot();
// AjaxContainer ajax = (AjaxContainer) component;
ResponseWriter out = context.getResponseWriter();
// DebugUtils.traceView("ViewRoot in AJAX Page encode begin");
out.startElement(HTML.HTML_ELEMENT, viewRoot);
Locale locale = viewRoot.getLocale();
out.writeAttribute(HTML.lang_ATTRIBUTE, locale.toString(), "lang");
out.startElement(HTML.BODY_ELEMENT, viewRoot);
}
/**
* End encoding of AJAX response. Render tag with included areas and close
* </body></html>
*
* @param context
* @throws IOException
*/
public void encodeAjaxEnd(FacesContext context)
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);
}
public void saveViewState(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
StateManager stateManager = context.getApplication().getStateManager();
Object serializedView = stateManager
.saveView(context);
if (null != serializedView && null != writer) {
StringWriter bufWriter = new StringWriter();
ResponseWriter tempWriter;
tempWriter = writer.cloneWithWriter(bufWriter);
context.setResponseWriter(tempWriter);
stateManager.writeState(context, serializedView);
tempWriter.flush();
if (bufWriter.getBuffer().length() > 0) {
context.getExternalContext().getRequestMap().put(
AjaxViewHandler.SERIALIZED_STATE_KEY,
bufWriter.toString());
}
context.setResponseWriter(writer);
}
}
protected RenderKit getRenderKit(FacesContext context) {
RenderKit renderKit = context.getRenderKit();
if (null == renderKit) {
String renderKitId = context.getApplication().getViewHandler().calculateRenderKitId(context);
RenderKitFactory factory = (RenderKitFactory) FactoryFinder
.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
renderKit = factory.getRenderKit(context, renderKitId);
}
return renderKit;
}
/**
* @return Returns the ajaxRequest.
*/
public boolean isAjaxRequest() {
return ajaxRequest;
}
/**
* @param ajaxRequest
* The ajaxRequest to set.
*/
public void setAjaxRequest(boolean ajaxRequest) {
this.ajaxRequest = ajaxRequest;
}
/**
* @return Returns the ajaxAreasToRender.
*/
public Set<String> getAjaxAreasToRender() {
return this.ajaxAreasToRender;
}
/**
* @return the ajaxAreasToProcess
*/
@Override
public Set<String> getAjaxAreasToProcess() {
return ajaxAreasToProcess;
}
/**
* @param ajaxAreasToProcess the ajaxAreasToProcess to set
*/
@Override
public void setAjaxAreasToProcess(Set<String> ajaxAreasToProcess) {
this.ajaxAreasToProcess = ajaxAreasToProcess;
}
/**
* Add affected regions's ID to ajaxView component.
*
* @param component
*/
public void addRegionsFromComponent(UIComponent component) {
// First step - find parent ajax view
Set<String> 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<String> iter = ajaxRegions.iterator(); iter.hasNext();) {
String id = iter.next().toString();
ajaxAreasToRender.add(convertId(component, id));
}
}
}
/**
* Add IDs of regions to process
*
* @see org.ajax4jsf.context.AjaxContext#addAreasToProcessFromComponent(javax.faces.component.UIComponent)
*/
@Override
public void addAreasToProcessFromComponent(FacesContext context, UIComponent component) {
RendererUtils rendererUtils = RendererUtils.getInstance();
Set<String> areasToProcess = AjaxRendererUtils.getAjaxAreasToProcess(component);
if (areasToProcess != null) {
Set<String> convertedAreaIds = new HashSet<String>();
for (String areaId : areasToProcess) {
UIComponent areaComponent = rendererUtils.findComponentFor(component, areaId);
if (areaComponent != null) {
convertedAreaIds.add(areaComponent.getClientId(context));
} else {
convertedAreaIds.add(areaId);
}
}
if (this.ajaxAreasToProcess == null) {
this.ajaxAreasToProcess = convertedAreaIds;
} else {
this.ajaxAreasToProcess.addAll(convertedAreaIds);
}
}
}
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<String> 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() {
return this.submittedRegionClientId;
}
/**
* @param submittedClientId
* The submittedClientId to set.
*/
@Override
public void setSubmittedRegionClientId(String submittedClientId) {
this.submittedRegionClientId = submittedClientId;
}
/**
* @return the ajaxSingleClientId
*/
@Override
public String getAjaxSingleClientId() {
return ajaxSingleClientId;
}
/**
* @param ajaxSingleClientId the ajaxSingleClientId to set
*/
@Override
public void setAjaxSingleClientId(String ajaxSingleClientId) {
this.ajaxSingleClientId = ajaxSingleClientId;
}
/**
* @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<String, Object> 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(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);
// HACK - check for a Jboss PortletBridge implementation. If present, append DirectLink attribute to url.
// TODO - how to detect portlet application ?
if (null != context.getExternalContext().getApplicationMap().get(
"org.jboss.portletbridge.application.PortletStateHolder")) {
// 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<String, Object> 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;
}
}