Struts2 validating 3 times on single textfield - validation

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...
}
}

Related

Reading XML Files using Java, return in a SOAP request

I'm fairly new to using SOAP and Blueprint (Which is just like Spring).
Anyway, I'm just trying to learn the basics atm, doing pretty well so far.
I've run into a small problem when using a Java Class to retrieve a specific node value from an XML file. This works when I run the application as a stand-alone but when I am getting the request using Soap, the value "lastName" returns null.
public static void main(String[] args) throws XPathExpressionException {
DocumentBuilderFactory builderFactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
try {
builder = builderFactory.newDocumentBuilder();
} catch (ParserConfigurationException p) {
p.printStackTrace();
}
try {
Document document = builder.parse(new FileInputStream("d:\\input11.xml"));
XPath xP = XPathFactory.newInstance().newXPath();
String expression ="/people/person/lastName";
NodeList nodeList = (NodeList) xP.compile(expression).evaluate(document, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); i++) {
lastName += nodeList.item(i).getFirstChild().getNodeValue() + " ";
}
} catch (IOException e) {
e.printStackTrace();
} catch (SAXException s) {
s.printStackTrace();
}
System.out.println(lastName);
}
public static String returnLastName(String input){
System.out.println(lastName);
return "LastName: "+lastName +"\n";
}
}
And here's my blueprint.xml code:
<bean id="lastNameBean" class="com.*****.camelBlueprintTest.XMLCamel" />
<route id="lastName">
<from uri="cxf:bean:returnLName" />
<bean ref="lastNameBean" method="returnLastName" />
<log message="The message contains ${body}" />
<to uri="mock:result" />
</route>
So it does actually return the last names when I run the Java application, but in the SOAP request I am getting "LastName: null".
AH!! I've found the error. Silly me. So, I was calling the method in my blueprint "returnLastName" and it was returning null, I didn't realize that this method was called ALONE, so moving my code from the main into the method fixed it like a charm haha.
I feel really silly but it's always the little mistakes that get me.

Mark inputText as invalid in Invoke Applications phase

I am performing some business rule validations in the Invoke Applications phase, and when there is an error, a custom Exception will be thrown. The custom exception is handled in a custom JSF ErrorHandler, where the input component in question will be marked as invalid, FacesMessages created and validation will be failed on the FacesContext.
Bean
public void performAction() {
if ("aaa".equals(input)) {
// custom exception: arg1 - Error Message, arg2 - clientId
throw new ServiceValidationException("Something went wrong", ":f:input");
}
}
XHTML
<h:form id="f">
<p:inputText id="input" value="#{bean.input}" />
<h:commandButton value="Submit" action="#{bean.performAction}"/>
</h:form>
Custom JSF ErrorHandler
#Override
public void handle() throws FacesException {
try {
Iterator<ExceptionQueuedEvent> unhandledExceptionQueuedEvents = getUnhandledExceptionQueuedEvents().iterator();
if (unhandledExceptionQueuedEvents.hasNext()) {
Throwable exception = unhandledExceptionQueuedEvents.next().getContext().getException();
Throwable rootCause = unwrapRootCause(exception);
if (rootCause instanceof ServiceValidationException) {
ServiceValidationException sve = (ServiceValidationException) rootCause;
JSFComponentUtil.markComponentAsInvalid(sve.getClientId());
// create FacesMessage here etc
...
FacesContext.getCurrentInstance().validationFailed();
return;
}
}
} catch (Exception e) {
logger.error("Error encountered while processing exception, allow default error handling to take over", e);
}
// delegate to Omnifaces Ajax exception handler
super.handle();
}
JSFComponentUtil
public static void markComponentAsInvalid(String componentId) {
UIComponent component = findComponent(componentId);
if (component != null && component instanceof EditableValueHolder) {
EditableValueHolder evh = (EditableValueHolder) component;
if (evh.isValid()) {
evh.setValid(false);
}
} else {
LOG.debug("component not found or is not instance of EditableValueHolder");
}
}
public static UIComponent findComponent(String componentId) {
UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
if (viewRoot != null) {
return viewRoot.findComponent(componentId);
}
LOG.debug("View Root is null, returning null");
return null;
}
The Problem
The issue I'm running into is that upon submitting the form via the command button, the page redisplays with the input text field marked as red (expected behavior), however the text that was typed into the field is lost. I want the invalid text entered to remain in the field.
In markComponentInvalid, you can try manually setting the component's value:
evh.setSubmittedValue("aaa");
evh.setValue("aaa");
Of course, instead of hard-coding "aaa" you could add an "input" property to your ServiceValidationClass so that you can pass that value from the action method to the error handler and then to the Util class, e.g.
bean:
throw new ServiceValidationClass ("Something went wrong", ":f:input", input);
error handler:
JSFComponentUtil.markComponentAsInvalid(sve.getClientId(), sve.getInput());
etc.

Database event handling by spring

I want to handle database events with spring. spring has event handling mechanism and I have defined some custom event handlers in spring. for example if an employee gets inactivate in system so certain sets of activity are required to perform so is there any way in which spring custom event listners can be fired. I am using spring with hibernate.
I suspect if there could be a way in hibernate to achieve this but i want to handle it spring. Thanks in advance
If I understand your question correctly, you need to know a way to intercept hibernate entity change event and trigger an event which can be handled by Spring. You can always intercept events in hibernate i.e., inspect and/or change property values of hibernate entity elements using Interceptor. For example, Employee entity has status(say String) field, you can detect change(say, changed from active to inactive) to this field and perform some action before change can be persisted to database. Changes to any collection present in this entity can be intercepted as well. For brevity I am taking up only field change.
Define a listener interface FieldChangeListener for field change tracking as:
public interface FieldChangeListener<T>{
/**
* Defining generically so that it can be implemented by any entity on which field change needs to be observed.
* #param propertyName - the property name on the entity
* #param entity - the entity object
* #param previousState - the old value
* #param currentState - the new value
* */
void onChange(Object[] previousState, Object[] currentState, String[] propertyName, Type[] types, Object entity);
}
In order to use said Interceptor, we need to create a class that extends EmptyInterceptor:
public class FieldChangeInterceptor extends EmptyInterceptor {
private Map<Class, FieldChangeListener<?>> listeners;
public void setListeners(Map<Class, FieldChangeListener<?>> listeners) {
this.listeners = listeners;
}
#Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
FieldChangeListener<?> listener = listeners.get(entity.getClass());
boolean report = false;
// Only check for changes if an entity-specific listener was registered.
if (listener != null) {
listener.onChange(previousState, currentState, propertyNames, types, entity);
report = true;
}
return report;
}
}
Define the interceptor and listener(s) in your context xml as:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
...
</bean>
<bean id="fieldInterceptor" class="package.FieldChangeInterceptor">
<property name="listeners">
<util:map id="listeners" map-class="java.util.HashMap">
<entry>
<key><value>package.Employee</value></key><ref bean="employeeListener"/>
</entry>
</util:map>
</property>
</bean>
<bean id="employeeListener" class="package.listener.EmployeeListener"/>
EmployeeListener class is:
public class EmployeeListener implements FieldChangeListener<Employee>, ApplicationEventPublisherAware{
private ApplicationEventPublisher publisher;
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
#Override
public void onChange(Object[] previousState, Object[] currentState, String[] propertyName, Type[] types, Object obj) {
log.info("Enter onChange()...");
Employee employee = (Employee)obj;
boolean isChange = false;
for (int i = 0; i < currentState.length; i++) {
if (currentState[i] == null) {
if (previousState[i] != null){
isChange = true;
}else{
isChange = false;
}
} else if (!currentState[i].equals(previousState[i])) {
isChange = true;
}
if (isChange) {
//check for status property of Employee
if(propertyName[i].equals("status")){
//Do your activity here - maybe you want to publish application event using eventService.
//Define event.
publisher.publishEvent(event);
}
report = false;
}
}
log.info("Exit onChange()...");
}
To publish a custom ApplicationEvent, call the publishEvent() method on an ApplicationEventPublisher. For more details on event publishing, please check
Section 5.14.2 Standard and Custom Events here.

How to suppress azure cache client warnings from the WAD logs

We have 2 roles which use a cache role to share data. When we deploy we get many many of the following entries in the logs:
INFORMATION: <CASClient> Updated partition table to (1-901) generation: 635036190744461419:0 with transfer (1-901) generation: 635036190744461419:0; TraceSource 'w3wp.exe' event
INFORMATION: <Complaint> Add hard complaint :0 ; TraceSource 'w3wp.exe' event
Changing the values of the setting:
<Setting name="Microsoft.WindowsAzure.Plugins.Caching.ClientDiagnosticLevel" value="0" />
seems to have no effect.
Any ideas how we can remove this noise from the WADLogs table?
It seems there is a bug in Caching (see this post). I tried to get rid of these log entries with no luck running SDK1.8. Recently I switched to SDK2.0 but unfortunately the problem is still not fixed.
Bug report on GitHub
I'm going to be adding a filter for this.
Sample for web.config:
<system.diagnostics>
<trace>
<listeners>
<add name="console" type="System.Diagnostics.ConsoleTraceListener">
<filter type="Namespace.TraceFilter, Assembly" initializeData="Information"/>
</add>
</listeners>
</trace>
</system.diagnostics>
Note: The attribute initializeData is set to the text from System.Diagnostics.SourceLevels enum. See here.
TraceFilter.cs
public class TraceFilter : EventTypeFilter
{
public TraceFilter(SourceLevels level)
: base(level) {}
public override bool ShouldTrace(TraceEventCache cache, string source, TraceEventType eventType, int id, string formatOrMessage, object[] args, object data1, object[] data)
{
return !Regex.IsMatch(formatOrMessage, "INFORMATION: <[^>*]*>");
}
}
You could extend this to a more generic filter which could run off a configuration accepting different patterns to include/ignore.
After reading a suggestion at the end of this thread on GitHub we managed to disable this by running the following code in the application:
DataCacheClientLogManager.ChangeLogLevel(TraceLevel.Off);
DataCacheClientLogManager.SetSink(DataCacheTraceSink.DiagnosticSink, TraceLevel.Off);
This stops all logging from the Azure Cache Client without you having to turn off your own Warning or Information level logs.
We ended up adding this in the constructor of our Cache Provider wrapper around the DataCacheClient:
public class AzureCacheProvider : ICacheProvider
{
public AzureCacheProvider()
{
DataCacheClientLogManager.ChangeLogLevel(TraceLevel.Off);
DataCacheClientLogManager.SetSink(
DataCacheTraceSink.DiagnosticSink,
TraceLevel.Off);
InitializeCache();
}
Here is a complete solution
Just make sure to use your namespace and corresponding assembly name.
using Microsoft.WindowsAzure.Diagnostics;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace MyNamespace
{
/*
Solves the Azure In-Role Cache client warnings bug which is too verbose in the WAD logs
Also Solves Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener not using Filter
For roles which uses in-role caching, configure your Web.config or app.config with the following system.diagnostics listner and filter:
<system.diagnostics>
<trace>
<listeners>
<add name="AzureDiagnostics" type="MyNamespace.FilteringDiagnosticMonitorTraceListener, MyAssemblyName">
<!-- WARNING: does not work with type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
because the DiagnosticMonitorTraceListener does not call the filter's ShouldTrace method as is was supposed to... -->
<!-- Note: working with type="System.Diagnostics.ConsoleTraceListener" -->
<filter type="MyNamespace.SuppressCacheClientWarningsTraceFilter, MyAssemblyName" initializeData="Information"/>
<!-- Note: The attribute initializeData is set to the text from System.Diagnostics.SourceLevels enum. -->
</add>
</listeners>
</trace>
</system.diagnostics>
*/
/// <summary>EventTypeFilter which suppress the 'noise' messages from the In-Role Azure Cache client
/// </summary>
/// <remarks>It's a workaround for the following problem http://social.msdn.microsoft.com/Forums/windowsazure/en-US/7ebbc44e-7b61-4bbe-aa54-a85a7788079f/complaint-add-hard-complaint?forum=windowsazuredata.
/// The solution is based on http://stackoverflow.com/questions/16443856/how-to-suppress-azure-cache-client-warnings-from-the-wad-logs and http://pastebin.com/qKc1aTTW
/// </remarks>
public class SuppressCacheClientWarningsTraceFilter : EventTypeFilter
{
public SuppressCacheClientWarningsTraceFilter(SourceLevels level)
: base(level) { }
public override bool ShouldTrace(TraceEventCache cache, string source, TraceEventType eventType, int id, string formatOrMessage, object[] args, object data1, object[] data)
{
return !(
(eventType == TraceEventType.Information && Regex.IsMatch(formatOrMessage, #"^INFORMATION:\ <(CASClient|Complaint)>"))
|| (eventType == TraceEventType.Warning && Regex.IsMatch(formatOrMessage, #"^WARNING:\ <SimpleSendReceiveModule>\ DeadServerCallback"))
);
//return !Regex.IsMatch(formatOrMessage, #"^INFORMATION: <[^>*]*>");
}
}
/// <summary>Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener which uses the configured Trace Filter
/// </summary>
/// <remarks>It's a workaround for the following problem http://social.msdn.microsoft.com/Forums/en-US/92ed1175-d6b7-4173-a224-0f7eb3e99481/diagnosticmonitortracelistener-ignors-filter?forum=windowsazuretroubleshooting
/// The solution is based on the thread comment from "Qin Dian Tang - MSFT": "If you need to use trace filter, then it is needed to use a custom trace listener which derives from DiagnosticMonitorTraceListener, override TraceData, and either manually check filters or call the root class's (TraceListener) TraceData."
/// </remarks>
public class FilteringDiagnosticMonitorTraceListener : DiagnosticMonitorTraceListener
{
public FilteringDiagnosticMonitorTraceListener() : base() { }
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
{
if (this.Filter == null || this.Filter.ShouldTrace(eventCache, source, eventType, id, format, args, null, null))
base.TraceEvent(eventCache, source, eventType, id, format, args);
}
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
{
if (this.Filter == null || this.Filter.ShouldTrace(eventCache, source, eventType, id, message, null, null, null))
base.TraceEvent(eventCache, source, eventType, id, message);
}
}
}
Hope it helps.

Struts2 handle session timeout using Interceptor

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.

Resources