[
https://issues.apache.org/jira/browse/FINERACT-2665?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18092339#comment-18092339
]
Berk Emir commented on FINERACT-2665:
-------------------------------------
Hi, I'd like to work on this one.
I've traced the root cause to
LoanScheduleAssembler#assembleLoanApplicationTermsFrom. For the override-gated
attributes (interestType, amortizationType, interestCalculationPeriodType,
repaymentEvery), the value is read from the request JSON and passed straight
into XxxMethod.fromInt(...) whenever the product allows overriding it — without
checking whether the attribute was actually present in the body. On a modify
request that omits the attribute, the extracted value is null, so e.g.
InterestMethod.fromInt(null) throws the NPE.
The fix is to fall back to the loan product's configured value when an
override-enabled attribute is omitted (value == null), instead of dereferencing
null. This is exactly how the grace/tolerance attributes in the same method
already behave (e.g. graceOnArrearsAgeing, inArrearsTolerance), so it just
makes the override-gated fields consistent with the existing pattern. I'll also
add an integration test that modifies a loan on an override-enabled product
while omitting the attribute and asserts it no longer 500s.
Could you please assign this to me? I'll open a PR shortly.
> PUT /loans/{loanId} (modify loan application) fails with a
> NullPointerException (HTTP 500) when a loan product allows attribute
> overrides but the request omits the attribute
> -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: FINERACT-2665
> URL: https://issues.apache.org/jira/browse/FINERACT-2665
> Project: Apache Fineract
> Issue Type: Bug
> Components: Loan
> Reporter: Farooq Ayoade
> Priority: Minor
> Labels: beginner, beginner-friendly, bug, loan
>
> h3. Observed behavior
> Modifying a submitted-and-pending loan application via {{{}PUT
> /fineract-provider/api/v1/loans/{loanId{}}}} — when the change triggers a
> schedule recalculation (e.g. changing {{{}productId{}}}) and the request body
> does not resend {{interestType}} — returns HTTP 500 with a raw NPE:
>
>
> {{java.lang.NullPointerException: Cannot invoke
> "java.lang.Integer.intValue()" because "selectedMethod" is null
> at
> org.apache.fineract.portfolio.loanproduct.domain.InterestMethod.fromInt(InterestMethod.java:45)
> at
> org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler.assembleLoanApplicationTermsFrom(LoanScheduleAssembler.java:238)
> at
> org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler.assembleLoanTerms(LoanScheduleAssembler.java:178)
> at
> org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler.assembleLoanScheduleFrom(LoanScheduleAssembler.java:711)
> at
> org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformServiceImpl.calculateLoanSchedule(LoanScheduleCalculationPlatformServiceImpl.java:79)
> at
> org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl.updateFrom(LoanAssemblerImpl.java:866)
> at
> org.apache.fineract.portfolio.loanaccount.service.LoanApplicationWritePlatformServiceJpaRepositoryImpl.modifyApplication(LoanApplicationWritePlatformServiceJpaRepositoryImpl.java:277)}}
> The same class of NPE occurs for {{amortizationType}} and
> {{{}interestCalculationPeriodType{}}}, and a null {{repaymentEvery}} is
> produced, when those overrides are enabled on the product and the field is
> omitted.
> h3. Expected behavior
> "Allow overriding X" on a loan product means the override is {*}optional{*}.
> When the modify request omits an override-enabled attribute, the schedule
> should be (re)built using the *product's configured value* for that attribute
> — exactly as it already does when the override is _not_ enabled — instead of
> dereferencing a null and throwing.
> h3. Steps to reproduce
> # Have a loan product with one or more attribute overrides enabled — i.e. a
> row in {{m_product_loan_configurable_attributes}} with {{interest_method_enum
> = 1}} (and/or {{{}amortization_method_enum{}}},
> {{{}interest_calculated_in_period_enum{}}}, {{{}repay_every{}}}). A product
> with *no* row also reproduces, because
> {{LoanProductConfigurableAttributes.populateDefaultsForConfigurableAttributes()}}
> defaults every flag to {{{}true{}}}.
> # Create a loan application (Submitted and pending approval) on some product.
> # {{{}PUT /fineract-provider/api/v1/loans/{loanId{}}}} changing
> {{productId}} to an override-enabled product, with a minimal body that omits
> {{{}interestType{}}}:
> \{{ { "locale": "en", "dateFormat": "dd MMMM yyyy", "loanType":
> "individual", "productId": <overrideEnabledId> }
> }}
> # Observe HTTP 500 and the NPE above. (Re-sending the loan's _current_
> product is a no-op — {{{}changes:{}{{}}}}, no recalculation — so it appears
> to "work"; the failure only manifests once a real change forces the schedule
> rebuild.)
> h3. Root cause
> {{LoanScheduleAssembler.assembleLoanApplicationTermsFrom(...)}} reads each
> override-gated attribute from the request JSON when the product allows
> overriding it, but passes the (possibly {{{}null{}}}) value straight into
> {{{}XxxMethod.fromInt(...){}}}:
> final Boolean allowOverridingInterestMethod =
> loanProduct.getLoanConfigurableAttributes().getInterestMethodBoolean();
> final Integer interestType =
> this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType",
> element); // null on modify when omitted
> final InterestMethod interestMethod = allowOverridingInterestMethod
> ? InterestMethod.fromInt(interestType) // line 238 — fromInt(null)
> → NPE
> : loanProduct.getLoanProductRelatedDetail().getInterestMethod();
> {{}}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)