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