blob: 47a4bd45cea572ddde0ef79b2836a0ebb5b7c95a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.empire.jsf2.pages;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.application.NavigationHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import org.apache.empire.commons.StringUtils;
import org.apache.empire.db.DBDatabase;
import org.apache.empire.db.DBRowSet;
import org.apache.empire.exceptions.EmpireException;
import org.apache.empire.exceptions.InternalException;
import org.apache.empire.exceptions.ItemNotFoundException;
import org.apache.empire.exceptions.MiscellaneousErrorException;
import org.apache.empire.jsf2.app.FacesUtils;
import org.apache.empire.jsf2.app.TextResolver;
import org.apache.empire.jsf2.app.WebApplication;
import org.apache.empire.jsf2.utils.ParameterMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class Page implements Serializable
{
private static final long serialVersionUID = 1L;
public static final String SESSION_MESSAGE = "PAGE_SESSION_MESSAGE";
private static final Logger log = LoggerFactory.getLogger(Page.class);
private String action = null;
private boolean initialized = false;
private PageDefinition pageDefinition = null;
private List<PageElement> pageElements = null;
protected Page()
{
if (log.isDebugEnabled())
{ String name = this.getClass().getSimpleName();
Page.log.debug("PageBean {} created.", name);
}
}
public String getPageName()
{
return (pageDefinition != null ? pageDefinition.getPageBeanName() : "{" + getClass().getSimpleName() + "}");
}
public String getName()
{
String className = pageDefinition.getPageBeanClass().getName();
int lastDot = className.lastIndexOf(".");
String name = className.substring(lastDot + 1);
return name;
}
public boolean isInitialized()
{
return initialized;
}
public String getAction()
{
if (this.action==null)
return null;
// Generate key
ParameterMap pm = FacesUtils.getParameterMap(FacesUtils.getContext());
String actionParam = (pm!=null ? pm.encodeString(action) : action);
return actionParam;
}
public void setAction(String actionParam)
{
if (!initialized)
Page.log.debug("Setting PageBean action {} for bean {}.", action, getPageName());
else
Page.log.trace("Re-setting PageBeanAction {} for bean {}.", action, getPageName());
// actionParam
if (StringUtils.isEmpty(actionParam))
return;
// Set action from param
this.action = PageDefinition.decodeActionParam(actionParam);
if (this.action==null)
throw new ItemNotFoundException(actionParam);
}
public PageDefinition getPageDefinition()
{
return pageDefinition;
}
public void setPageDefinition(PageDefinition pageDefinition)
{
this.pageDefinition = pageDefinition;
}
public PageDefinition getParentPage()
{
return pageDefinition.getParent();
}
public void preRenderPage(FacesContext context)
{
if (this.initialized)
{
// PageBean.log.error("PageBean {} is already initialized.", name());
try
{
Page.log.debug("PageBean {} is already initialized. Calling doRefresh().", getPageName());
doRefresh();
}
catch (Exception e)
{
logAndHandleActionException("doRefresh", e);
}
return; // already Initialized
}
// Check access
try
{
checkPageAccess();
// redirected?
if (context.getResponseComplete())
return;
}
catch (Exception e)
{
logAndHandleActionException("checkAccess", e);
// redirected?
if (context.getResponseComplete())
return;
// Oops, not redirected yet?
if (getParentPage()!=null)
navigateTo(getParentOutcome(true));
// Done
return;
}
// Initialize
this.initialized = true;
// String value of "null"?
if (this.action!=null && "null".equals(this.action))
{ log.warn("Invalid action name 'null' for {}", getClass().getName());
this.action = null;
}
// Execute Action
if (this.action != null)
{ try
{ // Process action
log.info("Processing action {} on {}.", String.valueOf(action), getPageName());
Method method = getClass().getMethod(action);
Object result = method.invoke(this);
if (result != null)
{
String outcome = result.toString();
// Retrieve the NavigationHandler instance..
NavigationHandler navHandler = context.getApplication().getNavigationHandler();
// Invoke nav handling..
navHandler.handleNavigation(context, action, outcome);
// Trigger a switch to Render Response if needed
context.renderResponse();
return;
}
restoreSessionMessage();
}
catch (NoSuchMethodException nsme)
{
logAndHandleActionException(action, nsme);
}
catch (Exception e)
{
logAndHandleActionException(action, e.getCause());
}
finally
{
// Clear action
this.action = null; // Page.INVALID_ACTION;
}
}
else
{ // call default Action
try
{
Page.log.debug("Initializing PageBean {}. Calling doInit()", getPageName());
doInit();
restoreSessionMessage();
}
catch (Exception e)
{
logAndHandleActionException("doInit", e);
}
}
}
public boolean isHasMessages()
{
Iterator<FacesMessage> fmi = FacesContext.getCurrentInstance().getMessages();
return fmi.hasNext();
}
protected void checkPageAccess()
{
/* Throw exception if User has no Access */
}
private void restoreSessionMessage()
{
// Restore Session Error Message
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
Map<String, Object> map = ec.getSessionMap();
if (map.containsKey(SESSION_MESSAGE))
{
FacesMessage errorMsg = (FacesMessage) map.get(SESSION_MESSAGE);
FacesContext.getCurrentInstance().addMessage(getPageName(), errorMsg);
map.remove(SESSION_MESSAGE);
}
}
private void logAndHandleActionException(String action, Throwable e)
{
String msg = "Failed to perform action " + action + " on " + getPageName();
// Message
Page.log.error(msg, e);
if (!handleActionError(action, e))
{ // Not handled. Throw again
if (e instanceof EmpireException)
throw ((EmpireException)e);
else
throw new InternalException(e);
}
}
protected void setSessionMessage(FacesMessage facesMsg)
{
// Set Session Message
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.getSessionMap().put(SESSION_MESSAGE, facesMsg);
}
protected boolean handleActionError(String action, Throwable e)
{
// Set Faces Message
String msg = getErrorMessage(e);
String detail = extractErrorMessageDetail(action, e, 1);
log.error(msg + "\r\n" + detail);
FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, detail);
setSessionMessage(facesMsg);
// Return to parent page
PageDefinition parentPage = getParentPage();
if (parentPage == null)
{ FacesContext.getCurrentInstance().addMessage(getPageName(), facesMsg);
return false;
}
// redirect
navigateTo(parentPage.getRedirect());
return true;
}
protected void setErrorMessage(Throwable e)
{
String msg = getErrorMessage(e);
String detail = extractErrorMessageDetail(action, e, 1);
if (log.isDebugEnabled())
log.debug(msg + "\r\n" + detail, e);
FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, detail);
FacesContext.getCurrentInstance().addMessage(getPageName(), facesMsg);
}
protected String getErrorMessage(Throwable e)
{ // Wrap Exception
if (!(e instanceof EmpireException))
e = new InternalException(e);
// get message
return getTextResolver().getExceptionMessage((Exception)e);
}
protected String extractErrorMessageDetail(String action, Throwable e, int stackTraceElements)
{
StringBuilder b = new StringBuilder();
b.append("Error performing action '");
b.append(action);
b.append("' on page ");
b.append(getPageName());
b.append(": ");
b.append(e.toString());
b.append("\r\nat:");
StackTraceElement[] stack = e.getStackTrace();
int len = (stack.length>stackTraceElements) ? stackTraceElements : stack.length;
for (int i=0; i<len; i++)
{
b.append(stack[i].toString());
b.append("\r\n");
}
return b.toString();
}
/**
* navigates to the desired page. Depending on the page outcome provided this is either a forward or a redirect.
* @param outcome the destination page to navigate to
*/
protected void navigateTo(PageOutcome outcome)
{
if (log.isDebugEnabled())
log.debug("Redirecting from page {} to page {}.", getPageName(), outcome.toString());
// Return to Parent
FacesContext context = FacesContext.getCurrentInstance();
// Retrieve the NavigationHandler instance..
NavigationHandler navHandler = context.getApplication().getNavigationHandler();
// Invoke nav handling..
navHandler.handleNavigation(context, action, outcome.toString());
// Trigger a switch to Render Response if needed
context.renderResponse();
}
/**
* adds a page element to this page
* DO NOT CALL yourself, this method is called from the PageElement constructor!
*
* @param element
*/
protected void registerPageElement(PageElement element)
{
if (pageElements == null)
pageElements = new ArrayList<PageElement>(1);
// register now
if (pageElements.contains(element) == false)
pageElements.add(element);
else
log.warn("PageElement {} was registered twice!", element.getPropertyName());
}
/**
* Helper methods for parent outcome
*
* @param action
* @param redirect
* @return the parent outcome string
*/
protected PageOutcome getParentOutcome(String action, boolean redirect)
{
PageDefinition parentPage = getParentPage();
if (parentPage == null)
throw new MiscellaneousErrorException("No Parent Page defined for " + getPageName());
if (redirect)
return parentPage.getRedirect(action);
else
return parentPage.getOutcome(action);
}
protected PageOutcome getParentOutcome(boolean redirect)
{
return getParentOutcome(null, redirect);
}
/**
* return a connection for a particular database
* @param db the database for which to obtain a connection
* @return the connection for the given database
*/
public Connection getConnection(DBDatabase db)
{
WebApplication app = FacesUtils.getWebApplication();
return app.getConnectionForRequest(FacesUtils.getContext(), db);
}
public Object[] getKeyFromParam(DBRowSet rowset, String idParam)
{
FacesContext fc = FacesUtils.getContext();
return FacesUtils.getParameterMap(fc).getKey(rowset, idParam);
}
public Object[] getKeyFromParam(PageDefinition page, DBRowSet rowset, String idParam)
{
FacesContext fc = FacesUtils.getContext();
return FacesUtils.getParameterMap(fc).getKey(page, rowset, idParam);
}
public String getIdParamForKey(DBRowSet rowset, Object[] key)
{
FacesContext fc = FacesUtils.getContext();
return FacesUtils.getParameterMap(fc).put(rowset, key);
}
public String getIdParamForKey(PageDefinition page, DBRowSet rowset, Object[] key)
{
FacesContext fc = FacesUtils.getContext();
return FacesUtils.getParameterMap(fc).put(page, rowset, key);
}
public void addJavascriptCall(String function)
{
// Add Call
FacesContext fc = FacesUtils.getContext();
WebApplication app = FacesUtils.getWebApplication();
app.addJavascriptCall(fc, function);
}
/* Page Resources */
/**
* Adds an object required for resource handling, to the page resource map.
* <pre>
* Since resource requests are not attached to a view, they cannot access page properties via expression language like this
* #{page.someProperty}
* Instead, the page should add properties that are required to the "pageResources"-map. This map is held on the session, and cleared when the page changes.
* In order to access such page resources via expression language use
* #{pageResources.someProperty}
* </pre>
* @param name the name of the resource
* @param resource the resource
*/
public void addPageResource(String name, Object resource)
{
Map<String, Object> prm = FacesUtils.getPageResourceMap(FacesUtils.getContext());
prm.put(name, this);
}
/**
* Returns the page resource object previously added by addPageResource(...)
* @param name the name of the resource
* @return resource the resource
*/
public Object getPageResource(String name)
{
Map<String, Object> prm = FacesUtils.getPageResourceMap(FacesUtils.getContext());
return prm.get(name);
}
/* Default Init Method */
public void doInit()
{
if (pageElements != null)
{ // Init Page Elements
for (PageElement pe : pageElements)
doInitElement(pe);
}
}
public void doRefresh()
{
if (pageElements != null)
{ // Refresh Page Elements
for (PageElement pe : pageElements)
doRefreshElement(pe);
}
}
/**
* called by doInit() to initialize a particular page element
* @param pe the page element to initialize
*/
protected void doInitElement(PageElement pe)
{
pe.onInitPage();
}
/**
* called by doRefresh() to refresh a particular page element
* @param pe the page element to refresh
*/
protected void doRefreshElement(PageElement pe)
{
pe.onRefreshPage();
}
/* Helpers */
protected final TextResolver getTextResolver()
{
FacesContext fc = FacesUtils.getContext();
return FacesUtils.getTextResolver(fc);
}
}