-
Notifications
You must be signed in to change notification settings - Fork 832
fix logMissingProperties (WW-4999) #358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Moves checking OgnlValueStack.THROW_EXCEPTION_ON_FAILURE outside loop because it shouldn't throw exception on first failure while is trying all root objects. Returns on first successful call because it's not rational and is confusing user to skip when user method successfully returns null as an actual result. Fixes WW-4999 via honoring (devMode && logMissingProperties) for OgnlValueStack.THROW_EXCEPTION_ON_FAILURE and REPORT_ERRORS_ON_NO_PROP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we have to set both devMode
and logMissingProperties
true in order to enable logging? Could we just use logMissingProperties
for clarity?
} | ||
|
||
if (context.containsKey(OgnlValueStack.THROW_EXCEPTION_ON_FAILURE)) { | ||
throw new MethodFailedException(target, name, reason); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like previously this MethodFailedException
is thrown when the method is found but failed on execution. Now we are mixing up both cases "no such method" and "method failed" by throwing this exception outside the loop. Is this the expected behavior?
Also, shall we consider break the loop at the point we threw MethodFailedException
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, previously it was mixed up both cases also so it is same as previous in this behavior. The only difference for it is: Now it is outside of the loop. Being inside of the loop is a bug. I discovered it in #357 - For example OgnlValueStack.findValue(expr: "someProp", throwExceptionOnFailure: true)
always fail when top of stack doesn't have someProp
property while it must check also other objects in stack and fail if none of them had this property.
Also, shall we consider break the loop at the point we threw MethodFailedException?
I think no. AFAIK it must try on all of objects in stack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks ok to me, it makes sense to keep the mixed up behavior.
Regarding the exception previously at line 248, doesn't that break the loop already? I don't think that would be an issue even throwExceptionOnFailure
is set true. Because that is only thrown when the property is found, but an exception is thrown in application code (that's why reason != null
).
Do you propose to search down the stack anyway even we've already "hit"?
The only use case I can think of: we have more than one "hit" on stack, while first one throws runtime exception and second one execute smoothly and return -- yes, in this case we don't want to break the loop.
I'm not super familiar with the coding style and design pattern of ognl
, is that really what we want?
Hiding exceptions doesn't look quite right to me...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, it doesn't break the loop but previously it did, and I thought it was an issue when throwExceptionOnFailure
is set true because of casual failed tests #357 - for example it accidentally uncovered that XWorkConverter.getConversionErrorMessage
seems fails when user set throwExceptionOnFailure
to true, I think. right?
I just found all usages of OgnlValueStack.THROW_EXCEPTION_ON_FAILURE
and found all of them outside loop except here. Thanks for heads up for reason != null
, it makes sense! But now I'm surprised if reason != null
is a good decision to distinguish between property not found
and user app exception
then why #357 tests fail?! (see prev para) In #357 everything is same as before except throwExceptionOnFailure
which is hard-coded to true.
@apache/struts-committers, I agree with @qqu0127 , any objection? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My "vote" would be too keep devMode
and logMissingProperties
"linked" so that logMissingProperties
is only effective in devMode (reasoning given within the review).
if (!context.containsKey(OgnlValueStack.THROW_EXCEPTION_ON_FAILURE) && (mc != null) && (reason != null) && (reason.getClass() == NoSuchMethodException.class)) { | ||
if ((mc != null) && (reason != null) && (reason.getClass() == NoSuchMethodException.class)) { | ||
invalidMethods.put(mc, Boolean.TRUE); | ||
} else if (reason != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it certain that dropping the "if (reason != null)" check as part of the decision whether to throw the exception or not won't cause an issue ?
It looks like the old code would "skip" throwing an Exception in that case, no matter what THROW_EXCEPTION_ON_FAILURE
is set to ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @qqu0127 mentioned, it seems reason != null
tries to distinguish between property not found
and user app
exception but actually it doesn't work. As far as I could see and debug, OGNL always fill it here
in both NoSuchMethodException
(i.e. property not found) and InvocationTargetException
(i.e. user app exception).
I realized that this is an old issue, please see this test :
* Fails on 2.5.20 and earlier - tested on 2.5 (5/5/2016) and failed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, you are right. reason != null
itself is not a complete check. Actually, I fixed this in my workplace integrated with our logging system internally, based on 2.5.17. And it classifies the exceptions correctly. FYI, we modified ValueStack
. (we also changed CompoundRootAccessor
, but differently, so not sure if it works here). Just for reference, not super important.
@Override
public Object handleOgnlException(String expr, boolean throwExceptionOnFailure, OgnlException e) {
Object ret = findInContext(expr);
if (ret != null) {
return ret;
}
String errMsg = null;
if (shouldLogWarning()) {
if (e.getCause() == null || e.getCause() instanceof NoSuchMethodException) {
errMsg = String.format("Could not evaluate expression [%s]", expr);
}
else {
// exception in invoked method
errMsg = String.format("Caught an exception in evaluating expression [%s]", expr);
}
logStackTrace(errMsg, e);
}
if (throwExceptionOnFailure) {
throw new XWorkException(e, errMsg);
}
return null;
}
And
@Override
protected Object handleOtherException(String expr, boolean throwExceptionOnFailure, Exception e) {
if (shouldLogWarning()) {
if (e.getCause() == null) {
logStackTrace(String.format("Could not evaluate expression [%s]", expr), e);
}
else {
logStackTrace(String.format("Caught an exception in evaluating expression [%s]", expr), e);
}
}
if (throwExceptionOnFailure) {
throw new XWorkException(e);
}
return findInContext(expr);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not sure if we should remove elseif (reason != null)
. In my view it changes the flow too much (validity and performance), and it's irrelevant to the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should because with that following test fails. I tested and even Struts 2.5 (5/5/2016) with no change also fails on this test:
public void testNotThrowExceptionOnTopMissingProperty() { |
but this is and was a wrong behavior.
if ((mc != null) && (reason != null) && (reason.getClass() == NoSuchMethodException.class)) { | ||
invalidMethods.put(mc, Boolean.TRUE); | ||
} else if (reason != null) { | ||
throw new MethodFailedException(o, name, e.getReason()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was the old logic intentionally limiting the loop to 1 iteration (unless the exception reason was null
) ?
Previously it would seem things would only succeed if the method was defined on the object at the top of the stack.
Will "walking the whole valuestack" potentially introduce a different issue (unexpectedly) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was the old logic intentionally limiting the loop to 1 iteration (unless the exception reason was
null
) ?
Previously it would seem things would only succeed if the method was defined on the object at the top of the stack.
In case of throwExceptionOnFailure=true
, Yes, but it is a wrong behavior. Please see my test at
* Fails on 2.5.20 and earlier - tested on 2.5 (5/5/2016) and failed |
Will "walking the whole valuestack" potentially introduce a different issue (unexpectedly) ?
No, it is already current behavior in case of throwExceptionOnFailure=false
.
|
||
protected void setupExceptionOnFailure(boolean throwExceptionOnFailure) { | ||
if (throwExceptionOnFailure) { | ||
if (throwExceptionOnFailure || (devMode && logMissingProperties)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the main reason why devMode
and logMissingProperties
should continue to be "linked" (at least in 2.5.x). Most users probably only want to check for missing properties in devMode (development) anyway.
If logMissingProperties
becomes a "standalone flag", it could turn on THROW_EXCEPTION_ON_FAILURE
in a production configuration - and that could have greater repercussions than intended. Maybe it could be decoupled in 2.6 in a "safe way" ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throwExceptionOnFailure || logMissingProperties
also works even better. As far as I can see in codes, Struts will check them separately later to check throw any exception and log any message.
return null; | ||
} | ||
|
||
Throwable reason = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Oops ... forgot to include this comment in the review).
Assuming the loop logic will continue to process more than 1 iteration, I suggest a small optimization:
- Move Line 227:
Class[] argTypes = getArgTypes(objects);
-- to Line 219/220 (outside of the loop).
Should be safe as the argument types don't change across the loop iterations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 fixed
Would you mind adding a test case where an existing property throws a runtime exception? Such case should not be reported as missing property. Basically something has changed in struts <-> ognl flow and |
Also reaks loop on user method exceptions - but continue to next objects in stack on NoSuchMethodException.
Hi all, Thanks all for comments! I tried to fix all of them in a new commit above. The only weakness is when user method body itself throws @lukaszlenart , I realized these issues are old. I checked out Struts 2.5 (5/5/2016) and tested it against two issues. 1. @qqu0127 , I think in this new commit I was able to address your concerns. Could you check please? and thanks a tone for heads up! @JCgH4164838Gh792C124B5 , I'm working on your concerns :) |
instead continue to next objects in stack Also tests not skip returned null values by user method
I tested and finally found it more clear and nice in both test and production codes to decouple logMissingProperties from devMode so did it. |
} | ||
} | ||
|
||
public void testLogMissingProperties() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since testNotThrowExceptionOnTopMissingProperty()
added @since 2.5.21
, do you want the same annotation here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 fixed
} | ||
} | ||
|
||
public void testNotLogUserExceptionsAsMissingProperties() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And @since 2.5.21
here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 fixed
logger.removeAppender(testAppender); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yasserzamani , do you think adding equivalent tests testLogMissingPropertiesFalse()
and testNotLogUserExceptionsAsMissingPropertiesFalse()
would be a good idea ?
They could set vs.setLogMissingProperties("false");
and then confirm that nothing was logged. Might be a good additional check ?
As it stands the changes LGTM, but I think those two additional tests might be useful to catch a future change that accidentally causes the opposite behaviour (i.e. something gets logged when logMissingProperties
is false
, but should not).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @yasserzamani . Thanks for responding to the feedback and your excellent work on this PR. 👍 .
Thanks to @qqu0127 for opening the Jira issue and reviewing the PR's changes too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work. 👍
LGTM 👍 |
@yasserzamani do you plan cherry-pick those changes into 2.6? |
Thanks for thanks and 👍s :) @lukaszlenart , cherry picked into 2.6 and travis is green :) |
💯 |
Moves checking OgnlValueStack.THROW_EXCEPTION_ON_FAILURE outside loop because it shouldn't throw exception on first failure while is trying all root objects.
Returns on first successful call because it's not rational and is confusing user to skip when user method successfully returns null as an actual result.
Fixes WW-4999 via honoring (devMode && logMissingProperties) for OgnlValueStack.THROW_EXCEPTION_ON_FAILURE and REPORT_ERRORS_ON_NO_PROP.