Nikolaos (JIRA)
14 years ago
MockRoundtrip:: trip.getActionBean() returns NULL 1st run but OK 2nd+
---------------------------------------------------------------------
Key: STS-803
URL: http://www.stripesframework.org/jira/browse/STS-803
Project: Stripes
Issue Type: Bug
Reporter: Nikolaos
While the issue outlined below is "Minor" there are 2 reasons why I am flagging this as "Major":
(1) as it affects testing it does impede normal testing in that an expected non-null return of trip.getActionBean() and subsequent assertion failure test can not be made... so test code needs to be padded to expect and treat an error case as a normal case... problematic but yes not the end of the world
(2) but the bigger reason is b/c Stripes UrlBinding mechanism determines one set of bindings when the Stripes initializes... and then as Stripes runs modifies those bindings... which is cause for concern / alarm as this may have adverse side effects on non-test / production code... i.e. I would expect Stripes to initialize the bindings properly from the get go and service all requests uniformly (also I am not so sure these changes occur at a specified point in time i.e. I don't think they necessarily occur after the 1st hit of a given UrlBinding and I also wonder how all other bindings are affected... in any event it may be harmless but at this point appears a little unsettling ;-)
What follows is the e-mail sent to the list verbatim:
======================================
This issue is very bizarre and VERY interesting indeed.
I run 2 "identical" TestNG tests in a unit test - back to back - and the 1st fails and the 2nd succeeds:
@DataProvider(name = "userIssueRequests")
public Object[][] userIssueRequests() {
return new Object[][] {
{ ShareActionBean.class, "en_US", "/share/3gi/_/edit", "/share/3gi/_/view" },
{ ShareActionBean.class, "en_US", "/share/3gi/_/edit", "/share/3gi/_/view" },
};
}
ShareActionBean has the following URL binding:
@UrlBinding("/share/{uuidRadix}/{titleUrlified}/{$event}")
And the following default handler:
@DefaultHandler
@DontValidate
public final Resolution view() {
return (super.view(VIEW));
}
The test involves the following (relevant) code:
MockRoundtrip trip = new MockRoundtrip(context, actionBeanUrl, new MockHttpSession(context));
trip.execute();
ShareActionBean bean = trip.getActionBean(ShareActionBean.class);
In BOTH cases the ShareActionBean is created and the "edit" handler (as per URL) is invoked. All is well... so far...
However, In the 1st run bean is NULL and in the 2nd run bean returns the action bean that handled the request... HUH????
So, I did some debugging... if we drill down into trip.getActionBean(...) we find the following method:
public <A extends ActionBean> A getActionBean(Class<A> type) {
A bean = (A) this.request.getAttribute(getUrlBinding(type, this.context));
if (bean == null) {
bean = (A) this.request.getSession().getAttribute(getUrlBinding(type, this.context));
}
return bean;
}
Here is where it gets REALLY interesting - if we simply focus on the 1st line of the method call:
A bean = (A) this.request.getAttribute(getUrlBinding(type, this.context));
We find that getUrlBinding(type, this.context) returns the following as the key to the outer method call:
/share/{uuidRadix}/{titleUrlified}/{$event=view}
And in the 1st run this.request.getAttribute(...) has a map that contains:
__stripes_resolved_action=/share/{uuidRadix}/{titleUrlified}/{$event},
/share/{uuidRadix}/{titleUrlified}/{$event}=***@10fd9d27, actionBean=***@10fd9d27, __current_flash_scope=-1806527276, _stripes_defaultMessages=[***@779dc644]}
whereas in the 2nd run this.request.getAttribute(...) has a map that contains:
__stripes_resolved_action=/share/{uuidRadix}/{titleUrlified}/{$event=view},
/share/{uuidRadix}/{titleUrlified}/{$event=view}=***@44dd7637, actionBean=***@44dd7637, __current_flash_scope=-624278302, _stripes_defaultMessages=[***@779dc644]}
How can Stripes produce 2 different URL bindings for the same ActionBean in the same run????
This behaviour is not only odd but worrisome if there is a bug at work here that may affect non-Test code.
Now what is more interesting is that the ShareActionBean is setup with the Application logic such that if user is trying to change a Share that they do not have access to that they are redirected to the default handler with a message i.e.:
/share/3gi/_/view?__fsk=-1806527276
So we have the following things happening:
(A) Stripes UrlBinding's are determined when Stripes fires up and receives an initial request. At this point the ShareActionBean is referenced to have cached UrlBinding of:
/share/{uuidRadix}/{titleUrlified}/{$event}
(B) Run #1 - we test the request to "/share/3gi/_/edit" and the ShareActionBean "edit" event handler redirects to "/share" with params "3gi" and "_" and as such no explicitly specified "event" handler so the default one should be determined by Stripes.
But that causes UrlBindingParameter.getDefaultValue() to execute to determine that the "event" value for this action bean is the @DefaultHandler method "view"
At this point though this changes the UrlBinding component for ShareActionBean to have "event" with defaultValue = "view" and moreover the ShareActionBean cached UrlBinding from this point forward is:
/share/{uuidRadix}/{titleUrlified}/{$event=view}
(C) However when the trip.getActionBean(...) method fires a lookup for the request attribute the key it got initially is used:
/share/{uuidRadix}/{titleUrlified}/{$event}
And of course this fails and no ActionBean is returned b/c this no longer maps to any cached UrlBinding.
(D) When Run #2 comes along and does the same thing the same thing occurs as in Run #1 except from the start this time though the cached UrlBinding is:
/share/{uuidRadix}/{titleUrlified}/{$event=view}
And nothing changes and all is well in that the trip.getActionBean(...) method return the ShareActionBean instance
So it appears that as long as an explicit "event" parameter is supplied the getDefaultValue() method will not get called on the "event" UrlBindingParameter and until then the binding of the ActionBean will not have the default value embedded in it for "event" but once a default event URL is produced that bindings will appear differently.
Shouldn't Stripes ensure from the get-go try to trigger the getDefaultValue() for "event" to have it incorporated in the UrlBinding cached????
What a ride!!!!
--Nikolaos
UrlBindingParameter.java:
public String getDefaultValue() {
// for $event parameters with no explicit default value, get default from action resolver
if (this.defaultValue == null && PARAMETER_NAME_EVENT.equals(name)) {
try {
Method defaultHandler = StripesFilter.getConfiguration().getActionResolver()
.getDefaultHandler(beanClass);
HandlesEvent annotation = defaultHandler.getAnnotation(HandlesEvent.class);
if (annotation != null)
this.defaultValue = annotation.value();
else
this.defaultValue = defaultHandler.getName();
}
catch (Exception e) {
/* Ignore any exceptions and just return null. */
}
}
return defaultValue;
}
---------------------------------------------------------------------
Key: STS-803
URL: http://www.stripesframework.org/jira/browse/STS-803
Project: Stripes
Issue Type: Bug
Reporter: Nikolaos
While the issue outlined below is "Minor" there are 2 reasons why I am flagging this as "Major":
(1) as it affects testing it does impede normal testing in that an expected non-null return of trip.getActionBean() and subsequent assertion failure test can not be made... so test code needs to be padded to expect and treat an error case as a normal case... problematic but yes not the end of the world
(2) but the bigger reason is b/c Stripes UrlBinding mechanism determines one set of bindings when the Stripes initializes... and then as Stripes runs modifies those bindings... which is cause for concern / alarm as this may have adverse side effects on non-test / production code... i.e. I would expect Stripes to initialize the bindings properly from the get go and service all requests uniformly (also I am not so sure these changes occur at a specified point in time i.e. I don't think they necessarily occur after the 1st hit of a given UrlBinding and I also wonder how all other bindings are affected... in any event it may be harmless but at this point appears a little unsettling ;-)
What follows is the e-mail sent to the list verbatim:
======================================
This issue is very bizarre and VERY interesting indeed.
I run 2 "identical" TestNG tests in a unit test - back to back - and the 1st fails and the 2nd succeeds:
@DataProvider(name = "userIssueRequests")
public Object[][] userIssueRequests() {
return new Object[][] {
{ ShareActionBean.class, "en_US", "/share/3gi/_/edit", "/share/3gi/_/view" },
{ ShareActionBean.class, "en_US", "/share/3gi/_/edit", "/share/3gi/_/view" },
};
}
ShareActionBean has the following URL binding:
@UrlBinding("/share/{uuidRadix}/{titleUrlified}/{$event}")
And the following default handler:
@DefaultHandler
@DontValidate
public final Resolution view() {
return (super.view(VIEW));
}
The test involves the following (relevant) code:
MockRoundtrip trip = new MockRoundtrip(context, actionBeanUrl, new MockHttpSession(context));
trip.execute();
ShareActionBean bean = trip.getActionBean(ShareActionBean.class);
In BOTH cases the ShareActionBean is created and the "edit" handler (as per URL) is invoked. All is well... so far...
However, In the 1st run bean is NULL and in the 2nd run bean returns the action bean that handled the request... HUH????
So, I did some debugging... if we drill down into trip.getActionBean(...) we find the following method:
public <A extends ActionBean> A getActionBean(Class<A> type) {
A bean = (A) this.request.getAttribute(getUrlBinding(type, this.context));
if (bean == null) {
bean = (A) this.request.getSession().getAttribute(getUrlBinding(type, this.context));
}
return bean;
}
Here is where it gets REALLY interesting - if we simply focus on the 1st line of the method call:
A bean = (A) this.request.getAttribute(getUrlBinding(type, this.context));
We find that getUrlBinding(type, this.context) returns the following as the key to the outer method call:
/share/{uuidRadix}/{titleUrlified}/{$event=view}
And in the 1st run this.request.getAttribute(...) has a map that contains:
__stripes_resolved_action=/share/{uuidRadix}/{titleUrlified}/{$event},
/share/{uuidRadix}/{titleUrlified}/{$event}=***@10fd9d27, actionBean=***@10fd9d27, __current_flash_scope=-1806527276, _stripes_defaultMessages=[***@779dc644]}
whereas in the 2nd run this.request.getAttribute(...) has a map that contains:
__stripes_resolved_action=/share/{uuidRadix}/{titleUrlified}/{$event=view},
/share/{uuidRadix}/{titleUrlified}/{$event=view}=***@44dd7637, actionBean=***@44dd7637, __current_flash_scope=-624278302, _stripes_defaultMessages=[***@779dc644]}
How can Stripes produce 2 different URL bindings for the same ActionBean in the same run????
This behaviour is not only odd but worrisome if there is a bug at work here that may affect non-Test code.
Now what is more interesting is that the ShareActionBean is setup with the Application logic such that if user is trying to change a Share that they do not have access to that they are redirected to the default handler with a message i.e.:
/share/3gi/_/view?__fsk=-1806527276
So we have the following things happening:
(A) Stripes UrlBinding's are determined when Stripes fires up and receives an initial request. At this point the ShareActionBean is referenced to have cached UrlBinding of:
/share/{uuidRadix}/{titleUrlified}/{$event}
(B) Run #1 - we test the request to "/share/3gi/_/edit" and the ShareActionBean "edit" event handler redirects to "/share" with params "3gi" and "_" and as such no explicitly specified "event" handler so the default one should be determined by Stripes.
But that causes UrlBindingParameter.getDefaultValue() to execute to determine that the "event" value for this action bean is the @DefaultHandler method "view"
At this point though this changes the UrlBinding component for ShareActionBean to have "event" with defaultValue = "view" and moreover the ShareActionBean cached UrlBinding from this point forward is:
/share/{uuidRadix}/{titleUrlified}/{$event=view}
(C) However when the trip.getActionBean(...) method fires a lookup for the request attribute the key it got initially is used:
/share/{uuidRadix}/{titleUrlified}/{$event}
And of course this fails and no ActionBean is returned b/c this no longer maps to any cached UrlBinding.
(D) When Run #2 comes along and does the same thing the same thing occurs as in Run #1 except from the start this time though the cached UrlBinding is:
/share/{uuidRadix}/{titleUrlified}/{$event=view}
And nothing changes and all is well in that the trip.getActionBean(...) method return the ShareActionBean instance
So it appears that as long as an explicit "event" parameter is supplied the getDefaultValue() method will not get called on the "event" UrlBindingParameter and until then the binding of the ActionBean will not have the default value embedded in it for "event" but once a default event URL is produced that bindings will appear differently.
Shouldn't Stripes ensure from the get-go try to trigger the getDefaultValue() for "event" to have it incorporated in the UrlBinding cached????
What a ride!!!!
--Nikolaos
UrlBindingParameter.java:
public String getDefaultValue() {
// for $event parameters with no explicit default value, get default from action resolver
if (this.defaultValue == null && PARAMETER_NAME_EVENT.equals(name)) {
try {
Method defaultHandler = StripesFilter.getConfiguration().getActionResolver()
.getDefaultHandler(beanClass);
HandlesEvent annotation = defaultHandler.getAnnotation(HandlesEvent.class);
if (annotation != null)
this.defaultValue = annotation.value();
else
this.defaultValue = defaultHandler.getName();
}
catch (Exception e) {
/* Ignore any exceptions and just return null. */
}
}
return defaultValue;
}
--
This message is automatically generated by JIRA.
-
For more information on JIRA, see: http://www.atlassian.com/software/jira
This message is automatically generated by JIRA.
-
For more information on JIRA, see: http://www.atlassian.com/software/jira