[
https://issues.apache.org/jira/browse/FINERACT-2657?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Adam Saghy resolved FINERACT-2657.
----------------------------------
Fix Version/s: 1.15.0
Resolution: Fixed
> 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
> Fix For: 1.15.0
>
>
> 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)