From 9430a83dde5d7c3f4535f6c3a5f9e21ac68ac8fa Mon Sep 17 00:00:00 2001
From: Joaquín Reñé <jrene@curisit.net>
Date: Thu, 16 Apr 2026 17:05:28 +0000
Subject: [PATCH] #4479 - upgrade SecurisServer to Java 21

---
 securis/src/main/resources/META-INF/persistence.xml                      |   34 +++++------
 securis/pom.xml                                                          |    6 ++
 securis/src/main/resources/version.properties                            |    5 +
 securis/src/main/webapp/WEB-INF/web.xml                                  |   12 ++-
 securis/src/main/java/net/curisit/securis/services/UserResource.java     |   54 ++++++++++++------
 securis/src/main/webapp/src/app/footer.component.ts                      |    2 
 securis/src/main/webapp/src/app/user.service.ts                          |    4 
 securis/src/main/java/net/curisit/securis/ioc/EntityManagerProvider.java |   30 +++++++++-
 8 files changed, 100 insertions(+), 47 deletions(-)

diff --git a/securis/pom.xml b/securis/pom.xml
index 312b1c8..bf7229f 100644
--- a/securis/pom.xml
+++ b/securis/pom.xml
@@ -176,6 +176,12 @@
     </dependencies>
 
     <build>
+	    <resources>
+		    <resource>
+		      <directory>src/main/resources</directory>
+		      <filtering>true</filtering>
+		    </resource>
+		</resources>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
diff --git a/securis/src/main/java/net/curisit/securis/ioc/EntityManagerProvider.java b/securis/src/main/java/net/curisit/securis/ioc/EntityManagerProvider.java
index f702c97..2add6fc 100644
--- a/securis/src/main/java/net/curisit/securis/ioc/EntityManagerProvider.java
+++ b/securis/src/main/java/net/curisit/securis/ioc/EntityManagerProvider.java
@@ -3,6 +3,7 @@
 */
 package net.curisit.securis.ioc;
 
+import jakarta.annotation.PostConstruct;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.EntityManagerFactory;
@@ -27,15 +28,27 @@
 @ApplicationScoped
 public class EntityManagerProvider {
 
-    @SuppressWarnings("unused")
     private static final Logger log = LogManager.getLogger(EntityManagerProvider.class);
 
     /** 
      * entityManagerFactory<p>
      * Application-wide EMF built from persistence.xml PU "localdb". 
      */
-    private final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("localdb");
+    //private final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("localdb");
+    private EntityManagerFactory entityManagerFactory;
 
+    @PostConstruct
+    public void init() {
+        try {
+            log.info("Initializing EntityManagerFactory with persistence unit 'localdb'");
+            entityManagerFactory = Persistence.createEntityManagerFactory("localdb");
+            log.info("EntityManagerFactory initialized correctly: {}", entityManagerFactory);
+        } catch (Exception e) {
+            log.error("Error creating EntityManagerFactory for persistence unit 'localdb'", e);
+            entityManagerFactory = null;
+        }
+    }
+    
     /**
     * getEntityManager<p>
     * Create a new {@link EntityManager}.
@@ -43,6 +56,17 @@
     * @return a new EntityManager; caller must close it
     */
     public EntityManager getEntityManager() {
-        return entityManagerFactory.createEntityManager();
+        try {
+            if (entityManagerFactory == null) {
+                log.error("EntityManagerFactory is null");
+                return null;
+            }
+            EntityManager em = entityManagerFactory.createEntityManager();
+            log.info("Created EntityManager: {}", em);
+            return em;
+        } catch (Exception e) {
+            log.error("Error creating EntityManager", e);
+            return null;
+        }
     }
 }
diff --git a/securis/src/main/java/net/curisit/securis/services/UserResource.java b/securis/src/main/java/net/curisit/securis/services/UserResource.java
index 7c4681a..0764f5e 100644
--- a/securis/src/main/java/net/curisit/securis/services/UserResource.java
+++ b/securis/src/main/java/net/curisit/securis/services/UserResource.java
@@ -12,7 +12,6 @@
 import jakarta.enterprise.context.RequestScoped;
 import jakarta.inject.Inject;
 import jakarta.persistence.EntityManager;
-import jakarta.persistence.PersistenceException;
 import jakarta.persistence.TypedQuery;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.ws.rs.Consumes;
@@ -57,7 +56,7 @@
  * <p>
  * Notes:
  * - Uses {@link BasicSecurityContext} authorization via @Securable and @RolesAllowed.
- * - Uses JPA {@link EntityManager} injected through @Context.
+ * - Uses JPA {@link EntityManager} injected through dependency injection.
  * - Mutating endpoints are wrapped in @EnsureTransaction to guarantee commit/rollback.
  * - Passwords are stored as SHA-256 hashes (see {@link Utils#sha256(String)}).
  *
@@ -86,7 +85,7 @@
     @Inject private CacheTTL cache;
 
     /** JPA entity manager bound to the current request context. */
-    @Context EntityManager em;
+    @Inject EntityManager em;
 
     private static final Logger LOG = LogManager.getLogger(UserResource.class);
 
@@ -330,7 +329,7 @@
         // lastLogin can be set through API (rare), otherwise managed at login
         currentUser.setLastLogin(user.getLastLogin());
 
-        em.persist(currentUser);
+        em.merge(currentUser);
         clearUserCache(currentUser.getUsername());
 
         return Response.ok(currentUser).build();
@@ -402,35 +401,54 @@
     @POST
     @Path("/login")
     @Produces({ MediaType.APPLICATION_JSON })
+    @EnsureTransaction
     public Response login(@FormParam("username") String username, @FormParam("password") String password, @Context HttpServletRequest request) throws SeCurisServiceException {
         LOG.info("index session: " + request.getSession());
+        LOG.info("login() called. session={}", request.getSession(false));
+        LOG.info("login() username='{}'", username);
 
-        User user = em.find(User.class, username);
-        if (user == null) {
-            LOG.error("Unknown username {} used in login service", username);
+        if (username == null || username.trim().isEmpty()) {
+            LOG.error("login() username is null or empty");
             throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
         }
+        if (password == null || password.isEmpty()) {
+            LOG.error("login() password is null or empty for user '{}'", username);
+            throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
+        }
+
+        User user = em.find(User.class, username);
+        LOG.info("login() user found? {}", user != null);
+
+        if (user == null) {
+            LOG.error("Unknown username '{}' used in login service", username);
+            throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
+        }
+
         String securedPassword = Utils.sha256(password);
+        LOG.info("login() hashed password generated? {}", securedPassword != null);
 
         if (securedPassword == null || !securedPassword.equals(user.getPassword())) {
+            LOG.error("Wrong password for user '{}'", username);
             throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials");
         }
 
         user.setLastLogin(new Date());
-        em.getTransaction().begin();
-        try {
-            em.persist(user);
-            em.getTransaction().commit();
-        } catch (PersistenceException ex) {
-            LOG.error("Error updating last login date for user: {}", username);
-            LOG.error(ex);
-            em.getTransaction().rollback();
-        }
+        em.merge(user);
 
         clearUserCache(username);
-        String userFullName = String.format("%s %s", user.getFirstName(), user.getLastName() == null ? "" : user.getLastName()).trim();
+
+        String userFullName = String.format("%s %s",
+                user.getFirstName(),
+                user.getLastName() == null ? "" : user.getLastName()).trim();
+
         String tokenAuth = tokenHelper.generateToken(username);
-        return Response.ok(Utils.createMap("success", true, "token", tokenAuth, "username", username, "full_name", userFullName)).build();
+        LOG.info("login() success for user '{}'", username);
+
+        return Response.ok(Utils.createMap(
+                "success", true,
+                "token", tokenAuth,
+                "username", username,
+                "full_name", userFullName)).build();
     }
 
     /**
diff --git a/securis/src/main/resources/META-INF/persistence.xml b/securis/src/main/resources/META-INF/persistence.xml
index 62abb5f..dcfc8a8 100644
--- a/securis/src/main/resources/META-INF/persistence.xml
+++ b/securis/src/main/resources/META-INF/persistence.xml
@@ -1,22 +1,20 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<persistence version="2.0"
-	xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
-	<persistence-unit name="localdb" transaction-type="RESOURCE_LOCAL">
-		<description>SeCuris LocalDB</description>
-		<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
-		<non-jta-data-source>java:comp/env/SeCurisDS</non-jta-data-source>
-		<shared-cache-mode>NONE</shared-cache-mode>
-		<properties>
-			<property name="hibernate.cache.provider_class" value="org.hibernate.cache.internal.NoCachingRegionFactory"/>
-			<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
-	<!-- 		<property name="hibernate.connection.datasource" value="java:comp/env/jdbc/SeCurisDS" />  -->
+<persistence version="3.0"
+    xmlns="https://jakarta.ee/xml/ns/persistence"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd">
 
- 			<property name="hibernate.cache.use_second_level_cache" value="false" />
-  <!-- 			<property name="hibernate.show_sql" value="true" />  --> 
- 			
- 			<property name="hibernate.format_sql" value="false"/>
-		</properties>
+    <persistence-unit name="localdb" transaction-type="RESOURCE_LOCAL">
+        <description>SeCuris LocalDB</description>
+        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
+        <non-jta-data-source>java:comp/env/SeCurisDS</non-jta-data-source>
+        <shared-cache-mode>NONE</shared-cache-mode>
 
-	</persistence-unit>
+        <properties>
+            <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.internal.NoCachingRegionFactory"/>
+            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
+            <property name="hibernate.cache.use_second_level_cache" value="false"/>
+            <property name="hibernate.format_sql" value="false"/>
+        </properties>
+    </persistence-unit>
 </persistence>
\ No newline at end of file
diff --git a/securis/src/main/resources/version.properties b/securis/src/main/resources/version.properties
new file mode 100644
index 0000000..f3f2f9c
--- /dev/null
+++ b/securis/src/main/resources/version.properties
@@ -0,0 +1,5 @@
+version=${project.version}
+majorVersion=3
+minorVersion=0
+incrementalVersion=2
+qualifier=
\ No newline at end of file
diff --git a/securis/src/main/webapp/WEB-INF/web.xml b/securis/src/main/webapp/WEB-INF/web.xml
index 4059657..be1f5fa 100644
--- a/securis/src/main/webapp/WEB-INF/web.xml
+++ b/securis/src/main/webapp/WEB-INF/web.xml
@@ -104,11 +104,13 @@
        </description>
        <role-name>admin</role-name>
     </security-role>
-
-	<resource-env-ref>
-	    <resource-env-ref-name>SeCurisDS</resource-env-ref-name>
-	    <resource-env-ref-type>jakarta.sql.DataSource</resource-env-ref-type>
-	</resource-env-ref>
+	
+	<resource-ref>
+	    <description>SeCuris DataSource</description>
+	    <res-ref-name>SeCurisDS</res-ref-name>
+	    <res-type>jakarta.sql.DataSource</res-type>
+	    <res-auth>Container</res-auth>
+	</resource-ref>
 	
 	<!-- 
 	<resource-env-ref>
diff --git a/securis/src/main/webapp/src/app/footer.component.ts b/securis/src/main/webapp/src/app/footer.component.ts
index 309167b..2018eaa 100644
--- a/securis/src/main/webapp/src/app/footer.component.ts
+++ b/securis/src/main/webapp/src/app/footer.component.ts
@@ -17,7 +17,7 @@
 
   ngOnInit(): void {
             //TODO Move to service
-      this.http.get("version", /* workaround to avoid OPTIONS method request*/ new BaseRequestOptions())
+      this.http.get("api/version", /* workaround to avoid OPTIONS method request*/ new BaseRequestOptions())
         .map((res) => <string>res.json().version)
         .subscribe(
           version => this.securisVersion = version,
diff --git a/securis/src/main/webapp/src/app/user.service.ts b/securis/src/main/webapp/src/app/user.service.ts
index 45bb541..95273eb 100644
--- a/securis/src/main/webapp/src/app/user.service.ts
+++ b/securis/src/main/webapp/src/app/user.service.ts
@@ -27,7 +27,7 @@
     params.append('username', username);
     params.append('password', password);
     let options = new RequestOptions({ headers: new Headers({ "Content-Type": "application/x-www-form-urlencoded" })});
-    return this.http.post('user/login', params.toString(), options)
+    return this.http.post('api/user/login', params.toString(), options)
                     .map((resp) => this.mapLogin(resp))
                     .catch((err) => super.processErrorResponse(err));
   }
@@ -47,7 +47,7 @@
     }
     var token = this.store.get("token");
     let option = new RequestOptions({ headers: new Headers({ 'X-SECURIS-TOKEN': token }) });
-    return this.http.get('check', option)
+    return this.http.get('api/check', option)
                     .map((resp) => this.mapCheck(resp))
                     .catch((err) => super.processErrorResponse(err));
   }

--
Gitblit v1.3.2