/* ===============================================================================
*
* Part of the InfoGlue Content Management Platform (www.infoglue.org)
*
* ===============================================================================
*
* Copyright (C)
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 2, as published by the
* Free Software Foundation. See the file LICENSE.html for more information.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY, including the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc. / 59 Temple
* Place, Suite 330 / Boston, MA 02111-1307 / USA.
*
* ===============================================================================
*/
package org.infoglue.cms.applications.common.actions;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.log4j.Logger;
import org.infoglue.cms.applications.common.Session;
import org.infoglue.cms.applications.databeans.LinkBean;
import org.infoglue.cms.controllers.kernel.impl.simple.LabelController;
import org.infoglue.cms.controllers.kernel.impl.simple.LuceneController;
import org.infoglue.cms.exception.AccessConstraintException;
import org.infoglue.cms.exception.Bug;
import org.infoglue.cms.exception.ConfigurationError;
import org.infoglue.cms.exception.ConstraintException;
import org.infoglue.cms.exception.SystemException;
import org.infoglue.cms.security.InfoGluePrincipal;
import org.infoglue.cms.util.ChangeNotificationController;
import org.infoglue.cms.util.CmsPropertyHandler;
import org.infoglue.cms.util.StringManager;
import org.infoglue.cms.util.StringManagerFactory;
import org.infoglue.deliver.util.BrowserBean;
import org.infoglue.deliver.util.RequestAnalyser;
import org.infoglue.deliver.util.ThreadMonitor;
import org.infoglue.deliver.util.Timer;
import webwork.action.Action;
import webwork.action.CommandDriven;
import webwork.action.ResultException;
import webwork.action.ServletRequestAware;
import webwork.action.ServletResponseAware;
/**
* @author Mattias Bogeblad
* @author Frank Febbraro (frank@phase2technology.com)
*/
public abstract class WebworkAbstractAction implements Action, ServletRequestAware, ServletResponseAware, CommandDriven
{
private final static Logger logger = Logger.getLogger(WebworkAbstractAction.class.getName());
private final String ACCESS_DENIED = "accessDenied";
private Error error;
private Errors errors = new Errors();
private List<LinkBean> linkBeans = new ArrayList<LinkBean>();
private HttpServletRequest request;
private HttpServletResponse response;
private String commandName;
/**
*
*/
public Error getError()
{
if(logger.isInfoEnabled())
logger.info("Fetching error from error-template:" + this.error);
return this.error;
}
/**
*
*/
public Errors getErrors()
{
if(logger.isInfoEnabled())
logger.info("Errors:" + this.errors);
return errors;
}
/**
*
*/
public List<LinkBean> getLinkBeans()
{
return linkBeans;
}
/**
*
*/
public String doDefault() throws Exception
{
return INPUT;
}
public abstract void protectFromCSSAttacks(String actionName, String commandName) throws Exception;
/**
* This is the main execution point for any webwork action.
* Lately we added statistics on each action for debugging and optimization purposes.
*/
public String execute() throws Exception
{
Timer t = new Timer();
String result = "";
ThreadMonitor tm = new ThreadMonitor(10000L, request, "Action took to long", false);
/*
ThreadMonitor tm = null;
if(getRequest().getParameter("trackThread") != null && getRequest().getParameter("trackThread").equals("true"))
tm = new ThreadMonitor(10000L, request, "Action took to long", false);
*/
try
{
//This registers what the system needs to know about the port etc to be able to call update cache actions etc.
CmsPropertyHandler.registerDefaultSchemeAndPort(getRequest());
protectFromCSSAttacks(this.getClass().getName(), this.commandName);
result = isCommand() ? invokeCommand() : doExecute();
setStandardResponseHeaders();
long elapsedTime = t.getElapsedTime();
long memoryDiff = t.getMemoryDifferenceAsMegaBytes();
if(elapsedTime > 5000 || memoryDiff > 100)
{
logger.warn("The " + CmsPropertyHandler.getApplicationName() + " request: " + this.getUnencodedCurrentURIWithParameters() + " took " + elapsedTime + " ms to render and seems to have allocated " + memoryDiff + " MB of memory)");
}
}
catch(ResultException e)
{
logger.error("ResultException " + e.getMessage());
logger.warn("ResultException " + e.getMessage(), e);
result = e.getResult();
}
catch(AccessConstraintException e)
{
logger.info("AccessConstraintException " + e, e);
setErrors(e);
result = ACCESS_DENIED;
}
catch(ConstraintException e)
{
logger.info("ConstraintException " + e, e);
List<LinkBean> linkBeans = e.getLinkBeans();
if(linkBeans != null && linkBeans.size() > 0)
{
getResponse().sendRedirect(linkBeans.get(0).getActionURL());
result = NONE;
}
else
{
setErrors(e);
if(e.getResult() != null && !e.getResult().equals(""))
result = e.getResult();
else
result = INPUT;
}
}
catch(Bug e)
{
logger.error("Bug " + e.getMessage());
logger.warn("Bug " + e.getMessage(), e);
setError(e, e.getCause());
result = ERROR;
}
catch(ConfigurationError e)
{
logger.error("ConfigurationError " + e.getMessage());
logger.warn("ConfigurationError " + e.getMessage(), e);
setError(e, e.getCause());
result = ERROR;
}
catch(SystemException e)
{
if(e.getMessage().indexOf("correct checksum") > -1)
{
if(getUnencodedCurrentUrl().indexOf("Confirm.action") > -1)
logger.warn("SystemException on url: " + getUnencodedCurrentUrl() + " with " + this.getRequest().getParameter("yesDestination") + "\n" + e.getMessage(), e);
else
logger.warn("SystemException on url: " + getUnencodedCurrentUrl() + "\n" + e.getMessage(), e);
}
else
{
logger.error("SystemException: " + e.getMessage());
logger.warn("SystemException: " + e.getMessage(), e);
}
setError(e, e.getCause());
result = ERROR;
}
catch(Throwable e)
{
logger.error("Throwable " + e.getMessage());
logger.warn("Throwable " + e.getMessage(), new Exception(e));
final Bug bug = new Bug("Uncaught exception!", e);
setError(bug, bug.getCause());
result = ERROR;
}
finally
{
if(tm != null && !tm.getIsDoneRunning())
tm.done();
}
try
{
if(CmsPropertyHandler.getApplicationName().equalsIgnoreCase("cms"))
ChangeNotificationController.getInstance().notifyListeners();
}
catch(Exception e)
{
logger.error("Error notifying listener " + e.getMessage());
logger.warn("Error notifying listener " + e.getMessage(), e);
}
return result;
}
/**
* This method returns the url to the current page.
* Could be used in case of reload for example or for logging reasons.
*/
public String getCurrentUrl() throws Exception
{
String urlBase = getRequest().getRequestURL().toString();
String urlParameters = getRequest().getQueryString();
return URLEncoder.encode(urlBase + "?" + urlParameters, "UTF-8");
}
/**
* This method returns the url to the current page.
* Could be used in case of reload for example or for logging reasons.
*/
public String getUnencodedCurrentUrl() throws Exception
{
String urlBase = getRequest().getRequestURL().toString();
String urlParameters = getRequest().getQueryString();
if(urlBase.contains("/cms/common/viewAccessRights.vm"))
urlBase = urlBase.replaceFirst("/cms/common/viewAccessRights.vm", "/ViewAccessRights!V3.action");
return urlBase + (urlParameters != null ? "?" + urlParameters : "");
}
/**
* This method returns the URI to the current page.
*/
public String getUnencodedCurrentURI() throws Exception
{
String urlBase = getRequest().getRequestURI().toString();
return urlBase;
}
/**
*
*/
public void setCommand(String commandName)
{
this.commandName = commandName;
}
/**
*
*/
public void setServletRequest(HttpServletRequest request)
{
this.request = request;
}
/**
*
*/
public void setServletResponse(HttpServletResponse response)
{
this.response = response;
}
/**
*
*/
public void setError(Throwable throwable, Throwable cause)
{
this.error = new Error(throwable, cause);
}
/**
*
*/
private void setErrors(ConstraintException exception)
{
final Locale locale = getSession().getLocale();
for (ConstraintException ce = exception; ce != null; ce = ce.getChainedException())
{
final String fieldName = ce.getFieldName();
final String errorCode = ce.getErrorCode();
final String extraInformation = ce.getExtraInformation();
String localizedErrorMessage = "";
logger.warn("Error class: " + ce.getClass().getName());
if(errorCode.length() > 5)
localizedErrorMessage = errorCode;
else
localizedErrorMessage = getLocalizedErrorMessage(locale, errorCode);
//System.out.println("localizedErrorMessage:" + localizedErrorMessage);
//final String localizedErrorMessage = getLocalizedErrorMessage(locale, errorCode);
getErrors().addError(fieldName, localizedErrorMessage + (extraInformation.length() > 0 ? " " + extraInformation + " " : ""));
getLinkBeans().addAll(ce.getLinkBeans());
}
logger.debug(getErrors().toString());
}
/**
* <todo>Move to a ConstraintExceptionHelper class?</todo>
* @throws NullPointerException If any of the arguments are null
* @throws MissingResourceException If no key matches the given <em>errorCode</em>
*/
protected String getLocalizedErrorMessage(Locale locale, String errorCode) throws NullPointerException, MissingResourceException
{
// <todo>fetch packagename from somewhere</todo>
StringManager stringManager = StringManagerFactory.getPresentationStringManager("org.infoglue.cms.entities", locale);
// check if a specific error message exists - <todo/>
// nah, use the general error message
return stringManager.getString(errorCode);
}
public String getLocalizedString(Locale locale, String key)
{
StringManager stringManager = StringManagerFactory.getPresentationStringManager("org.infoglue.cms.applications", locale);
return stringManager.getString(key);
}
public String getLocalizedString(Locale locale, String key, Object arg1)
{
return LabelController.getController(locale).getLocalizedString(locale, key, arg1);
}
public String getLocalizedString(Locale locale, String key, Object[] parameters)
{
return LabelController.getController(locale).getLocalizedString(locale, key, parameters);
}
/**
*
*/
private boolean isCommand()
{
return this.commandName != null && commandName.trim().length() > 0 && (this instanceof CommandDriven);
}
/**
* This is a complement to the normal webwork execution which allows for a command-based execution of actions.
*/
private String invokeCommand() throws Exception
{
//Timer t = new Timer();
//ThreadMonitor tm = null;
//if(getRequest().getParameter("trackThread") != null && getRequest().getParameter("trackThread").equals("true"))
// tm = new ThreadMonitor(10L, request, "Command took to long", false);
ThreadMonitor tm = new ThreadMonitor(10000L, request, "Command took to long", false);
final StringBuffer methodName = new StringBuffer("do" + this.commandName);
methodName.setCharAt(2, Character.toUpperCase(methodName.charAt(2)));
String result = "";
try
{
protectFromCSSAttacks(this.getClass().getName(), this.commandName);
final Method method = getClass().getMethod(methodName.toString(), new Class[0]);
result = (String) method.invoke(this, new Object[0]);
}
catch(Exception ie)
{
if(ie.getMessage() != null)
logger.error("Exception in top action:" + ie.getMessage(), ie);
try
{
throw ie.getCause();
}
catch(ResultException e)
{
logger.error("ResultException " + e, e);
result = e.getResult();
}
catch(AccessConstraintException e)
{
logger.info("AccessConstraintException " + e, e);
setErrors(e);
result = ACCESS_DENIED;
}
catch(ConstraintException e)
{
logger.info("ConstraintException " + e, e);
List<LinkBean> linkBeans = e.getLinkBeans();
if(linkBeans != null && linkBeans.size() > 0)
{
logger.info("linkBean:" + linkBeans.get(0).getActionURL());
getResponse().sendRedirect(linkBeans.get(0).getActionURL());
}
else
{
setErrors(e);
if(e.getResult() != null && !e.getResult().equals(""))
result = e.getResult();
else
result = INPUT;
}
}
catch(Bug e)
{
logger.error("Bug " + e.getMessage(), e);
setError(e, e.getCause());
result = ERROR;
}
catch(ConfigurationError e)
{
logger.error("ConfigurationError " + e);
setError(e, e.getCause());
result = ERROR;
}
catch(SystemException e)
{
if(e.getMessage() != null && e.getMessage().indexOf("correct checksum") > -1)
{
if(getUnencodedCurrentUrl().indexOf("Confirm.action") > -1)
logger.warn("SystemException on url: " + getUnencodedCurrentUrl() + " with " + this.getRequest().getParameter("yesDestination") + "\n" + e.getMessage(), e);
else
logger.warn("SystemException on url: " + getUnencodedCurrentUrl() + "\n" + e.getMessage(), e);
}
else
logger.error("SystemException: " + e.getMessage(), e);
setError(e, e.getCause());
result = ERROR;
}
catch(Throwable e)
{
logger.error("Throwable " + e.getMessage(), e);
final Bug bug = new Bug("Uncaught exception!", e);
setError(bug, bug.getCause());
result = ERROR;
}
}
finally
{
if(tm != null && !tm.getIsDoneRunning())
tm.done();
}
try
{
if(!this.getRequest().getRequestURI().contains("UpdateCache!test"))
{
//System.out.println("URL:" + this.getRequest().getRequestURI());
ChangeNotificationController.getInstance().notifyListeners();
}
}
catch(Exception e)
{
e.printStackTrace();
}
return result;
}
public void setStandardResponseHeaders()
{
try
{
Map<String, String> standardHeaders = CmsPropertyHandler.getStandardResponseHeaders();
HttpServletResponse response = getResponse();
for (Map.Entry<String, String> entry : standardHeaders.entrySet())
{
response.setHeader(entry.getKey(), entry.getValue());
}
response.setHeader("Cache-Control", "no-cache");
}
catch (Exception e)
{
logger.warn("Could not set headers:" + e.getMessage());
}
}
public final String getRoot()
{
return request.getContextPath();
}
/**
* This method returns a logged in principal if existing.
*/
public final InfoGluePrincipal getInfoGluePrincipal()
{
return getSession().getInfoGluePrincipal();
}
/**
* Subclasses implement this
*/
protected abstract String doExecute() throws Exception;
/**
*
*/
public final BrowserBean getBrowserBean()
{
BrowserBean browserBean = new BrowserBean();
browserBean.setRequest(this.request);
return browserBean;
}
/**
* Returns the HttpServletRequest
* TODO: Hide the implementation behind WebWorks abstractions in ActionContext
*/
protected final HttpServletRequest getRequest()
{
return this.request;
}
/**
* Returns the HttpServletResponse
* TODO: Hide the implementation behind WebWorks abstractions in ActionContext
*/
protected final HttpServletResponse getResponse()
{
return this.response;
}
/**
* Returns the HttpSession
* TODO: Hide the implementation behind WebWorks abstractions in ActionContext
*/
protected final HttpSession getHttpSession()
{
return getRequest().getSession();
}
/**
* Use the ActionContext to initialize the Session and remove the dependence
* on HTTP and the Servlet Spec. Makes it much easier for testing.
*/
public final Session getSession()
{
return new Session(getHttpSession());
}
public final String getSessionId()
{
return getHttpSession().getId();
}
/**
* This method returns the URI to the current page.
*/
public String getUnencodedCurrentURIWithParameters() throws Exception
{
String urlBase = getRequest().getRequestURI().toString();
String queryString = "";
Enumeration parameterNames = getRequest().getParameterNames();
while(parameterNames.hasMoreElements())
{
String name = (String)parameterNames.nextElement();
queryString += "&" + name + "=" + getRequest().getParameter(name);
}
if(queryString != null && !queryString.equals(""))
urlBase = urlBase + "?" + queryString.substring(1);
return urlBase;
}
}