/*
* Copyright 2009 the original author or authors.
*
* 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 de.hybris.yfaces.context;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.render.ResponseStateManager;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import de.hybris.yfaces.YFacesException;
import de.hybris.yfaces.YManagedBean;
import de.hybris.yfaces.component.YFrame;
import de.hybris.yfaces.util.YFacesErrorHandler;
/**
* A context object whose scope and lifetime is bound to {@link HttpServletRequest}.
*
* @author Denny.Strietzbaum
*/
public abstract class YRequestContext {
private YPageContext pageContext = null;
private static final Logger log = Logger.getLogger(YRequestContext.class);
private static final String IS_FLASHBACK = YRequestContext.class.getName() + "_isFlashback";
public enum REQUEST_PHASE {
START_REQUEST, FORWARD_REQUEST, END_REQUEST
};
private REQUEST_PHASE currentPhase = REQUEST_PHASE.END_REQUEST;
private YSessionContext sessionContext = null;
private YFacesErrorHandler errorHandler = null;
private boolean isFlashback = false;
/**
* Constructor. Sets flashback property to true when previous {@link YRequestContext} instance
* was used to do a redirect with enabled flashback.
*/
public YRequestContext() {
isFlashback = FacesContext.getCurrentInstance().getExternalContext().getSessionMap()
.remove(IS_FLASHBACK) != null;
}
// TODO: move to appCtx
public YFacesErrorHandler getErrorHandler() {
return errorHandler;
}
protected void setErrorHandler(YFacesErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
/**
* Returns the current {@link YRequestContext} instance.
*
* @return {@link YRequestContext}
*/
public static YRequestContext getCurrentContext() {
return (YRequestContext) YApplicationContext.getApplicationContext().getBean(
YRequestContext.class.getSimpleName());
}
/**
* @return {@link YPageContext}
*/
public YPageContext getPageContext() {
return this.pageContext;
}
/**
* Sets the {@link YPageContext}
*
* @param pageContext
* {@link YPageContext} to set
*/
protected void setPageContext(YPageContext pageContext) {
this.pageContext = pageContext;
}
/**
* @return {@link YSessionContext}
*/
public YSessionContext getSessionContext() {
return sessionContext;
}
protected void setSessionContext(YSessionContext sessionContext) {
this.sessionContext = sessionContext;
}
/**
* Shortcut to {@link #redirect(String, boolean)} with disabled flashback.
*
* @param url
* target url
*/
public void redirect(String url) {
this.redirect(url, false);
}
/**
* Shortcut to {@link #redirect(String, boolean)} whereas URL is the servletpath.
*
* @param isFlash
* whether flashback shall be enabled
* @see HttpServletRequest#getServletPath()
*/
public void redirect(boolean isFlash) {
final String url = FacesContext.getCurrentInstance().getExternalContext()
.getRequestServletPath();
redirect(url, isFlash);
}
/**
* Shortcut to {@link #redirect(String, boolean)} whereas URL is taken from passed
* {@link YPageContext}
*
* @param page
* {@link YPageContext} to redirect to
* @param isFlash
* whether flashback shall be enabled
* @see YPageContext#getURL()
*/
public void redirect(YPageContext page, boolean isFlash) {
this.redirect(page.getURL(), isFlash);
}
/**
* Redirects to the passed URL. URLs starting with 'http' are treated absolute whereas URLs
* starting with a slash '/' are handled relative to the webapp root. All other URLs are handled
* relative to the current request URI.
*
* @param url
* target url.
* @param isFlash
* true when flash shall be enabled
*/
public void redirect(String url, boolean enableFlashback) {
if (url == null) {
throw new YFacesException("No URL specified", new NullPointerException());
}
final FacesContext fctx = FacesContext.getCurrentInstance();
final ExternalContext ectx = fctx.getExternalContext();
// when url is not absolute...
if (!url.startsWith("http")) {
// spec. accepts absolute as well as relative url
// relative path without leading slash is interpreted relatively to
// current request URI
// relative path with leading slash is interpreted relatively to
// context root
// but here a leading slash is interpreted relatively to
// webapplication root
if (url.startsWith("/")) {
url = ectx.getRequestContextPath() + url;
}
}
log.info("Redirecting to " + url);
try {
ectx.redirect(ectx.encodeResourceURL(url));
fctx.responseComplete();
} catch (IOException e) {
throw new YFacesException("Can't redirect to " + url, e);
}
//this.setFlash(isFlash);
if (enableFlashback) {
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(
IS_FLASHBACK, Boolean.TRUE);
}
}
/**
* Similar to a postback but can only be true when current request is of type GET and flashback
* was enabled in previous request.
*/
public boolean isFlashback() {
return this.isFlashback;
}
/**
* Shortcut for {@link ResponseStateManager#isPostback(FacesContext)}
*
* @return true when current request is a jsf postback
*/
public boolean isPostback() {
// true when a _JSF_ form was submitted
// (javax.faces.ViewState parameter is present at request map)
return FacesContext.getCurrentInstance().getRenderKit().getResponseStateManager()
.isPostback(FacesContext.getCurrentInstance());
}
/**
* Starts a new YPage request.<br/>
*
* @param viewId
*/
void startPageRequest(String viewId) {
this.currentPhase = REQUEST_PHASE.START_REQUEST;
// detect method
boolean isPostBack = YRequestContext.getCurrentContext().isPostback();
boolean isFlash = YRequestContext.getCurrentContext().isFlashback();
YConversationContext convCtx = getPageContext().getConversationContext();
// restore context information (mbeans) when
// a)POST (postback) or
// b)GET with enabled flash
if (isPostBack || isFlash) {
// iterate over all context pages...
Collection<YPageContext> pages = convCtx.getAllPages();
for (YPageContext page : pages) {
// ...and notify page for a new request (re-inject all
// frames/mbeans)
for (YFrame frame : page.getFrames()) {
((YManagedBean) frame).refreshBeanScope();
}
}
// force a one-time survive after a GET (redirect)
if (isFlash) {
if (isPostBack) {
throw new YFacesException("Illegal Navigationstate");
}
// must explicitly invoked for GET
this.switchPage(viewId);
}
}
// otherwise ...
else {
// ...reset context with new initialized page
String url = getViewURL(viewId, true);
YPageContext newPage = new YPageContext(convCtx, viewId, url);
convCtx.start(newPage);
}
}
/**
* Gets invoked before the new Page is processed.<br/>
* Invocation happens:<br/>
* a) for a POST: after INVOKE_APPLICATION and before RENDER_RESPONSE<br/>
* b) for a GET (flash=true): after RESTORE_VIEW and before RENDER_RESPONSE<br/>
*
* @param newViewId
*/
void switchPage(String newViewId) {
this.currentPhase = REQUEST_PHASE.FORWARD_REQUEST;
YConversationContext convCtx = getPageContext().getConversationContext();
// lookup whether newViewId matches on of context managed previous pages
// (browser backbutton, regular "back" navigation, etc. )
YPageContext previousPage = convCtx.getPage(newViewId);
// when no previous page is available (e.g. navigation to a new view)
// ...
if (previousPage == null) {
final String viewUrl = getViewURL(newViewId, false);
// ...and the context is prepared to have a next page...
YPageContext forwardPage = convCtx.getNextPage();
if (forwardPage != null) {
forwardPage.setURL(viewUrl);
forwardPage.setId(newViewId);
convCtx.forward(forwardPage);
}
// ...otherwise reset Conversation
else {
// ...initialize new context and new YPage
convCtx.start(new YPageContext(convCtx, newViewId, viewUrl));
}
}
// when a previous page is available (or current page)...
else {
// ...navigate to this page
convCtx.backward(previousPage);
// ...and start update mechanism
this.sessionContext.refresh();
}
}
/**
* Finishes the current Page request. The viewid has changed when an internal forward was
* accomplished <br/>
* This method gets called after RENDER_RESPONSE.
*
* @param viewId
*/
void finishPageRequest(String viewId) {
this.currentPhase = REQUEST_PHASE.END_REQUEST;
if (log.isDebugEnabled()) {
int i = 0;
YPageContext page = getPageContext();
YConversationContext convCtx = page.getConversationContext();
String id = convCtx.getId();
do {
log.debug(id + " Page(" + i++ + "):" + page.toString());
} while ((page = page.getPreviousPage()) != null);
}
}
/**
* Returns a URI starting with a slash and relative to the webapps context root for the
* requested view.
*
* @param viewId
* view to generate the URL for
* @return String
*/
private String getViewURL(String viewId, boolean addCurrentQueryParams) {
FacesContext fc = FacesContext.getCurrentInstance();
// request view url but without context path
String result = fc.getApplication().getViewHandler().getActionURL(fc, viewId);
result = result.substring(fc.getExternalContext().getRequestContextPath().length());
// optional append a query parameter string
// HttpServletRequest#getQueryString isn't used here as it is not
// available in an portlet
// environment and, more important, may return an incorrect string when
// urlrewriting is used.
if (addCurrentQueryParams) {
Map<String, String[]> values = FacesContext.getCurrentInstance().getExternalContext()
.getRequestParameterValuesMap();
if (!values.isEmpty()) {
String params = "?";
for (Map.Entry<String, String[]> entry : values.entrySet()) {
for (String value : entry.getValue()) {
params = params + entry.getKey() + "=" + value + ";";
}
}
result = result + params.substring(0, params.length() - 1);
}
}
return result;
}
public REQUEST_PHASE getRequestPhase() {
return this.currentPhase;
}
}