2020 */
2121
2222import java .util .ArrayList ;
23+ import java .util .Arrays ;
2324import java .util .HashMap ;
2425import java .util .List ;
2526import java .util .Map ;
@@ -131,7 +132,7 @@ public long getTimestamp() {
131132 }
132133
133134 /**
134- * Add a new maintenance reason (or update an existing one if the triggering entity already has a reason) .
135+ * Add a new maintenance reason. If the triggering entity already has a reason, it will be replaced .
135136 *
136137 * @param reason The reason for maintenance
137138 * @param timestamp The timestamp when maintenance was triggered
@@ -141,34 +142,22 @@ public void addMaintenanceReason(String reason, long timestamp, TriggeringEntity
141142 LOG .info ("Adding maintenance reason for entity: {}, reason: {}, timestamp: {}" ,
142143 triggeringEntity , reason , timestamp );
143144
144- // The triggering entity is our unique key - Overwrite any existing entry with this entity
145- String triggerEntityStr = triggeringEntity .name ();
146-
147145 List <Map <String , String >> reasons = getMaintenanceReasons ();
148146 LOG .debug ("Before addition: Reasons list contains {} entries" , reasons .size ());
149147
150- boolean found = false ;
151- for (Map <String , String > entry : reasons ) {
152- if (triggerEntityStr .equals (entry .get (MaintenanceSignalProperty .TRIGGERED_BY .name ()))) {
153- entry .put (PauseSignalProperty .REASON .name (), reason );
154- entry .put (MaintenanceSignalProperty .TIMESTAMP .name (), Long .toString (timestamp ));
155- found = true ;
156- LOG .debug ("Updated existing entry for entity: {}" , triggeringEntity );
157- break ;
158- }
159- }
148+ // Filter out any existing reasons for this triggering entity
149+ List <Map <String , String >> filteredReasons = filterReasons (reasons , null ,
150+ Arrays .asList (triggeringEntity ));
160151
161- if (!found ) {
162- Map <String , String > newEntry = new HashMap <>();
163- newEntry .put (PauseSignalProperty .REASON .name (), reason );
164- newEntry .put (MaintenanceSignalProperty .TIMESTAMP .name (), Long .toString (timestamp ));
165- newEntry .put (MaintenanceSignalProperty .TRIGGERED_BY .name (), triggerEntityStr );
166- reasons .add (newEntry );
167- LOG .debug ("Added new entry for entity: {}" , triggeringEntity );
168- }
152+ // Always add the new reason at the end of the list
153+ Map <String , String > newEntry = new HashMap <>();
154+ newEntry .put (PauseSignalProperty .REASON .name (), reason );
155+ newEntry .put (MaintenanceSignalProperty .TIMESTAMP .name (), Long .toString (timestamp ));
156+ newEntry .put (MaintenanceSignalProperty .TRIGGERED_BY .name (), triggeringEntity .name ());
157+ filteredReasons .add (newEntry );
169158
170- updateReasonsListField (reasons );
171- LOG .debug ("After addition: Reasons list contains {} entries" , reasons .size ());
159+ updateReasonsListField (filteredReasons );
160+ LOG .debug ("After addition: Reasons list contains {} entries" , filteredReasons .size ());
172161 }
173162
174163 /**
@@ -247,73 +236,77 @@ public boolean removeMaintenanceReason(TriggeringEntity triggeringEntity) {
247236 LOG .info ("Removing maintenance reason for entity: {}" , triggeringEntity );
248237
249238 List <Map <String , String >> reasons = getMaintenanceReasons ();
250-
251- boolean entityExists = false ;
252- for (Map <String , String > entry : reasons ) {
253- String entryEntity = entry .get (MaintenanceSignalProperty .TRIGGERED_BY .name ());
254- if (triggeringEntity .name ().equals (entryEntity )) {
255- entityExists = true ;
256- break ;
257- }
258- }
259-
260- if (!entityExists ) {
239+ List <Map <String , String >> filteredReasons = filterReasons (reasons , null ,
240+ Arrays .asList (triggeringEntity ));
241+
242+ // Return false early if no entities were filtered out
243+ if (filteredReasons .size () == reasons .size ()) {
261244 LOG .info ("Entity {} doesn't have a maintenance reason entry, ignoring exit request" , triggeringEntity );
262245 return false ;
263246 }
264247
265- int originalSize = reasons . size ();
266- LOG . debug ( "Before removal: Reasons list contains {} entries" , reasons . size () );
248+ // Update reasons list field with filtered reasons
249+ updateReasonsListField ( filteredReasons );
267250
268- List <Map <String , String >> updatedReasons = new ArrayList <>();
269- String targetEntity = triggeringEntity .name ();
251+ // Always set/reset the simpleFields if filteredReasons.size() != 0
252+ if (!filteredReasons .isEmpty ()) {
253+ // Get the most recent reason (last element, since list is in chronological order)
254+ Map <String , String > mostRecent = filteredReasons .get (filteredReasons .size () - 1 );
255+ String newReason = mostRecent .get (PauseSignalProperty .REASON .name ());
256+ long newTimestamp = Long .parseLong (mostRecent .get (MaintenanceSignalProperty .TIMESTAMP .name ()));
257+ TriggeringEntity newEntity = TriggeringEntity .valueOf (
258+ mostRecent .get (MaintenanceSignalProperty .TRIGGERED_BY .name ()));
270259
271- // Only keep reasons that don't match the triggering entity
272- for (Map <String , String > entry : reasons ) {
273- String entryEntity = entry .get (MaintenanceSignalProperty .TRIGGERED_BY .name ());
274- if (!targetEntity .equals (entryEntity )) {
275- updatedReasons .add (entry );
276- } else {
277- LOG .debug ("Removing entry with reason: {} for entity: {}" ,
278- entry .get (PauseSignalProperty .REASON .name ()), entryEntity );
279- }
260+ LOG .info ("Updated to most recent reason: {}, entity: {}, timestamp: {}" ,
261+ newReason , newEntity , newTimestamp );
262+
263+ setReason (newReason );
264+ setTimestamp (newTimestamp );
265+ setTriggeringEntity (newEntity );
280266 }
281267
282- boolean removed = updatedReasons .size () < originalSize ;
283- LOG .debug ("After removal: Reasons list contains {} entries" , updatedReasons .size ());
284-
285- if (removed ) {
286- updateReasonsListField (updatedReasons );
287-
288- // Update the simpleFields with the most recent reason (for backward compatibility)
289- if (!updatedReasons .isEmpty ()) {
290- // Sort by timestamp in descending order to get the most recent
291- updatedReasons .sort ((r1 , r2 ) -> {
292- long t1 = Long .parseLong (r1 .get (MaintenanceSignalProperty .TIMESTAMP .name ()));
293- long t2 = Long .parseLong (r2 .get (MaintenanceSignalProperty .TIMESTAMP .name ()));
294- return Long .compare (t2 , t1 );
295- });
296-
297- Map <String , String > mostRecent = updatedReasons .get (0 );
298- String newReason = mostRecent .get (PauseSignalProperty .REASON .name ());
299- long newTimestamp = Long .parseLong (mostRecent .get (MaintenanceSignalProperty .TIMESTAMP .name ()));
300- TriggeringEntity newEntity = TriggeringEntity .valueOf (
301- mostRecent .get (MaintenanceSignalProperty .TRIGGERED_BY .name ()));
302-
303- LOG .info ("Updated to most recent reason: {}, entity: {}, timestamp: {}" ,
304- newReason , newEntity , newTimestamp );
305-
306- setReason (newReason );
307- setTimestamp (newTimestamp );
308- setTriggeringEntity (newEntity );
268+ return true ;
269+ }
270+
271+ /**
272+ * Filter maintenance reasons based on include and exclude entity lists.
273+ *
274+ * @param reasons The original list of maintenance reasons
275+ * @param includeEntities List of entities to include (null means include all)
276+ * @param excludeEntities List of entities to exclude (null means exclude none)
277+ * @return Filtered list of maintenance reasons (maintains original order)
278+ */
279+ private List <Map <String , String >> filterReasons (List <Map <String , String >> reasons ,
280+ List <TriggeringEntity > includeEntities ,
281+ List <TriggeringEntity > excludeEntities ) {
282+ List <Map <String , String >> filtered = new ArrayList <>();
283+
284+ for (Map <String , String > reason : reasons ) {
285+ String triggeredByStr = reason .get (MaintenanceSignalProperty .TRIGGERED_BY .name ());
286+ if (triggeredByStr == null ) {
287+ continue ;
288+ }
289+
290+ TriggeringEntity entity ;
291+ try {
292+ entity = TriggeringEntity .valueOf (triggeredByStr );
293+ } catch (IllegalArgumentException ex ) {
294+ LOG .warn ("Unknown triggering entity: {}, skipping" , triggeredByStr );
295+ continue ;
296+ }
297+
298+ if ((includeEntities != null && !includeEntities .contains (entity )) ||
299+ (excludeEntities != null && excludeEntities .contains (entity ))) {
300+ continue ;
309301 }
310- } else {
311- LOG . info ( "No matching maintenance reason found for entity: {}" , triggeringEntity );
302+
303+ filtered . add ( reason );
312304 }
313305
314- return removed ;
306+ return filtered ;
315307 }
316308
309+
317310 /**
318311 * Check if there are any active maintenance reasons.
319312 *
@@ -331,49 +324,14 @@ public boolean hasMaintenanceReasons() {
331324 */
332325 public boolean hasMaintenanceReason (TriggeringEntity triggeringEntity ) {
333326 List <Map <String , String >> reasons = getMaintenanceReasons ();
334- for (Map <String , String > entry : reasons ) {
335- if (triggeringEntity .name ().equals (entry .get (MaintenanceSignalProperty .TRIGGERED_BY .name ()))) {
336- return true ;
337- }
338- }
339- return false ;
327+ List <Map <String , String >> filteredReasons = filterReasons (reasons ,
328+ Arrays .asList (triggeringEntity ), null );
329+ return !filteredReasons .isEmpty ();
340330 }
341331
342- /**
343- * Gets the maintenance reason details for a specific triggering entity.
344- *
345- * @param triggeringEntity The entity to get reason details for
346- * @return Map containing reason details, or null if not found
347- */
348- public Map <String , String > getMaintenanceReasonDetails (TriggeringEntity triggeringEntity ) {
349- List <Map <String , String >> reasons = getMaintenanceReasons ();
350- for (Map <String , String > entry : reasons ) {
351- if (triggeringEntity .name ().equals (entry .get (MaintenanceSignalProperty .TRIGGERED_BY .name ()))) {
352- return entry ;
353- }
354- }
355- return null ;
356- }
357332
358- /**
359- * Gets the number of active maintenance reasons.
360- *
361- * @return The count of active maintenance reasons
362- */
363- public int getMaintenanceReasonsCount () {
364- return getMaintenanceReasons ().size ();
365- }
366333
367- /**
368- * Gets the maintenance reason from a specific triggering entity.
369- *
370- * @param triggeringEntity The entity to get reason for
371- * @return The reason string, or null if not found
372- */
373- public String getMaintenanceReason (TriggeringEntity triggeringEntity ) {
374- Map <String , String > details = getMaintenanceReasonDetails (triggeringEntity );
375- return details != null ? details .get (PauseSignalProperty .REASON .name ()) : null ;
376- }
334+
377335
378336 /**
379337 * Reconcile legacy data from simpleFields into listFields.reasons if it's missing.
@@ -390,23 +348,23 @@ public void reconcileLegacyData() {
390348 TriggeringEntity simpleEntity = getTriggeringEntity ();
391349 long simpleTimestamp = getTimestamp ();
392350
393- // Only reconcile USER data from legacy clients
394- // CONTROLLER and AUTOMATION should not have legacy data loss scenarios
395- if (simpleReason != null && !simpleReason .isEmpty () && simpleEntity == TriggeringEntity .USER
396- && simpleTimestamp > 0 && !hasMaintenanceReason (simpleEntity )) {
351+ // Early return if no simple reason exists, not a USER entity, or USER already has a reason
352+ List <Map <String , String >> reasons = getMaintenanceReasons ();
353+ if (simpleReason == null || simpleEntity != TriggeringEntity .USER ||
354+ !filterReasons (reasons , Arrays .asList (TriggeringEntity .USER ), null ).isEmpty ()) {
355+ return ;
356+ }
397357
398- // Legacy USER data exists but not in listFields - preserve it
399- Map <String , String > legacyEntry = new HashMap <>();
400- legacyEntry .put (PauseSignalProperty .REASON .name (), simpleReason );
401- legacyEntry .put (MaintenanceSignalProperty .TIMESTAMP .name (), String .valueOf (simpleTimestamp ));
402- legacyEntry .put (MaintenanceSignalProperty .TRIGGERED_BY .name (), simpleEntity .name ());
358+ // Legacy USER data exists but not in listFields - preserve it
359+ Map <String , String > legacyEntry = new HashMap <>();
360+ legacyEntry .put (PauseSignalProperty .REASON .name (), simpleReason );
361+ legacyEntry .put (MaintenanceSignalProperty .TIMESTAMP .name (), String .valueOf (simpleTimestamp ));
362+ legacyEntry .put (MaintenanceSignalProperty .TRIGGERED_BY .name (), simpleEntity .name ());
403363
404- List <Map <String , String >> reasons = getMaintenanceReasons ();
405- reasons .add (legacyEntry );
406- updateReasonsListField (reasons );
364+ reasons .add (legacyEntry );
365+ updateReasonsListField (reasons );
407366
408- LOG .info ("Reconciled legacy USER maintenance data: reason={}, timestamp={}" ,
409- simpleReason , simpleTimestamp );
410- }
367+ LOG .info ("Reconciled legacy USER maintenance data: reason={}, timestamp={}" ,
368+ simpleReason , simpleTimestamp );
411369 }
412370}
0 commit comments