[ 
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)

Reply via email to