[ 
https://issues.apache.org/jira/browse/FINERACT-2657?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Farooq Ayoade updated FINERACT-2657:
------------------------------------
    Summary: PUT /provisioningcriteria/{id} fails with a NullPointerException 
(HTTP 500) because the definition update matches by a null surrogate id  (was: 
Updating a provisioning criteria fails with a NullPointerException (HTTP 500))

> 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{}}}):
>  
> !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(...){}}}.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to