• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 26773559161

01 Jun 2026 06:21PM UTC coverage: 63.376% (-0.01%) from 63.389%
26773559161

push

github

web-flow
TRUNK-6429: Create application events for service method calls and entity changes (#6084)

272 of 504 new or added lines in 27 files covered. (53.97%)

5 existing lines in 2 files now uncovered.

23598 of 37235 relevant lines covered (63.38%)

0.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.81
/api/src/main/java/org/openmrs/event/outbox/OutboxEventService.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.event.outbox;
11

12
import org.hibernate.SessionFactory;
13
import org.slf4j.Logger;
14
import org.slf4j.LoggerFactory;
15
import org.springframework.beans.factory.annotation.Value;
16
import org.springframework.stereotype.Service;
17
import org.springframework.transaction.annotation.Transactional;
18

19
import javax.persistence.Query;
20
import java.time.Duration;
21
import java.time.Instant;
22
import java.util.Date;
23
import java.util.List;
24

25
/**
26
 * It's internal and not for use in modules.
27
 * 
28
 * @since 2.9.x
29
 */
30
@Service
31
public class OutboxEventService {
32

33
        private static final Logger log = LoggerFactory.getLogger(OutboxEventService.class);
1✔
34
        
35
        private final SessionFactory sessionFactory;
36
        private final Duration listenerTimeout;
37
        
38
        public OutboxEventService(SessionFactory sessionFactory, @Value("${outboxevent.listener.timeout:120}") int listenerTimeout) {
1✔
39
                this.sessionFactory = sessionFactory;
1✔
40
                this.listenerTimeout = Duration.ofSeconds(listenerTimeout);
1✔
41
        }
1✔
42

43
        /**
44
         * Revert tasks stuck in PROCESSING for more than {@code outboxevent.listener.timeout} .
45
         */
46
        @Transactional(noRollbackFor = OutboxException.class)
47
        public void resetStuckEvent() throws OutboxException {
NEW
48
                Date threshold = Date.from(Instant.now().minus(listenerTimeout));
×
NEW
49
                Query resetStuckQuery = sessionFactory.getCurrentSession().createQuery(
×
50
                        "update OutboxEvent set status = 'PENDING', errorCount = coalesce(errorCount, 0) + 1, " +
51
                                "errorMessage = :errorMessage, dateChanged = :now where status = 'PROCESSING' and dateChanged < :threshold");
NEW
52
                resetStuckQuery.setParameter("now", new Date());
×
NEW
53
                resetStuckQuery.setParameter("threshold", threshold);
×
NEW
54
                resetStuckQuery.setParameter("errorMessage", "Stuck in PROCESSING state for more than " + listenerTimeout.getSeconds() + " seconds");
×
NEW
55
                int resetCount = resetStuckQuery.executeUpdate();
×
NEW
56
                if (resetCount > 0) {
×
57
                        // Throw an error to increase visibility of the event
NEW
58
                        throw new OutboxException("Reset stuck outbox item from PROCESSING back to PENDING");
×
59
                }
NEW
60
        }
×
61

62
        @Transactional(readOnly = true)
63
        public void warnOnTooManyPendingEvents() {
NEW
64
                Query query = sessionFactory.getCurrentSession().createQuery(
×
65
                        "select count(*) from OutboxEvent where status in ('PENDING')");
NEW
66
                long count = query.getSingleResult() == null ? 0 : (long) query.getSingleResult();
×
NEW
67
                if (count > 10000) {
×
NEW
68
                        log.warn("Too many pending ({}) outbox events to process in the system! " +
×
NEW
69
                                "Please review your listeners to fix performance issues so outbox events do no pile up!", count);
×
70
                }
NEW
71
        }
×
72
        
73
        @Transactional(readOnly = true)
74
        public List<OutboxEvent> getProcessingAndPendingEvents() {
NEW
75
                Query query = sessionFactory.getCurrentSession().createQuery(
×
76
                        "from OutboxEvent where status in ('PENDING', 'PROCESSING') order by id asc");
NEW
77
                query.setMaxResults(100);
×
78

NEW
79
                return query.getResultList();
×
80
        }
81
        
82
        @Transactional
83
        public boolean lockEventForProcessing(OutboxEvent outboxEvent) {
NEW
84
                Query claimQuery = sessionFactory.getCurrentSession().createQuery(
×
85
                        "update OutboxEvent set status = 'PROCESSING', dateChanged = :now where id = :id and status = 'PENDING'");
86

NEW
87
                claimQuery.setParameter("id", outboxEvent.getId());
×
NEW
88
                claimQuery.setParameter("now", new Date());
×
89

NEW
90
                int updatedCount = claimQuery.executeUpdate();
×
NEW
91
                return updatedCount == 1;
×
92
        }
93
        
94
        @Transactional
95
        public void saveOutboxEvent(OutboxEvent outboxEvent) {
96
                outboxEvent.setDateChanged(new Date());
1✔
97
                sessionFactory.getCurrentSession().saveOrUpdate(outboxEvent);
1✔
98
        }
1✔
99
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc