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

Foo Developer updated FINERACT-2865:
------------------------------------
    Description: 
h3. Summary

Reading calendars fails with a {{NullPointerException}} as soon as the entity 
has a *non-repeating* calendar. A non-repeating calendar is stored with 
{{recurrence = NULL}} (by design, see {{Calendar}} constructor and 
{{Calendar.update}}), but the row mappers in 
{{CalendarReadPlatformServiceImpl}} pass that null rule straight into 
{{CalendarUtils}}, which hands it to ical4j's {{RRule}} parser.

h3. Steps to reproduce (stock Fineract)
# Create a group: {{POST /v1/groups}} (any office, active).
# Add a non-repeating, non-collection calendar to it:

{code:json}POST /v1/groups/{groupId}/calendars
{ "title": "One-off meeting", "typeId": 4, "repeating": false, "startDate": "28 
September 2026", "locale": "en", "dateFormat": "dd MMMM yyyy" }

{code}
# Read it back: {{GET /v1/groups/{groupId}/calendars/{calendarId}}}, or list 
{{GET /v1/groups/{groupId}/calendars}}. The same mapper also backs other 
calendar reads, e.g. {{GET 
/v1/loans/template?templateType=jlgbulk&groupId={groupId}}}.
# Edit the calendar (e.g. only its title) with {{PUT 
/v1/groups/{groupId}/calendars/{calendarId}}} and read it again.

h3. Expected

HTTP 200. The calendar is returned with {{repeating: false}}, {{interval: 0}}, 
no {{frequency}} / {{repeatsOnDay}} value and {{repeatsOnNthDayOfMonth}} set to 
the "invalid" enum option ({{{"code": "nthDayType.invalid"}}}), the same 
defaults already used for a rule that cannot be parsed. The same holds after 
the calendar has been edited (e.g. only its title), which writes an 
{{m_calendar_history}} row, and for a calendar switched from non-repeating to 
repeating.

h3. Actual

The request fails with HTTP 500 ({{Internal Server Error}}); the server log 
shows a {{NullPointerException}} ({{Cannot invoke "String.split(String)" 
because "aValue" is null}}) thrown from {{CalendarUtils.getICalRecur}} (ical4j 
{{RRule}} with a null rule string), reached from 
{{CalendarReadPlatformServiceImpl$CalendarDataMapper.mapRow}}.

h3. Root cause

Both {{CalendarDataMapper}} and {{CalendarDataFromHistoryMapper}} call 
{{CalendarUtils.getFrequency}}, {{getInterval}}, {{getRepeatsOnDay}}, 
{{getRepeatsOnNthDayOfMonth}} and {{getMonthOnDay}} unconditionally. Each of 
them parses the rule with {{getICalRecur(recurrence)}}, which cannot handle 
{{null}}. The neighbouring {{humanReadable}} computation in the same mappers 
already guards with {{recurrence != null}}; the other five calls do not.

A second path fails once the calendar has history: 
{{CalendarReadPlatformServiceImpl.generateRecurringDates}} starts from 
{{generateRecurringDate(calendar)}} and adds {{generateRecurringDate(history)}} 
for each history row, but {{generateRecurringDate}} returns {{null}} for a 
non-repeating calendar. An edited non-repeating calendar therefore throws an 
NPE on {{null.addAll(...)}}, and a calendar switched from non-repeating to 
repeating throws on {{addAll(null)}} for its old history row.

h3. Proposed fix

Guard those five calls with {{recurrence != null}} in both mappers and fall 
back to {{CalendarFrequencyType.INVALID}}, {{0}}, 
{{CalendarWeekDaysType.INVALID}}, {{NthDayType.INVALID}} and {{null}} (which 
{{CalendarEnumerations}} already renders as "no value"). Repeating calendars 
are unaffected.

Make {{generateRecurringDates}} null-safe as well: start from an empty list and 
skip any calendar or history row that yields no dates.

h3. Test

A unit test {{CalendarReadPlatformServiceImplTest}} maps a non-repeating 
calendar row (null recurrence) through {{retrieveCalendar}} and checks the 
defaults, plus a repeating row to show the rule is still parsed, and covers 
{{generateRecurringDates}} for an edited non-repeating calendar and for 
non-repeating history of a now-repeating calendar. An integration test creates 
a non-repeating group calendar, edits its title and reads it back.

  was:
h3. Summary

Reading calendars fails with a {{NullPointerException}} as soon as the entity 
has a *non-repeating* calendar. A non-repeating calendar is stored with 
{{recurrence = NULL}} (by design, see {{Calendar}} constructor and 
{{Calendar.update}}), but the row mappers in 
{{CalendarReadPlatformServiceImpl}} pass that null rule straight into 
{{CalendarUtils}}, which hands it to ical4j's {{RRule}} parser.

h3. Steps to reproduce (stock Fineract)
# Create a group: {{POST /v1/groups}} (any office, active).
# Add a non-repeating, non-collection calendar to it:

{code:json}POST /v1/groups/{groupId}/calendars
{ "title": "One-off meeting", "typeId": 4, "repeating": false, "startDate": "28 
September 2026", "locale": "en", "dateFormat": "dd MMMM yyyy" }
{code}
# Read it back: {{GET /v1/groups/{groupId}/calendars/{calendarId}}}, or list 
{{GET /v1/groups/{groupId}/calendars}}. The same mapper also backs other 
calendar reads, e.g. {{GET 
/v1/loans/template?templateType=jlgbulk&groupId={groupId}}}.

h3. Expected

HTTP 200. The calendar is returned with {{repeating: false}}, no {{frequency}} 
/ {{repeatsOnDay}} / {{repeatsOnNthDayOfMonth}} and {{interval: 0}}, the same 
defaults already used for a rule that cannot be parsed.

h3. Actual

The request fails with HTTP 500 ({{Internal Server Error}}); the server log 
shows a {{NullPointerException}} ({{Cannot invoke "String.split(String)" 
because "aValue" is null}}) thrown from {{CalendarUtils.getICalRecur}} (ical4j 
{{RRule}} with a null rule string), reached from 
{{CalendarReadPlatformServiceImpl$CalendarDataMapper.mapRow}}.

h3. Root cause

Both {{CalendarDataMapper}} and {{CalendarDataFromHistoryMapper}} call 
{{CalendarUtils.getFrequency}}, {{getInterval}}, {{getRepeatsOnDay}}, 
{{getRepeatsOnNthDayOfMonth}} and {{getMonthOnDay}} unconditionally. Each of 
them parses the rule with {{getICalRecur(recurrence)}}, which cannot handle 
{{null}}. The neighbouring {{humanReadable}} computation in the same mappers 
already guards with {{recurrence != null}}; the other five calls do not.

h3. Proposed fix

Guard those five calls with {{recurrence != null}} in both mappers and fall 
back to {{CalendarFrequencyType.INVALID}}, {{0}}, 
{{CalendarWeekDaysType.INVALID}}, {{NthDayType.INVALID}} and {{null}} (which 
{{CalendarEnumerations}} already renders as "no value"). Repeating calendars 
are unaffected.

h3. Test

A unit test {{CalendarReadPlatformServiceImplTest}} maps a non-repeating 
calendar row (null recurrence) through {{retrieveCalendar}} and checks the 
defaults, plus a repeating row to show the rule is still parsed.


> Reading a group's calendars fails with HTTP 500 when it has a non-repeating 
> calendar
> ------------------------------------------------------------------------------------
>
>                 Key: FINERACT-2865
>                 URL: https://issues.apache.org/jira/browse/FINERACT-2865
>             Project: Apache Fineract
>          Issue Type: Bug
>          Components: Groups
>            Reporter: Foo Developer
>            Assignee: Foo Developer
>            Priority: Major
>
> h3. Summary
> Reading calendars fails with a {{NullPointerException}} as soon as the entity 
> has a *non-repeating* calendar. A non-repeating calendar is stored with 
> {{recurrence = NULL}} (by design, see {{Calendar}} constructor and 
> {{Calendar.update}}), but the row mappers in 
> {{CalendarReadPlatformServiceImpl}} pass that null rule straight into 
> {{CalendarUtils}}, which hands it to ical4j's {{RRule}} parser.
> h3. Steps to reproduce (stock Fineract)
> # Create a group: {{POST /v1/groups}} (any office, active).
> # Add a non-repeating, non-collection calendar to it:
> {code:json}POST /v1/groups/{groupId}/calendars
> { "title": "One-off meeting", "typeId": 4, "repeating": false, "startDate": 
> "28 September 2026", "locale": "en", "dateFormat": "dd MMMM yyyy" }
> {code}
> # Read it back: {{GET /v1/groups/{groupId}/calendars/{calendarId}}}, or list 
> {{GET /v1/groups/{groupId}/calendars}}. The same mapper also backs other 
> calendar reads, e.g. {{GET 
> /v1/loans/template?templateType=jlgbulk&groupId={groupId}}}.
> # Edit the calendar (e.g. only its title) with {{PUT 
> /v1/groups/{groupId}/calendars/{calendarId}}} and read it again.
> h3. Expected
> HTTP 200. The calendar is returned with {{repeating: false}}, {{interval: 
> 0}}, no {{frequency}} / {{repeatsOnDay}} value and {{repeatsOnNthDayOfMonth}} 
> set to the "invalid" enum option ({{{"code": "nthDayType.invalid"}}}), the 
> same defaults already used for a rule that cannot be parsed. The same holds 
> after the calendar has been edited (e.g. only its title), which writes an 
> {{m_calendar_history}} row, and for a calendar switched from non-repeating to 
> repeating.
> h3. Actual
> The request fails with HTTP 500 ({{Internal Server Error}}); the server log 
> shows a {{NullPointerException}} ({{Cannot invoke "String.split(String)" 
> because "aValue" is null}}) thrown from {{CalendarUtils.getICalRecur}} 
> (ical4j {{RRule}} with a null rule string), reached from 
> {{CalendarReadPlatformServiceImpl$CalendarDataMapper.mapRow}}.
> h3. Root cause
> Both {{CalendarDataMapper}} and {{CalendarDataFromHistoryMapper}} call 
> {{CalendarUtils.getFrequency}}, {{getInterval}}, {{getRepeatsOnDay}}, 
> {{getRepeatsOnNthDayOfMonth}} and {{getMonthOnDay}} unconditionally. Each of 
> them parses the rule with {{getICalRecur(recurrence)}}, which cannot handle 
> {{null}}. The neighbouring {{humanReadable}} computation in the same mappers 
> already guards with {{recurrence != null}}; the other five calls do not.
> A second path fails once the calendar has history: 
> {{CalendarReadPlatformServiceImpl.generateRecurringDates}} starts from 
> {{generateRecurringDate(calendar)}} and adds 
> {{generateRecurringDate(history)}} for each history row, but 
> {{generateRecurringDate}} returns {{null}} for a non-repeating calendar. An 
> edited non-repeating calendar therefore throws an NPE on 
> {{null.addAll(...)}}, and a calendar switched from non-repeating to repeating 
> throws on {{addAll(null)}} for its old history row.
> h3. Proposed fix
> Guard those five calls with {{recurrence != null}} in both mappers and fall 
> back to {{CalendarFrequencyType.INVALID}}, {{0}}, 
> {{CalendarWeekDaysType.INVALID}}, {{NthDayType.INVALID}} and {{null}} (which 
> {{CalendarEnumerations}} already renders as "no value"). Repeating calendars 
> are unaffected.
> Make {{generateRecurringDates}} null-safe as well: start from an empty list 
> and skip any calendar or history row that yields no dates.
> h3. Test
> A unit test {{CalendarReadPlatformServiceImplTest}} maps a non-repeating 
> calendar row (null recurrence) through {{retrieveCalendar}} and checks the 
> defaults, plus a repeating row to show the rule is still parsed, and covers 
> {{generateRecurringDates}} for an edited non-repeating calendar and for 
> non-repeating history of a now-repeating calendar. An integration test 
> creates a non-repeating group calendar, edits its title and reads it back.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to