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