[
https://issues.apache.org/jira/browse/FINERACT-2657?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Farooq Ayoade updated FINERACT-2657:
------------------------------------
Description:
h3. Observed behavior
Updating a loan-loss provisioning criteria returns HTTP 500 with a raw NPE:
java.lang.NullPointerException: Cannot invoke "java.lang.Long.equals(Object)"
because the return value of
"org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaDefinitionData.getId()"
is null
at
org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria.update(ProvisioningCriteria.java:119)
h3. Expected behavior
* Updating an existing provisioning criteria (changing a definition's
{{minAge}} / {{maxAge}} / {{provisioningPercentage}} / GL accounts) succeeds.
* A definition whose {{categoryId}} does not exist on the criteria returns a
clean *400* validation error, not an NPE / 500.
h3. Steps to reproduce
# Create a provisioning criteria: {{POST
/fineract-provider/api/v1/provisioningcriteria}} with a {{definitions}} array
(one entry per provisioning category). This persists definitions and JPA
assigns each a surrogate {{{}id{}}}.
# Update it: {{{}PUT
/fineract-provider/api/v1/provisioningcriteria/{criteriaId{}}}} with the *same
documented payload shape* — each {{definitions[]}} entry keyed by
{{categoryId}} (plus {{{}minAge{}}}, {{{}maxAge{}}},
{{{}provisioningPercentage{}}}, {{{}liabilityAccount{}}},
{{{}expenseAccount{}}}):
\{{ { "locale": "en", "criteriaName": "Standard", "loanProducts": [ \{ "id": 1
}
],
"definitions": [
{ "categoryId": 1, "minAge": 0, "maxAge": 30, "provisioningPercentage": 0,
"liabilityAccount": 5, "expenseAccount": 12 }
]
}}}
# Observe HTTP 500 and the NPE above.
h3. Root cause
The UPDATE path matches each incoming definition to an existing one by the
{*}surrogate {{id}}{*}, but the public payload never carries a per-definition
{{id}} — definitions are keyed by {{{}categoryId{}}}.
{{ProvisioningCriteria.update(...)}}
({{{}org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria{}}}):
{{public void update(ProvisioningCriteriaDefinitionData data, GLAccount
liability, GLAccount expense) {
for (ProvisioningCriteriaDefinition def : provisioningCriteriaDefinition) {
if (data.getId().equals(def.getId()))
{ // line 119 — data.getId() is null → NPE def.update(data.getMinAge(),
data.getMaxAge(), data.getProvisioningPercentage(), liability, expense); break;
}
}
}}}
{{data.getId()}} is null because, in
{{{}ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.updateProvisioningCriteriaDefinitions(...){}}}:
{{Long id = this.fromApiJsonHelper.extractLongNamed("id", jsonObject); // null
— payload has no per-definition id
Long categoryId =
this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_CATEOGRYID_PARAM,
jsonObject);
...
ProvisioningCriteriaDefinitionData data = new
ProvisioningCriteriaDefinitionData().setId(id) // id = null
.setCategoryId(categoryId) ...;
provisioningCriteria.update(data, liabilityAccount, expenseAccount); // → NPE
at update() line 119}}
And the update deserializer
({{{}ProvisioningCriteriaDefinitionJsonDeserializer.validateForUpdate(...){}}})
never reads or requires a per-definition {{id}} — it validates only
{{{}categoryId{}}}, {{{}minAge{}}}, {{{}maxAge{}}},
{{{}provisioningPercentage{}}}, {{{}liabilityAccount{}}},
{{{}expenseAccount{}}}. There is no {{id}} JSON constant for a definition in
{{{}ProvisioningCriteriaConstants{}}}. So the contract is keyed by
{{{}categoryId{}}}, but the entity-update matches by {{{}id{}}}. (CREATE
doesn't hit this: it builds new {{ProvisioningCriteriaDefinition}} entities and
lets JPA assign ids.)
{{categoryId}} is unique per criteria (one definition per provisioning
category), so it is the correct natural key to match on.
h3. Affected code
*
{{org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria#update(...)}}
— line 119 derefs a null {{{}data.getId(){}}}.
*
{{org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl#updateProvisioningCriteriaDefinitions(...)}}
— extracts a non-existent {{"id"}} from the payload (always null) and feeds it
to {{{}update(...){}}}.
was:
h3. Observed behavior
Updating a loan-loss provisioning criteria returns HTTP 500 with a raw NPE:
java.lang.NullPointerException: Cannot invoke "java.lang.Long.equals(Object)"
because the return value of
"org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaDefinitionData.getId()"
is null
at
org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria.update(ProvisioningCriteria.java:119)
h3. Expected behavior
* Updating an existing provisioning criteria (changing a definition's
{{minAge}} / {{maxAge}} / {{provisioningPercentage}} / GL accounts) succeeds.
* A definition whose {{categoryId}} does not exist on the criteria returns a
clean *400* validation error, not an NPE / 500.
h3. Steps to reproduce
# Create a provisioning criteria: {{POST
/fineract-provider/api/v1/provisioningcriteria}} with a {{definitions}} array
(one entry per provisioning category). This persists definitions and JPA
assigns each a surrogate {{{}id{}}}.
# Update it: {{PUT
/fineract-provider/api/v1/provisioningcriteria/\{criteriaId}}} with the *same
documented payload shape* — each {{definitions[]}} entry keyed by
{{categoryId}} (plus {{{}minAge{}}}, {{{}maxAge{}}},
{{{}provisioningPercentage{}}}, {{{}liabilityAccount{}}},
{{{}expenseAccount{}}}):
!http://localhost:63342/markdownPreview/402368850/custom-guide/core-tickets!
{{{
"locale": "en",
"criteriaName": "Standard",
"loanProducts": [ \{ "id": 1 } ],
"definitions": [
\{ "categoryId": 1, "minAge": 0, "maxAge": 30, "provisioningPercentage": 0,
"liabilityAccount": 5, "expenseAccount": 12 }
]
}}}
# Observe HTTP 500 and the NPE above.
h3. Root cause
The UPDATE path matches each incoming definition to an existing one by the
{*}surrogate {{id}}{*}, but the public payload never carries a per-definition
{{id}} — definitions are keyed by {{{}categoryId{}}}.
{{ProvisioningCriteria.update(...)}}
({{{}org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria{}}}):
{{public void update(ProvisioningCriteriaDefinitionData data, GLAccount
liability, GLAccount expense) \{
for (ProvisioningCriteriaDefinition def : provisioningCriteriaDefinition) {
if (data.getId().equals(def.getId())) { // line 119 — data.getId() is
null → NPE
def.update(data.getMinAge(), data.getMaxAge(),
data.getProvisioningPercentage(), liability, expense);
break;
}
}
}}}
{{data.getId()}} is null because, in
{{{}ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.updateProvisioningCriteriaDefinitions(...){}}}:
{{Long id = this.fromApiJsonHelper.extractLongNamed("id", jsonObject);
// null — payload has no per-definition id
Long categoryId =
this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_CATEOGRYID_PARAM,
jsonObject);
...
ProvisioningCriteriaDefinitionData data = new
ProvisioningCriteriaDefinitionData().setId(id) // id = null
.setCategoryId(categoryId) ...;
provisioningCriteria.update(data, liabilityAccount, expenseAccount);
// → NPE at update() line 119}}
And the update deserializer
({{{}ProvisioningCriteriaDefinitionJsonDeserializer.validateForUpdate(...){}}})
never reads or requires a per-definition {{id}} — it validates only
{{{}categoryId{}}}, {{{}minAge{}}}, {{{}maxAge{}}},
{{{}provisioningPercentage{}}}, {{{}liabilityAccount{}}},
{{{}expenseAccount{}}}. There is no {{id}} JSON constant for a definition in
{{{}ProvisioningCriteriaConstants{}}}. So the contract is keyed by
{{{}categoryId{}}}, but the entity-update matches by {{{}id{}}}. (CREATE
doesn't hit this: it builds new {{ProvisioningCriteriaDefinition}} entities and
lets JPA assign ids.)
{{categoryId}} is unique per criteria (one definition per provisioning
category), so it is the correct natural key to match on.
h3. Affected code
*
{{org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria#update(...)}}
— line 119 derefs a null {{{}data.getId(){}}}.
*
{{org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl#updateProvisioningCriteriaDefinitions(...)}}
— extracts a non-existent {{"id"}} from the payload (always null) and feeds it
to {{{}update(...){}}}.
> PUT /provisioningcriteria/{id} fails with a NullPointerException (HTTP 500)
> because the definition update matches by a null surrogate id
> ----------------------------------------------------------------------------------------------------------------------------------------
>
> Key: FINERACT-2657
> URL: https://issues.apache.org/jira/browse/FINERACT-2657
> Project: Apache Fineract
> Issue Type: Bug
> Components: Accounting
> Reporter: Farooq Ayoade
> Priority: Minor
>
> h3. Observed behavior
> Updating a loan-loss provisioning criteria returns HTTP 500 with a raw NPE:
>
> java.lang.NullPointerException: Cannot invoke "java.lang.Long.equals(Object)"
> because the return value of
> "org.apache.fineract.organisation.provisioning.data.ProvisioningCriteriaDefinitionData.getId()"
> is null
> at
> org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria.update(ProvisioningCriteria.java:119)
> h3. Expected behavior
> * Updating an existing provisioning criteria (changing a definition's
> {{minAge}} / {{maxAge}} / {{provisioningPercentage}} / GL accounts) succeeds.
> * A definition whose {{categoryId}} does not exist on the criteria returns a
> clean *400* validation error, not an NPE / 500.
> h3. Steps to reproduce
> # Create a provisioning criteria: {{POST
> /fineract-provider/api/v1/provisioningcriteria}} with a {{definitions}} array
> (one entry per provisioning category). This persists definitions and JPA
> assigns each a surrogate {{{}id{}}}.
> # Update it: {{{}PUT
> /fineract-provider/api/v1/provisioningcriteria/{criteriaId{}}}} with the
> *same documented payload shape* — each {{definitions[]}} entry keyed by
> {{categoryId}} (plus {{{}minAge{}}}, {{{}maxAge{}}},
> {{{}provisioningPercentage{}}}, {{{}liabilityAccount{}}},
> {{{}expenseAccount{}}}):
>
> \{{ { "locale": "en", "criteriaName": "Standard", "loanProducts": [ \{ "id":
> 1 }
> ],
> "definitions": [
> { "categoryId": 1, "minAge": 0, "maxAge": 30, "provisioningPercentage": 0,
> "liabilityAccount": 5, "expenseAccount": 12 }
> ]
> }}}
> # Observe HTTP 500 and the NPE above.
> h3. Root cause
> The UPDATE path matches each incoming definition to an existing one by the
> {*}surrogate {{id}}{*}, but the public payload never carries a per-definition
> {{id}} — definitions are keyed by {{{}categoryId{}}}.
> {{ProvisioningCriteria.update(...)}}
> ({{{}org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria{}}}):
>
> {{public void update(ProvisioningCriteriaDefinitionData data, GLAccount
> liability, GLAccount expense) {
> for (ProvisioningCriteriaDefinition def : provisioningCriteriaDefinition) {
> if (data.getId().equals(def.getId()))
> { // line 119 — data.getId() is null → NPE def.update(data.getMinAge(),
> data.getMaxAge(), data.getProvisioningPercentage(), liability, expense);
> break; }
> }
> }}}
> {{data.getId()}} is null because, in
> {{{}ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl.updateProvisioningCriteriaDefinitions(...){}}}:
>
> {{Long id = this.fromApiJsonHelper.extractLongNamed("id", jsonObject); //
> null — payload has no per-definition id
> Long categoryId =
> this.fromApiJsonHelper.extractLongNamed(ProvisioningCriteriaConstants.JSON_CATEOGRYID_PARAM,
> jsonObject);
> ...
> ProvisioningCriteriaDefinitionData data = new
> ProvisioningCriteriaDefinitionData().setId(id) // id = null
> .setCategoryId(categoryId) ...;
> provisioningCriteria.update(data, liabilityAccount, expenseAccount); // → NPE
> at update() line 119}}
> And the update deserializer
> ({{{}ProvisioningCriteriaDefinitionJsonDeserializer.validateForUpdate(...){}}})
> never reads or requires a per-definition {{id}} — it validates only
> {{{}categoryId{}}}, {{{}minAge{}}}, {{{}maxAge{}}},
> {{{}provisioningPercentage{}}}, {{{}liabilityAccount{}}},
> {{{}expenseAccount{}}}. There is no {{id}} JSON constant for a definition in
> {{{}ProvisioningCriteriaConstants{}}}. So the contract is keyed by
> {{{}categoryId{}}}, but the entity-update matches by {{{}id{}}}. (CREATE
> doesn't hit this: it builds new {{ProvisioningCriteriaDefinition}} entities
> and lets JPA assign ids.)
> {{categoryId}} is unique per criteria (one definition per provisioning
> category), so it is the correct natural key to match on.
> h3. Affected code
> *
> {{org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteria#update(...)}}
> — line 119 derefs a null {{{}data.getId(){}}}.
> *
> {{org.apache.fineract.organisation.provisioning.service.ProvisioningCriteriaWritePlatformServiceJpaRepositoryImpl#updateProvisioningCriteriaDefinitions(...)}}
> — extracts a non-existent {{"id"}} from the payload (always null) and feeds
> it to {{{}update(...){}}}.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)