Skip to content

Commit a0583a7

Browse files
authored
Merge pull request #13525 from SORMAS-Foundation/bugfix-no-session-crashes
Added new handling for current user avoiding entities being detached
2 parents 45627d8 + 22839e5 commit a0583a7

File tree

30 files changed

+1005
-311
lines changed

30 files changed

+1005
-311
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
3+
* Copyright © 2016-2026 SORMAS Foundation gGmbH
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
* You should have received a copy of the GNU General Public License
13+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
16+
package de.symeda.sormas.backend.audit;
17+
18+
import javax.ejb.Stateless;
19+
import javax.enterprise.inject.Instance;
20+
import javax.enterprise.inject.spi.CDI;
21+
import javax.inject.Inject;
22+
import javax.persistence.PrePersist;
23+
import javax.persistence.PreUpdate;
24+
25+
import de.symeda.sormas.backend.common.AbstractDomainObject;
26+
import de.symeda.sormas.backend.user.CurrentUserContext;
27+
import de.symeda.sormas.backend.user.User;
28+
29+
/**
30+
* Stateless JPA entity listener for auditing AbstractDomainObject lifecycle events.
31+
*
32+
* This listener automatically updates audit information on AbstractDomainObject entities
33+
* during persist and update operations. It captures the current authenticated user
34+
* and sets them as the change user for tracking purposes.
35+
*
36+
* <p>
37+
* The listener:
38+
* <ul>
39+
* <li>Responds to {@link PrePersist} and {@link PreUpdate} events</li>
40+
* <li>Only processes entities that extend {@link AbstractDomainObject}</li>
41+
* <li>Sets the current user as the change user for audit tracking</li>
42+
* <li>Uses defensive CDI lookup for dependency resolution</li>
43+
* </ul>
44+
*
45+
* <p>
46+
* This listener should be registered with JPA entities that extend
47+
* AbstractDomainObject to enable automatic audit user tracking.
48+
*/
49+
@Stateless
50+
public class AdoAuditUpdateListener {
51+
52+
private CurrentUserContext currentUserContext;
53+
54+
/**
55+
* Sets the current user context for audit operations.
56+
* This method is called by the CDI container to inject the current user context.
57+
*
58+
* @param currentUserContext
59+
* the current user context to set
60+
*/
61+
@Inject
62+
public void setCurrentUserContext(CurrentUserContext currentUserContext) {
63+
this.currentUserContext = currentUserContext;
64+
}
65+
66+
/**
67+
* Retrieves the current user context, with fallback to CDI lookup if not injected.
68+
* This method provides a defensive mechanism to obtain the current user context
69+
* even if dependency injection fails or is unavailable.
70+
*
71+
* @return the current user context, or null if unavailable
72+
*/
73+
public CurrentUserContext getCurrentUserContext() {
74+
if (currentUserContext != null) {
75+
return currentUserContext;
76+
}
77+
final Instance<CurrentUserContext> instance = CDI.current().select(CurrentUserContext.class);
78+
return instance.isUnsatisfied() ? null : instance.get();
79+
}
80+
81+
/**
82+
* JPA lifecycle callback that updates audit information before entity persistence.
83+
*
84+
* This method is automatically invoked by the JPA provider when an AbstractDomainObject
85+
* entity is about to be persisted or updated. It sets the current authenticated user
86+
* as the change user for audit tracking purposes.
87+
*
88+
* <p>
89+
* The method:
90+
* <ul>
91+
* <li>Only processes entities that are instances of {@link AbstractDomainObject}</li>
92+
* <li>Retrieves the current user from the user context</li>
93+
* <li>Sets the change user on the entity if a current user is available</li>
94+
* <li>Skips the update if no current user is authenticated</li>
95+
* </ul>
96+
*
97+
* @param entity
98+
* the entity being persisted or updated
99+
*/
100+
@PrePersist
101+
@PreUpdate
102+
public void updateADOAuditRecord(Object entity) {
103+
if (!(entity instanceof AbstractDomainObject)) {
104+
return;
105+
}
106+
final AbstractDomainObject ado = (AbstractDomainObject) entity;
107+
final User changeUser = getCurrentUserContext().getUserEntityReference();
108+
if (changeUser != null) {
109+
ado.setChangeUser(changeUser);
110+
}
111+
}
112+
}

sormas-backend/src/main/java/de/symeda/sormas/backend/common/AbstractDomainObject.java

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
import java.sql.Timestamp;
2222
import java.time.Instant;
2323

24-
import javax.naming.InitialContext;
25-
import javax.naming.NamingException;
2624
import javax.persistence.Basic;
2725
import javax.persistence.CascadeType;
2826
import javax.persistence.Column;
@@ -34,8 +32,6 @@
3432
import javax.persistence.JoinColumn;
3533
import javax.persistence.ManyToOne;
3634
import javax.persistence.MappedSuperclass;
37-
import javax.persistence.PrePersist;
38-
import javax.persistence.PreUpdate;
3935
import javax.persistence.SequenceGenerator;
4036
import javax.persistence.Version;
4137
import javax.validation.constraints.Size;
@@ -49,7 +45,7 @@
4945

5046
import de.symeda.sormas.api.utils.DataHelper;
5147
import de.symeda.sormas.api.uuid.HasUuid;
52-
import de.symeda.sormas.backend.user.CurrentUserService;
48+
import de.symeda.sormas.backend.audit.AdoAuditUpdateListener;
5349
import de.symeda.sormas.backend.user.User;
5450

5551
/**
@@ -60,7 +56,7 @@
6056
@TypeDef(name = "json", typeClass = JsonType.class)
6157
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
6258
@MappedSuperclass
63-
@EntityListeners(AbstractDomainObject.AdoListener.class)
59+
@EntityListeners(AdoAuditUpdateListener.class)
6460
public abstract class AbstractDomainObject implements Serializable, Cloneable, HasUuid {
6561

6662
private static final long serialVersionUID = 3957437214306161226L;
@@ -74,6 +70,7 @@ public abstract class AbstractDomainObject implements Serializable, Cloneable, H
7470
public static final String UUID = "uuid";
7571
public static final String CREATION_DATE = "creationDate";
7672
public static final String CHANGE_DATE = "changeDate";
73+
public static final String CHANGE_USER = "changeUser";
7774

7875
@NotExposedToApi
7976
private Long id;
@@ -189,24 +186,4 @@ public int hashCode() {
189186
public String toString() {
190187
return getClass().getSimpleName() + StringUtils.SPACE + getUuid();
191188
}
192-
193-
static class AdoListener {
194-
195-
private User getCurrentUser() {
196-
try {
197-
CurrentUserService currentUserService =
198-
(CurrentUserService) new InitialContext().lookup("java:global/sormas-ear/sormas-backend/CurrentUserService");
199-
return currentUserService.getCurrentUser();
200-
} catch (NamingException e) {
201-
throw new RuntimeException(e);
202-
}
203-
}
204-
205-
@PrePersist
206-
@PreUpdate
207-
private void beforeAnyUpdate(AbstractDomainObject ado) {
208-
User currentUser = getCurrentUser();
209-
ado.setChangeUser(currentUser);
210-
}
211-
}
212189
}

0 commit comments

Comments
 (0)