Struts2 handle session timeout using Interceptor - session

I am trying to handle session timeout requests in my struts2 application using an Interceptor. Below are the files related to this:
Web.xml:
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>1</session-timeout>
</session-config>
Struts.xml:
<package name="default" extends="struts-default">
<interceptors>
<interceptor name="sessionInterceptor"
class="com.platform.web.security.SessionInterceptor" />
</interceptors>
<action name="doLogin"
class="com.platform.web.action.LoginAction">
<result name="input">/login/login.jsp</result>
<result name="error">/login/login.jsp</result>
<result type="chain">menuAction</result>
</action>
<action name="menuAction"
class="com.platform.web.action.MenuAction">
<interceptor-ref name="sessionInterceptor"/> //Interceptor included here
<result name="SUCCESS">/jsp/main.jsp</result>
<result name="ERROR">/login/login.jsp</result>
<result name="input">/jsp/myFavourite.jsp</result>
</action>
Interceptor Class:
public class SessionInterceptor extends AbstractInterceptor implements StrutsStatics {
/**
*
*/
private static final long serialVersionUID = 1L;
#Override
public String intercept(ActionInvocation invocation) throws Exception {
final ActionContext context = invocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest) context
.get(HTTP_REQUEST);
HttpSession session = request.getSession(false);
// Is there a "user" object stored in the user's HttpSession?
//Object user = session.getAttribute("User");
if (session == null) {
// The user has not logged in yet.
// Is the user attempting to log in right now?
//String loginAttempt = request.getParameter(LOGIN_ATTEMPT);
/* The user is attempting to log in. */
/*if (!StringUtils.isBlank(loginAttempt)) {
return invocation.invoke();
}*/
return "timeout";
} else {
return invocation.invoke();
}
}
}
LoginAction:
public class LoginAction extends MesActionSupport implements ServletRequestAware {
#Override
public String execute() throws Exception {
setActionNameForAudit("execute123");
FILE_LOGGER.debug("Entering into execute() ... ");
String strSessionId = "";
if (isValidUser == true) {
user = getUser();
strSessionId = request.getSession(true).getId();
setServletRequest(request);
session.put("SessionId", strSessionId);
setSession(session, user);
ServletActionContext.getRequest().getSession().setAttribute("User", user);
FILE_LOGGER.debug("Exit from LoginAction.execute() ... ");
return SUCCESS;
} else {
return ERROR;
}
}
MenuAction:
public class MenuAction extends MesActionSupport implements SessionAware, ParameterAware, RequestAware {
#Override
public String execute() throws Exception {
setActionNameForAudit("execute ");
User user = null; // To store current user
Map<String, ArrayList<String>> category = null; // To store all Menu
// Categories.
StringBuffer menu = new StringBuffer(""); // To store Menu String
StringBuffer dashboardMenu = new StringBuffer("");
// user = (User)(request.getSession().getAttribute("User")==null ? null : request.getSession().getAttribute("User")); //Request object IS NULL HERE!!
user = (User) (mapSession.get("User") == null ? null : mapSession
.get("User")); // mapSession object IS NULL HERE
FILE_LOGGER.debug("user is " + user == null);
if (user != null) {
menu = menuView.getMenu(user);
mapSession.put("Menu", menu.toString());
mapSession.put("dbMenu", dashboardMenu.toString());
ret = "SUCCESS";
} else if (user == null) {
ret = ERROR;
} else {
ret = SUCCESS;
}
return ret;
}
Flow is like this:
1. Login screen opens
2. User enters credentials and submits
3. LoginAction is called, user is authenticated
4. If valid user - MenuAction is called. Else redirect to Login.jsp
According to the above code, the session gets created in the LoginAction, control reaches the Interceptor where the session object is checked. If session exists the control reaches MenuAction.
But when this happens, the request object gets reset to NULL! Earlier when I was not using an interceptor, the flow was working completely fine between LoginAction and MenuAction.
Does the interceptor resets the HTTPRequest? and hence the Session? As a result I am not able to proceed.
Any help?

I have many comments on this code/configuration, some trivial, some not.
There's zero reason to create your own session; don't.
When you declare an interceptor in an action configuration you must declare all interceptors. As configured, only the session interceptor is running for menuAction.
That means no parameters will be filled, since no other interceptors are running.
In general, only use SessionAware to access the session. There is very rarely a need to access the request directly.
Obviously the interceptor doesn't "set the request to null", that doesn't even make sense.
I have no idea what your LoginAction is supposed to be doing. What's the intent of lines like setServletRequest(request); or setSession(session, user);? There's nothing about those two lines that look correct.
Name your success and error results just that, "success" and "error" (lower case), if you're going to use the ActionSupport.SUCCESS and ActionSupport.ERROR constants. If you're not using those constants in this code, I'd recommend using other names for them, because it will confuse anybody that has actually used Struts 2 before.
When posting code samples please remove stuff that isn't relevant. Particularly when you don't explicitly set syntax highlighting it makes things much more difficult to read.
Don't use code like if (isValidUser == true), use if (isValidUser).
Mind your conditionals: stuff like if (user == null) ... else if (user != null) ... else ... makes zero sense. The user is null, or it isn't: there's no third option.
Avoid unnecessary, confusing logic, like User currentUser = (User) (mapSession.get("User") == null ? null : mapSession.get("User")); which basically says "If it's null, use null, otherwise return the value you just got, but get it again." WHY???
Comments like User user = null; // To store current user are completely worthless. Isn't it obvious what User user is? A user. Not obvious enough? How about User currentUser???
Name plurals (e.g., collections) as something plural. A map of category names to lists isn't a single category.
Don't declare variables far away from where they're used; it's very confusing.
Put your JSP pages under WEB-INF somewhere to disallow direct client access.
Avoid unnecessary language constructs, like when an if branch returns, an else isn't strictly necessary, and IMO, it adds noise. Similarly, consider returning as soon as you know you're returning. The latter is a bit more controversial, but I think people are coming around to realizing it's okay to have multiple return points in short methods, and IMO it's easier to think about.
Use less code to say things. Create little utility methods to wrap up trivial functionality so it doesn't pollute the mainline code.
Use action chaining almost never.
There's more, but that's enough for now. Here's the actual relevant code, cleaned up. Some of it isn't actually relevant but I left it in anyway.
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>1</session-timeout>
</session-config>
<package name="default" extends="struts-default">
<interceptors>
<interceptor name="sessionInterceptor" class="com.platform.web.security.SessionInterceptor" />
</interceptors>
<action name="doLogin" class="com.platform.web.action.LoginAction">
<result name="input">/WEB-INF/jsp/login/login.jsp</result>
<result name="error">/WEB-INF/jsp/login/login.jsp</result>
<result type="redirectAction">menuAction</result>
</action>
<action name="menuAction" class="com.platform.web.action.MenuAction">
<interceptor-ref name="sessionInterceptor"/>
<result name="success">/WEB-INF/jsp/main.jsp</result>
<result name="input">/WEB-INF/jsp/myFavourite.jsp</result>
</action>
public class SessionInterceptor extends AbstractInterceptor implements StrutsStatics {
#Override
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext context = invocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest) context.get(HTTP_REQUEST);
HttpSession session = request.getSession(false);
// session will almost *never* be null. Check for a valid user object.
if (session == null) {
return "timeout";
}
return invocation.invoke();
}
}
public class LoginAction extends MesActionSupport implements ServletRequestAware {
#Override
public String execute() throws Exception {
if (!isValidUser) {
return ERROR;
}
user = getUser();
String strSessionId = request.getSession(true).getId();
setServletRequest(request);
session.put("SessionId", strSessionId);
setSession(session, user);
ServletActionContext.getRequest().getSession().setAttribute("User", user);
return SUCCESS;
}
}
public class MenuAction extends MesActionSupport implements SessionAware, ParameterAware, RequestAware {
#Override
public String execute() throws Exception {
User currentUser = (User) mapSession.get("User");
if (currentUser == null) {
return ERROR;
}
Map<String, List<String>> categories; // Left in for naming.
StringBuffer menu = menuView.getMenu(user);
mapSession.put("Menu", menu.toString());
StringBuffer dashboardMenu = new StringBuffer("");
mapSession.put("dbMenu", dashboardMenu.toString());
return SUCCESS;
}
}

There's no way you can tell between whether the session has timed out or it just hasn't been created yet in your interceptor, unless you add some tracking to your front-end, in primitive case it could be just a single request parameter.
A combination of HttpSessionListener and Servlet3 + AJAX Push notifications would be the right way to do it.

Related

How can I log method expressions of JSF ajax requests

I have figured out how to log when a request is an ajax request and which page it is from, in a filter.
What I would really like to do is log what the ajax request is actually for. Such as the name of the method being called by the ajax (eg "findAddress" in this call:<p:ajax process="contactDetails" update="#form" listener="#{aboutYouController.findAddress}" .... )
How can I do this? My app has many ajax requests and I want to log which are being triggered.
public class TrackingFilter implements Filter {
private static Logger LOG = Logger.getLogger(TrackingFilter.class);
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
String pageHit = req.getRequestURI().substring(req.getContextPath().length()+1).replace(".xhtml", "");
if(!pageHit.contains("javax.faces.resource")){ // if is a url we want to log
if ("partial/ajax".equals(req.getHeader("Faces-Request"))) {
LOG.trace("ajax on URI: " + req.getRequestURI());
}
What I would really like to do is log what the ajax request is actually for. Such as the name of the method being called by the ajax (eg "findAddress" in this call:<p:ajax process="contactDetails" update="#form" listener="#{aboutYouController.findAddress}" ....)
This information is only available in the JSF component tree. The JSF component tree is only available after view build time. A view is only built when the request has been served by FacesServlet. Thus, a servlet filter is way too early as it runs before any servlet.
You'd better run the code after the restore view phase of a postback. The JSF component tree is guaranteed to be available during that moment. You can use FacesContext#isPostback() to check if the current request is a postback. You can use PartialViewContext#isAjaxRequest() to check if the current request is an ajax request. You can use the predefined javax.faces.source request parameter to obtain the client ID of the source component of the ajax request. You can use the predefined javax.faces.behavior.event request parameter to obtain the ajax event name (e.g. change, click, action, etc).
Obtaining the associated behavior listeners is in turn a story apart. This is easy on ActionSource2 components (e.g. <h|p:commandButton action="#{...}">) as the MethodExpression is just available by ActionSource2#getActionExpression(). However, this isn't easy on BehaviorBase taghandlers (e.g. <f|p:ajax listener="#{...}">) as this API doesn't have any method like getBehaviorListeners(). There are only methods to add and remove them, but not to obtain a list of them. So some nasty reflection trickery is necessary to access the private field with those listeners whose name is JSF implementation specific. In Mojarra it's listeners and in MyFaces it's _behaviorListeners. Both are fortunately assignable from List and it's the only field of that type, so we could just check for that. Once having hand of the BehaviorListener instance, then you still need to do another reflection trickery to obtain the MethodExpression field of that instance. Yuck.
All in all, here's how the trickery look like in flavor of a PhaseListener listening on afterPhase of RESTORE_VIEW:
public class AjaxActionLoggerPhaseListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
if (!(context.isPostback() && context.getPartialViewContext().isAjaxRequest())) {
return; // Not an ajax postback.
}
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String sourceClientId = params.get("javax.faces.source");
String behaviorEvent = params.get("javax.faces.behavior.event");
UIComponent source = context.getViewRoot().findComponent(sourceClientId);
List<String> methodExpressions = new ArrayList<>();
if (source instanceof ClientBehaviorHolder && behaviorEvent != null) {
for (ClientBehavior behavior : ((ClientBehaviorHolder) source).getClientBehaviors().get(behaviorEvent)) {
List<BehaviorListener> listeners = getField(BehaviorBase.class, List.class, behavior);
if (listeners != null) {
for (BehaviorListener listener : listeners) {
MethodExpression methodExpression = getField(listener.getClass(), MethodExpression.class, listener);
if (methodExpression != null) {
methodExpressions.add(methodExpression.getExpressionString());
}
}
}
}
}
if (source instanceof ActionSource2) {
MethodExpression methodExpression = ((ActionSource2) source).getActionExpression();
if (methodExpression != null) {
methodExpressions.add(methodExpression.getExpressionString());
}
}
System.out.println(methodExpressions); // Do your thing with it.
}
private static <C, F> F getField(Class<? extends C> classType, Class<F> fieldType, C instance) {
try {
for (Field field : classType.getDeclaredFields()) {
if (field.getType().isAssignableFrom(fieldType)) {
field.setAccessible(true);
return (F) field.get(instance);
}
}
} catch (Exception e) {
// Handle?
}
return null;
}
}
In order to get it to run, register as below in faces-config.xml:
<lifecycle>
<phase-listener>com.example.AjaxActionLoggerPhaseListener</phase-listener>
</lifecycle>
Above is tested and compatible with Mojarra and PrimeFaces and theoretically also compatible with MyFaces.
Update: in case you're using JSF utility library OmniFaces, or are open to, since version 2.4 you can use the new Components#getCurrentActionSource() utility method to find out the current action source component and Components#getActionExpressionsAndListeners() to get a list of all action methods and listeners registered on a given component. This is also useable on regular (non-ajax) requests. With that, the above PhaseListener example can be reduced as below:
public class FacesActionLoggerPhaseListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.PROCESS_VALIDATIONS;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
if (!event.getFacesContext().isPostback())) {
return;
}
UIComponent source = Components.getCurrentActionSource();
List<String> methodExpressions = Components.getActionExpressionsAndListeners(source);
System.out.println(methodExpressions); // Do your thing with it.
}
}

Accessing ServletContext object in Action class in Struts 1.2

I have been given a use-case, to come up with a solution to allow configured number of users per user id to be logged in to my application at any given time.
For example : userid 'parentuser' can be used to log in to the application for a max of 10 times at any time.After this limit, the user will not allowed to log in as max number of users are accessing the application for that user.
Now, To implement this, I have created a context listener which will instantiate an attribute which I'll keep updating as the user logs in the application in the Action class.
My Context Listener is as under :
public class ApplicationContextListener implements ServletContextListener {
private Map<String, List<ApplicationContextBean>> userMap;
#Override
public void contextDestroyed(ServletContextEvent arg0) {
userMap = null;
}
#Override
public void contextInitialized(ServletContextEvent event) {
userMap = new HashMap<String, List<ApplicationContextBean>>();
}
public Map<String, List<ApplicationContextBean>> getUserMap() {
return userMap;
}
public void setUserMap(Map<String, List<ApplicationContextBean>> userMap) {
this.userMap = userMap;
}
}
web.xml is as under
<listener>
<listener-class>com.pcs.bpems.portal.listener.ApplicationContextListener</listener-class>
</listener>
Question : How can I now access this context object 'userMap' from my action class? If anyone has any other approach different than this also, kindly post the same.
Thanks
The answer is in the title of your question: store the Map (or an object wrapping the map and providing useful methods) into an attribute of the servlet context (accessible from the event), and retrieve it from wherever you want: the HttpServletRequest provides access to the servlet context.
A better solution, which would also work in case your application is clustered, would be to use the database.
Also, don't forget to decrement the counter when the session expires.
This can be stored in the Servlet Context as under :
#Override
public void contextInitialized(ServletContextEvent event) {
userMap = new HashMap<String, Map<String,List<ApplicationContextBean>>>();
event.getServletContext().setAttribute(ApplicationConstants.LOGGED_IN_USERS, userMap);
}
The stored parameters can be then fetched from the HttpSession Object as under :
currentSession.getServletContext().getAttribute(LOGGED_IN_USERS)

Get resteasy servlet context without annotation params

Quick project explanation: We have a built application based on JSF2 + Spring with Dynamic data sources. The data reference control is made with a spring-config:
<bean id="dataSource" class="com.xxxx.xxxx.CustomerRoutingDataSource">
....
and a class (referenced above):
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
the CustomerContextHolder called above is as follows:
public class CustomerContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
String manager = (String)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("dataBaseManager");
if (manager != null) {
contextHolder.set(manager);
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("dataBaseManager", null);
} else {
String base = (String)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("currentDatabBase");
if (base != null)
contextHolder.set(base);
}
return (String) contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
The problem is that the last guy is calling FacesContext.getCurrentInstance() to get the servlet context. Just to explain, it uses the session Attribute dataBaseManager to tell which base it should use.
For the actual solution it was working fine, but with the implementation of a RESTEASY web service, when we make a get request the FacesContext.getCurrentInstance() is obviously returning null and crashing.
I searched a lot and could not find a way of getting the servlet-context from outside of the #GET params. I would like to know if is there any way of getting it, or if there is another solution for my dynamic datasource problem.
Thanks!
Like magic and probably not much people know.
I searched deep into the Resteasy documentation, and found a part of springmvc plugin that comes with the resteasy jars, that has a class called RequestUtil.class.
With that I was able to use the method getRequest() without the "#Context HttpServletRequest req" param.
Using that I was able to set the desired database on the request attributes, and from another thread (called by spring) get it and load the stuff from the right place!
I'm using it for a week now and it works like a charm. Only thing that I needed to do is change the determineLookupKey() above to this:
#Override
protected String determineCurrentLookupKey() {
if (FacesContext.getCurrentInstance() == null) {
//RESTEASY
HttpServletRequest hsr = RequestUtil.getRequest();
String lookUpKey = (String) hsr.getAttribute("dataBaseManager");
return lookUpKey;
}else{
//JSF
return CustomerContextHolder.getCustomerType();
}
}
Hope this helps other people!
Thiago

Struts - Cannot find bean in any scope

I'm using eclipse to implement with the native Struts and hybernate support an application to display a series of links in a page. I'm getting the error:
javax.servlet.jsp.JspException: Cannot find bean: "ListeActeur" in scope: "session"
I've checked a lot of sites and forums, and nothing seems to fix this.
My struts-config:
<struts-config>
<form-beans type="org.apache.struts.action.ActionFormBean">
<form-bean name="ListeActeur" type="mesForms.strust.ListeActeur"/>
<form-bean name="vérifCritère" type="mesForms.strust.vérifCritère"/>
</form-beans>
<action-mapping>
</action>
<action path="/Liste"
parameter="/vue/Invitation.jsp"
name="ListeActeur scope="request validate="false"
type="mesAction.struts.ListeActeurAction">
<forward name="s" path="/vue/NewFile.jsp" redirect="false" />
</action>
</action-mappings>
</struts-config>
ListeActeurAction:
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest req, HttpServletResponse res) throws Exception {
System.out.println("Action");
ListeActeur ListeActeur= (ListeActeur) form;
String query = "select * from Acteur " ;
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Iterator results = session.createSQLQuery(query).list().iterator();
List <Acteur> lis = new ArrayList<Acteur>();
while((results.hasNext()))
{
Acteur gg =new Acteur();
Object[] row = (Object[]) results.next();
gg.setActeurId((Integer)row[0]);
gg.setNomActeur((String)row[2]);
lis.add(gg);
}
req.getSession(true).setAttribute("lis", lis);
session.getTransaction().commit();
HibernateUtil.getSessionFactory().close();
ListeActeur.setLis( lis);
req.setAttribute("formu", ListeActeur.getLis());
return mapping.findForward("s");
}
}
ListeActeur:
public class ListeActeur extends ActionForm {
private List <Acteur> lis=null;
public List <Acteur> getLis(){
return lis;}
public void setLis(List <Acteur> lis){this.lis=lis;}
public void reset(ActionMapping mapping, HttpServletRequest request) {
lis = new ArrayList<Acteur>();
}
I really dont know what to do. Im a newbie in Struts.Thanks in advance!
You have
<action path="/Liste" scope="request" .../>
and
<logic:iterate ... scope="session" >
no wonder you get this exception. If you configure Struts to store the form bean in the request, don't try to fetch it from the session in your JSP.

Struts2 validating 3 times on single textfield

I am having really upsetting issue with Struts(2.2.3). Here is my field validations on ActionName-validation.xml
<field name="txtRequestDateFrom">
<field-validator type="conversion">
<param name="repopulateField">false</param>
<message>${getText("E011", {"Date from"})}</message>
</field-validator>
</field>
I don't have validate() method in my action class. And I have this in my action class:
private Date txtRequestDateFrom;
{getter, setters}
When I enter letters on my txtRequestDateFrom field I get 3 validation messages on
<s:fielderror fieldName="txtRequestDateFrom"/>
It look like this
Invalid field value for field "txtRequestDateFrom".
Invalid field value for field "txtRequestDateFrom".
Date from has an invalid value
I have my custom theme, and I am sure there is not any much modification from SIMPLE theme. My interceptor stack is pretty much as same default value stack.
<interceptor-stack name="defaultStack">
<interceptor-ref name="security"/>
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="debugging"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUploadStack" />
<interceptor-ref name="fileUpload" >
<param name="maximumSize">4000000</param>
</interceptor-ref>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="multiselect"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError" />
<interceptor-ref name="validation">
<param name="excludeMethods">execute, complete ...</param>
</interceptor-ref>
<interceptor-ref name="workflow"/>
</interceptor-stack>
I found out that one field error can be removed by removing conversionError interceptor from the stack. But I don't think that would cause this problem. Struts should be able to show errors only defined by developer, right?
Please help me on this
You need to understand how Struts2 handles conversion errors.
Any error that occurs during type conversion may or may not wish to be reported. For example, reporting that the input "abc" could not be converted to a number might be important. On the other hand, reporting that an empty string, "", cannot be converted to a number might not be important - especially in a web environment where it is hard to distinguish between a user not entering a value vs. entering a blank value.
...
It is important to know that none of these errors are actually reported directly. Rather, they are added to a map called conversionErrors in the ActionContext. There are several ways this map can then be accessed and the errors can be reported accordingly.
There are two ways the error reporting can occur:
Globally, using the Conversion Error Interceptor
On a per-field basis, using the conversion validator
You are using both mechanisms, thus duplicating the errors found. As the documentation states, usually you don't want to report all conversion errors, and thus should remove the ConversionErrorInterceptor from the stack. Now you can selectively raise conversion errors as field errors using the conversion validator.
I found that my custom DateTimeConverter was causing the exceptions and the extra error message. Because I found the code below from Struts2 book in order to change my Date's normal format. When it throws an exception, it shows the exception on console and error message on field error rather than passing the exception to the validator. I think it is sort of bug because this class extends StrutsTypeConverter and it should work as normal converters.
public class StringToDateTimeConverter extends StrutsTypeConverter {
private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy/MM/dd");
public Object convertFromString(Map context, String[] strings, Class toClass) {
if (strings == null || strings.length == 0 || strings[0].trim().length() == 0) {
return null;
}
try {
Calendar calendar = Calendar.getInstance();
calendar.setTime(DATETIME_FORMAT.parse(strings[0]));
calendar.set(Calendar.HOUR, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
return calendar.getTime();
} catch (ParseException e) {
throw new TypeConversionException(e);
}
}
public String convertToString(Map context, Object date) {
if (date != null && date instanceof Date) {
return DATETIME_FORMAT.format(date);
} else {
return null;
}
}
}
Anyway I changed throw new TypeConversionException(e); to return null; and added REQUIRED validator on validation XML. Now it shows me error when I put invalid date on my date fields.
PS: Is there any other way to change Struts global date format? Thanks
I faced a similar problem yesterday and finally found a solution which I like to share. I'm using annotations in my actions for validation, so I changed default struts interceptor stack and put my SensibleConversionErrorInterceptor instead of StrutsConversionErrorInterceptor in. This one is total identically but doesn't create any validation errors. Instead they are generated by validation configured in annotations in my actions.
Here is my converter:
public class SensibleConversionErrorInterceptor extends StrutsConversionErrorInterceptor {
private static final long serialVersionUID = 8186282792289268544L;
#Override
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext invocationContext = invocation.getInvocationContext();
Map<String, Object> conversionErrors = invocationContext.getConversionErrors();
ValueStack stack = invocationContext.getValueStack();
HashMap<Object, Object> fakie = null;
for (Map.Entry<String, Object> entry : conversionErrors.entrySet()) {
String propertyName = entry.getKey();
Object value = entry.getValue();
if (shouldAddError(propertyName, value)) {
// removed cause error messages are generated from annotations in actions
// String message = XWorkConverter.getConversionErrorMessage(propertyName, stack);
// Object action = invocation.getAction();
// if (action instanceof ValidationAware) {
// ValidationAware va = (ValidationAware) action;
// va.addFieldError(propertyName, message);
// }
if (fakie == null) {
fakie = new HashMap<Object, Object>();
}
fakie.put(propertyName, getOverrideExpr(invocation, value));
}
}
if (fakie != null) {
// if there were some errors, put the original (fake) values in place right before the result
stack.getContext().put(ORIGINAL_PROPERTY_OVERRIDE, fakie);
invocation.addPreResultListener(new PreResultListener() {
public void beforeResult(ActionInvocation invocation, String resultCode) {
Map<Object, Object> fakie = (Map<Object, Object>) invocation.getInvocationContext().get(ORIGINAL_PROPERTY_OVERRIDE);
if (fakie != null) {
invocation.getStack().setExprOverrides(fakie);
}
}
});
}
return invocation.invoke();
}
}
And an example action:
#Conversion
public class ProductAction extends ActionSupport {
private Product product;
// getter, setter and so on...
#Action(...)
#Validations(
requiredFields = {
#RequiredFieldValidator(
type = ValidatorType.FIELD,
fieldName = "product.validFrom",
message = "required.product.validFrom",
shortCircuit = true
)
},
conversionErrorFields = {
#ConversionErrorFieldValidator(
fieldName = "product.validFrom",
key = "invalid.fieldvalue.product.validFrom'",
shortCircuit = true
)
}
)
public String saveOrUpdate() {
// do something here...
}
}

Resources