securis/pom.xml
.. .. @@ -5,7 +5,8 @@ 5 5 <modelVersion>4.0.0</modelVersion> 6 6 <groupId>net.curisit</groupId> 7 7 <artifactId>securis-server</artifactId> 8 - <version>2.0.2</version>8 + <version>3.0.0</version>9 + <packaging>war</packaging>9 10 <name>SeCuris-Server</name> 10 11 11 12 <properties> .. .. @@ -14,11 +15,13 @@ 14 15 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 15 16 <maven.compiler.source>21</maven.compiler.source> 16 17 <maven.compiler.target>21</maven.compiler.target> 18 +17 19 <resteasy.version>6.2.4.Final</resteasy.version> 18 20 <hibernate.version>5.6.15.Final</hibernate.version> 19 21 <jakarta.persistence.version>3.1.0</jakarta.persistence.version> 20 - <jakarta.servlet.version>6.0.0</jakarta.servlet.version>22 + <jakarta.servlet.version>6.1.0</jakarta.servlet.version>21 23 <jakarta.cdi.version>4.0.1</jakarta.cdi.version> 24 + <beanutils.version>1.9.4</beanutils.version>22 25 <log4j.version>2.18.0</log4j.version> 23 26 </properties> 24 27 .. .. @@ -36,12 +39,7 @@ 36 39 <artifactId>resteasy-core</artifactId> 37 40 <version>${resteasy.version}</version> 38 41 </dependency> 39 - <dependency>40 - <groupId>org.jboss.resteasy</groupId>41 - <artifactId>resteasy-servlet-initializer</artifactId>42 - <version>${resteasy.version}</version>43 - </dependency>44 - <dependency>42 + <dependency>45 43 <groupId>org.jboss.resteasy</groupId> 46 44 <artifactId>resteasy-multipart-provider</artifactId> 47 45 <version>${resteasy.version}</version> .. .. @@ -83,6 +81,13 @@ 83 81 </dependency> 84 82 85 83 <!-- Hibernate 5 compatible con Jakarta Persistence --> 84 + <!--85 + <dependency>86 + <groupId>org.hibernate</groupId>87 + <artifactId>hibernate-core</artifactId>88 + <version>${hibernate.version}</version>89 + </dependency>90 + -->86 91 <dependency> 87 92 <groupId>org.hibernate</groupId> 88 93 <artifactId>hibernate-core</artifactId> .. .. @@ -111,6 +116,14 @@ 111 116 <artifactId>guice</artifactId> 112 117 <version>5.1.0</version> 113 118 </dependency> 119 +120 + <!-- Bean utils -->121 + <dependency>122 + <groupId>commons-beanutils</groupId>123 + <artifactId>commons-beanutils</artifactId>124 + <version>${beanutils.version}</version>125 + </dependency>126 +114 127 115 128 </dependencies> 116 129 securis/src/main/java/net/curisit/securis/AppVersion.java
.. .. @@ -0,0 +1,158 @@ 1 +/*2 + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.3 + */4 +package net.curisit.securis;5 +6 +import java.io.IOException;7 +import java.text.MessageFormat;8 +import java.util.Properties;9 +10 +import org.apache.logging.log4j.LogManager;11 +import org.apache.logging.log4j.Logger;12 +13 +/**14 + * AppVersion15 + * <p>16 + * This class allows to read a version.properties file with information about17 + * application version This properties file is created automatically during18 + * compilation process. The source of this information is the version string19 + * inside pom.xml file. Format version has this format:20 + * {majorVersion}.{minorVersion}.{incrementalVersion}[-{qualifier}]21 + *22 + * @author cesar23 + * Last reviewed by JRA on Oct 5, 2025.24 + */25 +public class AppVersion {26 +27 + private static AppVersion instance;28 + private Properties prop;29 +30 + private static final String PROPERTIES_FILE_NAME = "/version.properties";31 + private static final Logger LOG = LogManager.getLogger(AppVersion.class);32 +33 + /**34 + * AppVersion<p>35 + * Constructor36 + */37 + private AppVersion() {38 + prop = new Properties();39 + try {40 + prop.load(getClass().getResourceAsStream(PROPERTIES_FILE_NAME));41 + } catch (IOException e) {42 + LOG.error("Version file is missing", e);43 + }44 + }45 +46 + /**47 + * getInstance<p>48 + * Get unique instance49 + *50 + * @return instance51 + */52 + public synchronized static AppVersion getInstance() {53 + if (instance == null) {54 + instance = new AppVersion();55 + }56 + return instance;57 + }58 +59 + /**60 + * getMajorVersion<p>61 + * Returns the major version62 + *63 + * @return majorVersion64 + */65 + public Integer getMajorVersion() {66 + return getParamAsInt(Keys.MAJOR_VERSION, -1);67 + }68 +69 + /**70 + * getMinorVersion<p>71 + * Return the minor version72 + *73 + * @return minorVersion74 + */75 + public Integer getMinorVersion() {76 + return getParamAsInt(Keys.MINOR_VERSION, -1);77 + }78 +79 + /**80 + * getIncrementalVersion<p>81 + * Returns the incremental version82 + *83 + * @return incrementalVersion84 + */85 + public Integer getIncrementalVersion() {86 + return getParamAsInt(Keys.INCREMENTAL_VERSION, -1);87 + }88 +89 + /**90 + * getQualifier<p>91 + * Returns qualifier if it exists92 + *93 + * @return qualifier94 + */95 + public String getQualifier() {96 + return getParam(Keys.QUALIFIER);97 + }98 +99 + /**100 + * getCompleteVersion<p>101 + * Return complete version102 + *103 + * @return completeVersion104 + */105 + public String getCompleteVersion() {106 + String strVersion = MessageFormat.format("{0}.{1}.{2}", getMajorVersion(), getMinorVersion(), getIncrementalVersion());107 + if (getQualifier() != null && !getQualifier().isEmpty())108 + strVersion = strVersion + "-" + getQualifier();109 + return strVersion;110 + }111 +112 + /*********************** Private methods *********************/113 +114 + /**115 + * getParam<p>116 + * Get the parameter associated with the key117 + *118 + * @param key119 + * @return param120 + */121 + private String getParam(String key) {122 + return prop.getProperty(key, null);123 + }124 +125 + /**126 + * getParamAsInt<p>127 + * Get the parameter as integer128 + *129 + * @param key130 + * @param defaulValue131 + * @return paramAsInt132 + */133 + private Integer getParamAsInt(String key, Integer defaulValue) {134 + String value = getParam(key);135 + try {136 + if (value == null) {137 + LOG.error("Wrong version field");138 + return defaulValue;139 + } else140 + return Integer.parseInt(value);141 + } catch (NumberFormatException e) {142 + LOG.error("Wrong version field");143 + return defaulValue;144 + }145 + }146 +147 + /**148 + * Keys<p>149 + * Application version keys150 + */151 + private static class Keys {152 + public static final String MAJOR_VERSION = "majorVersion";153 + public static final String MINOR_VERSION = "minorVersion";154 + public static final String INCREMENTAL_VERSION = "incrementalVersion";155 + public static final String QUALIFIER = "qualifier";156 + }157 +158 +}securis/src/main/java/net/curisit/securis/DefaultExceptionHandler.java
.. .. @@ -67,8 +67,6 @@ 67 67 HttpServletRequest request; 68 68 @Context 69 69 SecurityContext bsc; 70 - @Context71 - EntityManager em;72 70 73 71 /** 74 72 * toResponse .. .. @@ -81,20 +79,42 @@ 81 79 releaseEntityManager(); 82 80 if (e instanceof ForbiddenException) { 83 81 LOG.warn("ForbiddenException: {}", e.toString()); 84 - return Response.status(Status.UNAUTHORIZED).header(ERROR_CODE_MESSAGE_HEADER, ErrorCodes.INVALID_CREDENTIALS)85 - .header(ERROR_MESSAGE_HEADER, "Unathorized access to the application").type(MediaType.APPLICATION_JSON).build();82 + return Response.status(Status.UNAUTHORIZED)83 + .header(ERROR_CODE_MESSAGE_HEADER, ErrorCodes.INVALID_CREDENTIALS)84 + .header(ERROR_MESSAGE_HEADER, "Unathorized access to the application")85 + .type(MediaType.APPLICATION_JSON)86 + .build();86 87 } 87 88 88 89 if (e instanceof SeCurisServiceException) { 89 90 LOG.warn("SeCurisServiceException: {}", e.toString()); 90 - return Response.status(DEFAULT_APP_ERROR_STATUS_CODE).header(ERROR_CODE_MESSAGE_HEADER, ((SeCurisServiceException) e).getStatus())91 - .header(ERROR_MESSAGE_HEADER, e.getMessage()).type(MediaType.APPLICATION_JSON).build();91 + return Response.status(DEFAULT_APP_ERROR_STATUS_CODE)92 + .header(ERROR_CODE_MESSAGE_HEADER, ((SeCurisServiceException) e).getStatus())93 + .header(ERROR_MESSAGE_HEADER, e.getMessage())94 + .type(MediaType.APPLICATION_JSON)95 + .build();92 96 } 93 97 98 + String path = request != null ? request.getPathInfo() : null;99 + Object user = (bsc != null && bsc.getUserPrincipal() != null) ? bsc.getUserPrincipal() : null;100 + String host = request != null ? request.getRemoteHost() : null;101 + String ua = request != null ? request.getHeader("User-Agent") : null;102 + String url = request != null ? String.valueOf(request.getRequestURL()) : null;103 +104 + LOG.error("Unexpected error accessing to '{}' by user: {}", path, user);105 + LOG.error("Request sent from {}, with User-Agent: {}", host, ua);106 + LOG.error("Request url: {}", url, e);107 +108 + /**94 109 LOG.error("Unexpected error accesing to '{}' by user: {}", request.getPathInfo(), bsc.getUserPrincipal()); 95 110 LOG.error("Request sent from {}, with User-Agent: {}", request.getRemoteHost(), request.getHeader("User-Agent")); 96 111 LOG.error("Request url: " + request.getRequestURL(), e); 97 - return Response.serverError().header(ERROR_MESSAGE_HEADER, "Unexpected error: " + e.toString()).type(MediaType.APPLICATION_JSON).build();112 + */113 +114 + return Response.serverError()115 + .header(ERROR_MESSAGE_HEADER, "Unexpected error: " + e.toString())116 + .type(MediaType.APPLICATION_JSON)117 + .build();98 118 } 99 119 100 120 /** .. .. @@ -103,6 +123,8 @@ 103 123 * Best-effort cleanup: rollback active transaction (if joined) and close the {@link EntityManager}. 104 124 */ 105 125 private void releaseEntityManager() { 126 +127 + /**106 128 try { 107 129 if (em != null && em.isOpen()) { 108 130 LOG.debug("CLOSING EM: {}, trans: {}", em, em.isJoinedToTransaction()); .. .. @@ -116,5 +138,6 @@ 116 138 ex.printStackTrace(); 117 139 LOG.error("Error closing EM: {}, {}", em, ex); 118 140 } 141 + */119 142 } 120 143 } securis/src/main/java/net/curisit/securis/RestServicesApplication.java
.. .. @@ -31,7 +31,6 @@ 31 31 * @author JRA 32 32 * Last reviewed by JRA on Oct 5, 2025. 33 33 */ 34 -@ApplicationPath("/")35 34 public class RestServicesApplication extends Application { 36 35 37 36 private static final Logger LOG = LogManager.getLogger(RestServicesApplication.class); securis/src/main/java/net/curisit/securis/ioc/RequestsInterceptor.java
.. .. @@ -81,18 +81,67 @@ 81 81 */ 82 82 @Override 83 83 public void filter(ContainerRequestContext requestContext) throws IOException { 84 - EntityManager em = emProvider.getEntityManager();85 - LOG.debug("GETTING EM: {}", em);84 +85 + Method method = resourceInfo != null ? resourceInfo.getResourceMethod() : null;86 + if (method == null) {87 + LOG.warn("RequestsInterceptor: resource method is null");88 + return;89 + }86 90 87 - // Store EntityManager for later retrieval (writer interceptor)88 - requestContext.setProperty(EM_CONTEXT_PROPERTY, em);91 + boolean securable = method.isAnnotationPresent(Securable.class);92 + boolean ensureTransaction = method.isAnnotationPresent(EnsureTransaction.class);89 93 90 - Method method = resourceInfo.getResourceMethod();94 + // Only require injected helpers when the endpoint actually needs them95 + if (securable) {96 + if (tokenHelper == null || cache == null || emProvider == null) {97 + LOG.error(98 + "RequestsInterceptor is not fully initialized for secured endpoint '{}'. " +99 + "tokenHelper={}, cache={}, emProvider={}",100 + method.getName(), tokenHelper, cache, emProvider101 + );102 + requestContext.abortWith(103 + Response.status(Status.INTERNAL_SERVER_ERROR)104 + .entity("Security infrastructure not initialized")105 + .build()106 + );107 + return;108 + }91 109 92 - if (checkSecurableMethods(requestContext, method)) {93 - if (method.isAnnotationPresent(EnsureTransaction.class)) {94 - LOG.debug("Beginning transaction");95 - em.getTransaction().begin();110 + if (!checkSecurableMethods(requestContext, method)) {111 + return;112 + }113 + }114 +115 + // Only open/use EM when needed116 + if (ensureTransaction || securable) {117 + EntityManager em = getEntityManagerSafely();118 + if (em == null) {119 + LOG.error("No EntityManager available for method '{}'", method.getName());120 + requestContext.abortWith(121 + Response.status(Status.INTERNAL_SERVER_ERROR)122 + .entity("Persistence infrastructure not initialized")123 + .build()124 + );125 + return;126 + }127 +128 + LOG.debug("GETTING EM: {}", em);129 + requestContext.setProperty(EM_CONTEXT_PROPERTY, em);130 +131 + if (ensureTransaction) {132 + try {133 + if (!em.getTransaction().isActive()) {134 + LOG.debug("Beginning transaction");135 + em.getTransaction().begin();136 + }137 + } catch (Exception e) {138 + LOG.error("Error beginning transaction for method '{}'", method.getName(), e);139 + requestContext.abortWith(140 + Response.status(Status.INTERNAL_SERVER_ERROR)141 + .entity("Could not begin transaction")142 + .build()143 + );144 + }96 145 } 97 146 } 98 147 } .. .. @@ -107,9 +156,11 @@ 107 156 * @return true if request can proceed; false when aborted 108 157 */ 109 158 private boolean checkSecurableMethods(ContainerRequestContext ctx, Method method) { 110 - if (!method.isAnnotationPresent(Securable.class)) return true;159 + if (!method.isAnnotationPresent(Securable.class)) {160 + return true;161 + }111 162 112 - String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM);163 + String token = servletRequest != null ? servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM) : null;113 164 if (token == null || !tokenHelper.isTokenValid(token)) { 114 165 LOG.warn("Access denied, invalid token"); 115 166 ctx.abortWith(Response.status(Status.UNAUTHORIZED).build()); .. .. @@ -126,11 +177,30 @@ 126 177 return false; 127 178 } 128 179 129 - BasicSecurityContext sc = new BasicSecurityContext(username, roles, servletRequest.isSecure());180 + boolean secure = servletRequest != null && servletRequest.isSecure();181 + BasicSecurityContext sc = new BasicSecurityContext(username, roles, secure);130 182 sc.setOrganizationsIds(getUserOrganizations(username)); 131 183 sc.setApplicationsIds(getUserApplications(username)); 132 184 ctx.setSecurityContext(sc); 133 185 return true; 186 + }187 +188 + /**189 + * getEntityManagerSafely<p>190 + * Get the entity manager in a safely way191 + *192 + * @return entityManager193 + */194 + private EntityManager getEntityManagerSafely() {195 + try {196 + if (emProvider == null) {197 + return null;198 + }199 + return emProvider.getEntityManager();200 + } catch (Exception e) {201 + LOG.error("Error obtaining EntityManager from provider", e);202 + return null;203 + }134 204 } 135 205 136 206 // ------------------------------------------------------------- .. .. @@ -145,18 +215,31 @@ 145 215 * @return userRoles 146 216 */ 147 217 private int getUserRoles(String username) { 148 - if (username == null) return 0;149 - Integer cached = cache.get("roles_" + username, Integer.class);150 - if (cached != null) return cached;218 + if (username == null || cache == null) {219 + return 0;220 + }151 221 152 - EntityManager em = emProvider.getEntityManager();222 + Integer cached = cache.get("roles_" + username, Integer.class);223 + if (cached != null) {224 + return cached;225 + }226 +227 + EntityManager em = getEntityManagerSafely();228 + if (em == null) {229 + LOG.error("Cannot resolve user roles: EntityManager is not available");230 + return 0;231 + }232 +153 233 User user = em.find(User.class, username); 154 234 int roles = 0; 155 235 if (user != null) { 156 236 List<Integer> r = user.getRoles(); 157 - if (r != null) for (Integer role : r) roles += role;237 + if (r != null) {238 + for (Integer role : r) {239 + roles += role;240 + }241 + }158 242 cache.set("roles_" + username, roles, 3600); 159 - // also warm some caches160 243 cache.set("orgs_" + username, user.getOrgsIds(), 3600); 161 244 } 162 245 return roles; .. .. @@ -171,10 +254,22 @@ 171 254 * @return userOrganizations 172 255 */ 173 256 private Set<Integer> getUserOrganizations(String username) { 174 - Set<Integer> cached = cache.getSet("orgs_" + username, Integer.class);175 - if (cached != null) return cached;257 + if (username == null || cache == null) {258 + return Set.of();259 + }176 260 177 - User user = emProvider.getEntityManager().find(User.class, username);261 + Set<Integer> cached = cache.getSet("orgs_" + username, Integer.class);262 + if (cached != null) {263 + return cached;264 + }265 +266 + EntityManager em = getEntityManagerSafely();267 + if (em == null) {268 + LOG.error("Cannot resolve user organizations: EntityManager is not available");269 + return Set.of();270 + }271 +272 + User user = em.find(User.class, username);178 273 if (user != null) { 179 274 Set<Integer> result = user.getAllOrgsIds(); 180 275 cache.set("orgs_" + username, result, 3600); .. .. @@ -192,10 +287,22 @@ 192 287 * @return userApplications 193 288 */ 194 289 private Set<Integer> getUserApplications(String username) { 195 - Set<Integer> cached = cache.getSet("apps_" + username, Integer.class);196 - if (cached != null) return cached;290 + if (username == null || cache == null) {291 + return Set.of();292 + }197 293 198 - User user = emProvider.getEntityManager().find(User.class, username);294 + Set<Integer> cached = cache.getSet("apps_" + username, Integer.class);295 + if (cached != null) {296 + return cached;297 + }298 +299 + EntityManager em = getEntityManagerSafely();300 + if (em == null) {301 + LOG.error("Cannot resolve user applications: EntityManager is not available");302 + return Set.of();303 + }304 +305 + User user = em.find(User.class, username);199 306 if (user != null) { 200 307 Set<Integer> result = user.getAllAppsIds(); 201 308 cache.set("apps_" + username, result, 3600); .. .. @@ -218,30 +325,45 @@ 218 325 */ 219 326 @Override 220 327 public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { 221 - context.proceed();222 -223 - EntityManager em = (EntityManager) context.getProperty(EM_CONTEXT_PROPERTY);224 - if (em == null) return;225 -226 328 try { 227 - if (em.getTransaction().isActive()) {228 - if (servletResponse.getStatus() == Status.OK.getStatusCode()) {229 - em.getTransaction().commit();230 - LOG.debug("Transaction committed");231 - } else {232 - em.getTransaction().rollback();233 - LOG.debug("Transaction rolled back");234 - }235 - }329 + context.proceed();236 330 } finally { 237 - if (em.isOpen()) {331 + EntityManager em = (EntityManager) context.getProperty(EM_CONTEXT_PROPERTY);332 + if (em == null) {333 + return;334 + }335 +336 + try {337 + if (em.getTransaction() != null && em.getTransaction().isActive()) {338 + int status = servletResponse != null ? servletResponse.getStatus() : Status.INTERNAL_SERVER_ERROR.getStatusCode();339 + if (status >= 200 && status < 300) {340 + em.getTransaction().commit();341 + LOG.debug("Transaction committed");342 + } else {343 + em.getTransaction().rollback();344 + LOG.debug("Transaction rolled back");345 + }346 + }347 + } catch (Exception e) {348 + LOG.error("Error finalizing transaction", e);238 349 try { 239 - em.close();350 + if (em.getTransaction() != null && em.getTransaction().isActive()) {351 + em.getTransaction().rollback();352 + }353 + } catch (Exception rollbackEx) {354 + LOG.error("Error rolling back transaction", rollbackEx);355 + }356 + } finally {357 + try {358 + if (em.isOpen()) {359 + em.close();360 + }240 361 } catch (Exception e) { 241 362 LOG.error("Error closing EntityManager", e); 242 363 } 243 364 } 244 365 } 245 366 } 367 +246 368 } 247 369 securis/src/main/java/net/curisit/securis/services/BasicServices.java
.. .. @@ -28,8 +28,8 @@ 28 28 import org.apache.logging.log4j.LogManager; 29 29 import org.apache.logging.log4j.Logger; 30 30 31 -import net.curisit.integrity.AppVersion;32 -import net.curisit.integrity.commons.Utils;31 +import net.curisit.securis.AppVersion;32 +import net.curisit.securis.utils.Utils;33 33 import net.curisit.securis.ioc.EnsureTransaction; 34 34 import net.curisit.securis.security.Securable; 35 35 import net.curisit.securis.utils.TokenHelper; securis/src/main/java/net/curisit/securis/services/exception/CurisException.java
.. .. @@ -0,0 +1,86 @@ 1 +/*2 + * Copyright © 2015 CurisIT, S.L. All Rights Reserved.3 + */4 +package net.curisit.securis.services.exception;5 +6 +/**7 + * CurisException<p>8 + * This class manages the standard (before or after computation) error on computation that implies that the problem is reported9 + * and the computation can continue.10 + *11 + * @author JRA12 + * Last reviewed by APB on April 05, 2022.13 + */14 +public class CurisException extends Exception {15 +16 + private static final long serialVersionUID = 3830386897219028662L;17 +18 + // i18 code for message localization19 + String i18key = null;20 +21 + /**22 + * CurisException<p>23 + * This method is used to manage the standard exception with the message.24 + *25 + * @param msg26 + * The exception message27 + */28 + public CurisException(String msg) {29 + super(msg);30 + }31 +32 + /**33 + * CurisException<p>34 + * This method is used to manage the standard exception with the message and the cause.35 + *36 + * @param msg37 + * The exception message38 + * @param cause39 + * The error cause40 + */41 + public CurisException(String msg, Throwable cause) {42 + super(msg, cause);43 + }44 +45 + /**46 + * CurisException<p>47 + * This method is used to manage the standard exception with the message and the cause.48 + *49 + * @param msg50 + * The exception message51 + * @param cause52 + * The error cause53 + */54 + public CurisException(String msg, String i18k) {55 + this(msg);56 + this.i18key = i18k;57 + }58 +59 + /**60 + * CurisException<p>61 + * This method is used to manage the standard exception with the message, the i18 code and the cause.62 + *63 + * @param msg64 + * The exception message65 + * @param i18k66 + * The code for localization67 + * @param cause68 + * The error cause69 + */70 + public CurisException(String msg, String i18k, Throwable cause) {71 + this(msg, cause);72 + this.i18key = i18k;73 + }74 +75 + /**76 + * geti18key<p>77 + * This method is used to get the i18 code for localization.78 + *79 + * @return i18key80 + * The i18 code81 + */82 + public String geti18key() {83 + return i18key;84 + }85 +86 +}securis/src/main/java/net/curisit/securis/services/exception/SeCurisServiceException.java
.. .. @@ -3,7 +3,7 @@ 3 3 */ 4 4 package net.curisit.securis.services.exception; 5 5 6 -import net.curisit.integrity.exception.CurisException;6 +7 7 8 8 /** 9 9 * SeCurisServiceException securis/src/main/java/net/curisit/securis/utils/Utils.java
.. .. @@ -0,0 +1,719 @@ 1 +/*2 + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.3 + */4 +package net.curisit.securis.utils;5 +6 +import java.io.BufferedReader;7 +import java.io.File;8 +import java.io.IOException;9 +import java.io.InputStream;10 +import java.io.InputStreamReader;11 +import java.lang.reflect.InvocationTargetException;12 +import java.math.BigInteger;13 +import java.nio.ByteBuffer;14 +import java.nio.channels.FileChannel;15 +import java.nio.charset.Charset;16 +import java.nio.file.FileVisitResult;17 +import java.nio.file.Files;18 +import java.nio.file.Path;19 +import java.nio.file.Paths;20 +import java.nio.file.SimpleFileVisitor;21 +import java.nio.file.StandardOpenOption;22 +import java.nio.file.attribute.BasicFileAttributes;23 +import java.security.MessageDigest;24 +import java.security.NoSuchAlgorithmException;25 +import java.text.ParseException;26 +import java.text.SimpleDateFormat;27 +import java.time.Instant;28 +import java.time.LocalDate;29 +import java.time.LocalDateTime;30 +import java.time.ZoneId;31 +import java.util.ArrayList;32 +import java.util.Arrays;33 +import java.util.Calendar;34 +import java.util.Collection;35 +import java.util.Date;36 +import java.util.HashMap;37 +import java.util.HashSet;38 +import java.util.Iterator;39 +import java.util.LinkedList;40 +import java.util.List;41 +import java.util.Map;42 +import java.util.TimeZone;43 +import java.util.concurrent.CompletableFuture;44 +import java.util.concurrent.CompletionException;45 +import java.util.concurrent.TimeoutException;46 +import java.util.function.Supplier;47 +import java.util.regex.Pattern;48 +import java.util.stream.Collectors;49 +50 +import org.apache.commons.beanutils.BeanUtils;51 +import org.apache.commons.beanutils.PropertyUtils;52 +import org.apache.logging.log4j.LogManager;53 +import org.apache.logging.log4j.Logger;54 +55 +import net.curisit.integrity.exception.CurisRuntimeException;56 +57 +/**58 + * Utils59 + * <p>60 + * General-purpose static utilities: hashing, dates/times, collections, I/O helpers,61 + * simple email validation, reflection helpers, file tailing, etc.62 + *63 + * Author: JRA64 + * Last reviewed by JRA on Oct 6, 2025.65 + */66 +public class Utils {67 +68 + private static final Logger LOG = LogManager.getLogger(Utils.class);69 +70 + /* --------------------- Hash functions --------------------- */71 +72 + /**73 + * md5<p>74 + * Returns MD5 digest (hex, 32 chars) for the input text.75 + *76 + * @param str source text77 + * @return lowercase hex MD5 or null on error78 + */79 + public static String md5(String str) {80 + try {81 + MessageDigest mDigest = MessageDigest.getInstance("MD5");82 + mDigest.update(str.getBytes(), 0, str.length());83 + BigInteger i = new BigInteger(1, mDigest.digest());84 + return String.format("%1$032x", i);85 + } catch (NoSuchAlgorithmException e) {86 + LOG.error("Error generating MD5 for string: " + str, e);87 + }88 + return null;89 + }90 +91 + /**92 + * sha1<p>93 + * Returns SHA-1 digest (hex, 40 chars) for the input text.94 + *95 + * @param str source text96 + * @return lowercase hex SHA-1 or null on error97 + */98 + public static String sha1(String str) {99 + try {100 + MessageDigest mDigest = MessageDigest.getInstance("SHA1");101 + mDigest.update(str.getBytes(), 0, str.length());102 + BigInteger i = new BigInteger(1, mDigest.digest());103 + return String.format("%1$040x", i);104 + } catch (NoSuchAlgorithmException e) {105 + LOG.error("Error generating SHA1 for string: " + str, e);106 + }107 + return null;108 + }109 +110 + /**111 + * sha256<p>112 + * Returns SHA-256 digest (hex, 64 chars) for the input text.113 + *114 + * @param str source text115 + * @return lowercase hex SHA-256 or null on error116 + */117 + public static String sha256(String str) {118 + return sha256(str.getBytes());119 + }120 +121 + /**122 + * sha256<p>123 + * Returns SHA-256 digest (hex) for a byte array.124 + *125 + * @param bytes data126 + * @return hex SHA-256 or null on error127 + */128 + public static String sha256(byte[] bytes) {129 + try {130 + MessageDigest mDigest = MessageDigest.getInstance("SHA-256");131 + mDigest.update(bytes, 0, bytes.length);132 + BigInteger i = new BigInteger(1, mDigest.digest());133 + return String.format("%1$064x", i);134 + } catch (NoSuchAlgorithmException e) {135 + LOG.error("Error generating SHA-256 for bytes: " + bytes, e);136 + }137 + return null;138 + }139 +140 + /**141 + * sha256<p>142 + * Incrementally updates SHA-256 with multiple byte arrays and returns hex digest.143 + *144 + * @param bytes multiple byte chunks145 + * @return hex SHA-256 or null on error146 + */147 + public static String sha256(byte[]... bytes) {148 + try {149 + MessageDigest mDigest = MessageDigest.getInstance("SHA-256");150 + for (byte[] bs : bytes) {151 + mDigest.update(bs, 0, bs.length);152 + }153 + BigInteger i = new BigInteger(1, mDigest.digest());154 + return String.format("%1$064x", i);155 + } catch (NoSuchAlgorithmException e) {156 + LOG.error("Error generating SHA-256 for bytes: " + bytes, e);157 + }158 + return null;159 + }160 +161 + /* --------------------- ISO date helpers --------------------- */162 +163 + private static final String ISO_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";164 + private static final SimpleDateFormat sdf = new SimpleDateFormat(ISO_PATTERN);165 + static {166 + sdf.setTimeZone(TimeZone.getTimeZone("GMT"));167 + }168 +169 + /**170 + * toIsoFormat<p>171 + * Formats a Date to ISO-8601-like string with milliseconds in GMT.172 + *173 + * @param date input date174 + * @return formatted string175 + */176 + public static synchronized String toIsoFormat(Date date) {177 + return sdf.format(date);178 + }179 +180 + private static final String ISO_PATTERN_REDUCED_PRECISION = "yyyy-MM-dd'T'HH:mm:ssZ";181 + private static final SimpleDateFormat sdfReduced = new SimpleDateFormat(ISO_PATTERN_REDUCED_PRECISION);182 + static {183 + sdfReduced.setTimeZone(TimeZone.getTimeZone("GMT"));184 + }185 +186 + /**187 + * toIsoFormatReduced<p>188 + * Formats a Date to ISO string without milliseconds in GMT.189 + *190 + * @param date input date191 + * @return formatted string192 + */193 + public static synchronized String toIsoFormatReduced(Date date) {194 + return sdfReduced.format(date);195 + }196 +197 + /**198 + * toDateFromIso<p>199 + * Parses a string in {@link #ISO_PATTERN} into a Date (GMT).200 + *201 + * @param dateStr string to parse202 + * @return Date or null if parsing fails203 + */204 + public static synchronized Date toDateFromIso(String dateStr) {205 + try {206 + return sdf.parse(dateStr);207 + } catch (ParseException e) {208 + LOG.error("Error parsing string '{}' to Date object with pattern: {}", dateStr, ISO_PATTERN);209 + return null;210 + }211 + }212 +213 + /* --------------------- Null-safe helpers --------------------- */214 +215 + /**216 + * nonull<p>217 + * Returns {@code value} cast to T, or {@code defaultValue} if null.218 + *219 + * @param value input220 + * @param defaultValue fallback221 + * @return value or default222 + */223 + @SuppressWarnings("unchecked")224 + public static <T> T nonull(Object value, T defaultValue) {225 + return value == null ? defaultValue : (T) value;226 + }227 +228 + /**229 + * nonull<p>230 + * Returns String value or empty string if null.231 + *232 + * @param value input233 + * @return non-null string234 + */235 + public static String nonull(Object value) {236 + return nonull(value, "");237 + }238 +239 + /**240 + * value<p>241 + * Unchecked cast helper (avoid using unless necessary).242 + *243 + * @param value object244 + * @param type target type245 + * @return casted value246 + */247 + @SuppressWarnings("unchecked")248 + public static <T> T value(Object value, Class<T> type) {249 + return (T) value;250 + }251 +252 + /**253 + * createMap<p>254 + * Convenience to create a Map from varargs key/value pairs.255 + *256 + * @param keyValue alternating key, value257 + * @return map with pairs258 + */259 + @SuppressWarnings("unchecked")260 + public static <K extends Object, V extends Object> Map<K, V> createMap(Object... keyValue) {261 + Map<K, V> map = (Map<K, V>) new HashMap<Object, Object>();262 + for (int i = 0; i < keyValue.length; i += 2) {263 + ((Map<Object, Object>) map).put(keyValue[i], keyValue[i + 1]);264 + }265 + return map;266 + }267 +268 + /* --------------------- Date arithmetic --------------------- */269 +270 + /**271 + * addDays<p>272 + * Adds a number of days to a Date.273 + *274 + * @param date base date275 + * @param days positive/negative days276 + * @return new Date277 + */278 + public static Date addDays(Date date, int days) {279 + Calendar cal = Calendar.getInstance();280 + cal.setTime(date);281 + cal.add(Calendar.DATE, days);282 + return cal.getTime();283 + }284 +285 + /**286 + * addHours<p>287 + * Adds hours to a Date.288 + *289 + * @param date base date290 + * @param hours number of hours291 + * @return new Date292 + */293 + public static Date addHours(Date date, int hours) {294 + Calendar cal = Calendar.getInstance();295 + cal.setTime(date);296 + cal.add(Calendar.HOUR, hours);297 + return cal.getTime();298 + }299 +300 + /**301 + * addMinutes<p>302 + * Adds minutes to a Date.303 + *304 + * @param date base date305 + * @param minutes number of minutes306 + * @return new Date307 + */308 + public static Date addMinutes(Date date, int minutes) {309 + Calendar cal = Calendar.getInstance();310 + cal.setTime(date);311 + cal.add(Calendar.MINUTE, minutes);312 + return cal.getTime();313 + }314 +315 + /**316 + * getStrTime<p>317 + * Formats seconds as H:MM:SS (H omitted if 0). Negative yields "??".318 + *319 + * @param seconds total seconds320 + * @return formatted time321 + */322 + public static String getStrTime(int seconds) {323 + if (seconds < 0)324 + return "??";325 + String secs = String.format("%02d", seconds % 60);326 + String hours = "";327 + String mins = "0:";328 + if (seconds > 3600) {329 + hours = String.format("%d:", seconds / 3600);330 + }331 + if (seconds > 60) {332 + mins = String.format("%02d:", (seconds / 60) % 60);333 + }334 + return hours + mins + secs;335 + }336 +337 + /* --------------------- Collections & conversions --------------------- */338 +339 + /**340 + * areAllEmpty<p>341 + * Checks if all provided collections are empty.342 + *343 + * @param lists vararg of collections344 + * @return true if all empty345 + */346 + public static boolean areAllEmpty(Collection<?>... lists) {347 + for (Collection<?> list : lists) {348 + if (!list.isEmpty())349 + return false;350 + }351 + return true;352 + }353 +354 + /**355 + * toDateFromLocalDate<p>356 + * Converts a {@link LocalDate} to {@link Date} at system default zone start of day.357 + *358 + * @param ld local date359 + * @return Date or null360 + */361 + public static Date toDateFromLocalDate(LocalDate ld) {362 + if (ld == null)363 + return null;364 + Instant instant = ld.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();365 + return Date.from(instant);366 + }367 +368 + /**369 + * toLocalDateFromDate<p>370 + * Converts a {@link Date} to {@link LocalDate} at system default zone.371 + *372 + * @param date Date373 + * @return LocalDate or null374 + */375 + public static LocalDate toLocalDateFromDate(Date date) {376 + if (date == null)377 + return null;378 + Instant instant = Instant.ofEpochMilli(date.getTime());379 + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate();380 + }381 +382 + /**383 + * compare<p>384 + * Null-safe equals: both null → true; otherwise {@code equals()}.385 + *386 + * @param obj1 first object387 + * @param obj2 second object388 + * @return equality result389 + */390 + public static boolean compare(Object obj1, Object obj2) {391 + if (obj1 == null) {392 + return obj2 == null;393 + } else {394 + return obj1.equals(obj2);395 + }396 + }397 +398 + /* --------------------- Email validation --------------------- */399 +400 + private static final String EMAIL_PATTERN_STR = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";401 + private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_PATTERN_STR);402 +403 + /**404 + * isValidEmail<p>405 + * Validates an email address format with a basic regex.406 + *407 + * @param email candidate value408 + * @return valid or not409 + */410 + public static boolean isValidEmail(final String email) {411 + if (email == null) {412 + return false;413 + }414 + return EMAIL_PATTERN.matcher(email).matches();415 + }416 +417 + /* --------------------- Iterable helpers --------------------- */418 +419 + /**420 + * Reversed<p>421 + * Iterable wrapper to iterate a List in reverse order without copying.422 + *423 + * @param <T> element type424 + */425 + public static class Reversed<T> implements Iterable<T> {426 + private final List<T> original;427 +428 + public Reversed(List<T> original) {429 + this.original = original;430 + }431 +432 + public Iterator<T> iterator() {433 + return new Iterator<T>() {434 + int cursor = original.size();435 + public boolean hasNext() { return cursor > 0; }436 + public T next() { return original.get(--cursor); }437 + public void remove() { throw new CurisRuntimeException("ERROR removing item in read-only iterator"); }438 + };439 + }440 + }441 +442 + /**443 + * reversedList<p>444 + * Returns a reversed-iteration view of a list.445 + *446 + * @param original list to view447 + * @param <T> element type448 + * @return iterable reverse view449 + */450 + public static <T> Reversed<T> reversedList(List<T> original) {451 + return new Reversed<T>(original);452 + }453 +454 + /**455 + * isSorted<p>456 + * Checks if a list of Doubles is non-decreasing.457 + *458 + * @param original list459 + * @return true if sorted ascending460 + */461 + public static boolean isSorted(List<Double> original) {462 + for (int i = 0; i < (original.size() - 1); i++) {463 + Double v0 = original.get(i);464 + Double v1 = original.get(i + 1);465 + if (v0.doubleValue() > v1.doubleValue()) {466 + return false;467 + }468 + }469 + return true;470 + }471 +472 + /* --------------------- Simple file reading helpers --------------------- */473 +474 + /**475 + * readFileLines<p>476 + * Reads a file as UTF-8 and splits each line by a separator.477 + *478 + * @param file source file479 + * @param separator regex separator480 + * @return list of line fields481 + * @throws IOException on I/O errors482 + */483 + public static List<List<String>> readFileLines(File file, final String separator) throws IOException {484 + List<String> lines = Files.readAllLines(Paths.get(file.toURI()), Charset.forName("utf8"));485 + return lines.stream().map(line -> Arrays.asList(line.split(separator))).collect(Collectors.toList());486 + }487 +488 + /**489 + * readStreamLines<p>490 + * Reads an InputStream as UTF-8 and splits each line by a separator.491 + *492 + * @param stream input stream (caller closes it)493 + * @param separator regex separator494 + * @return list of line fields495 + * @throws IOException on I/O errors496 + */497 + public static List<List<String>> readStreamLines(InputStream stream, final String separator) throws IOException {498 + try (499 + InputStreamReader isr = new InputStreamReader(stream, Charset.forName("UTF-8"));500 + BufferedReader br = new BufferedReader(isr);501 + ) {502 + String line;503 + List<List<String>> lines = new ArrayList<>();504 + while ((line = br.readLine()) != null) {505 + lines.add(Arrays.asList(line.split(separator)));506 + }507 + return lines;508 + }509 + }510 +511 + /**512 + * deleteDirectoryTree<p>513 + * Recursively deletes a directory and its contents.514 + *515 + * @param baseDirectory directory path516 + * @throws IOException on failure517 + */518 + public static void deleteDirectoryTree(Path baseDirectory) throws IOException {519 + Files.walkFileTree(baseDirectory, new SimpleFileVisitor<Path>() {520 + @Override521 + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {522 + Files.deleteIfExists(file);523 + return FileVisitResult.CONTINUE;524 + }525 + @Override526 + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {527 + Files.deleteIfExists(dir);528 + return FileVisitResult.CONTINUE;529 + }530 + });531 + }532 +533 + /* --------------------- Directory size & tail --------------------- */534 +535 + private static class LongWrapper { long num = 0L; }536 +537 + /**538 + * getDirectoryContentSize<p>539 + * Computes total size (bytes) of files within a directory tree.540 + *541 + * @param baseDirectory root path542 + * @return total bytes543 + * @throws IOException on traversal errors544 + */545 + public static long getDirectoryContentSize(Path baseDirectory) throws IOException {546 + final LongWrapper size = new LongWrapper();547 + Files.walkFileTree(baseDirectory, new SimpleFileVisitor<Path>() {548 + @Override549 + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {550 + size.num += file.toFile().length();551 + return FileVisitResult.CONTINUE;552 + }553 + @Override554 + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {555 + if (!dir.equals(baseDirectory)) {556 + size.num += dir.toFile().length();557 + }558 + return FileVisitResult.CONTINUE;559 + }560 + });561 + return size.num;562 + }563 +564 + /**565 + * waitFor<p>566 + * Asynchronously waits until a condition is true or a timeout is reached.567 + *568 + * @param condition supplier that should eventually return true569 + * @param timeout max wait (ms)570 + * @return future completing with true if condition met571 + * @throws TimeoutException surfaced through CompletionException if timed out572 + */573 + public static CompletableFuture<Boolean> waitFor(Supplier<Boolean> condition, long timeout) throws TimeoutException {574 + return CompletableFuture.<Boolean>supplyAsync(() -> {575 + long t0 = System.currentTimeMillis();576 + while(!condition.get()) {577 + long t1 = System.currentTimeMillis();578 + if ((t1 - t0) > timeout) {579 + throw new CompletionException(new TimeoutException());580 + }581 + try {582 + Thread.sleep(100);583 + } catch (InterruptedException e) {584 + throw new CompletionException(e);585 + }586 + }587 + return true;588 + });589 + }590 +591 + private static final int BLOCK_SIZE = 4096; // 4KB592 +593 + /**594 + * getLastLines<p>595 + * Efficiently reads the last N lines of a text file by scanning from the end in blocks.596 + *597 + * @param file file to read598 + * @param numLines number of lines from the end599 + * @return list of lines in natural order (oldest→newest among the last N)600 + * @throws IOException on I/O errors601 + */602 + public static List<String> getLastLines(File file, int numLines) throws IOException {603 + LinkedList<String> lines = new LinkedList<>();604 + long fileSize = file.length();605 + int chunk = (fileSize < BLOCK_SIZE) ? (int)fileSize : BLOCK_SIZE;606 + long seekPos = fileSize - chunk;607 + FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ);608 + final byte newLine = (byte)'\n';609 + String partialLine = null;610 + while (lines.size() < numLines) {611 + ByteBuffer dst = ByteBuffer.allocate(chunk);612 + int bytesRead = fc.read(dst, seekPos);613 + byte[] content = dst.array();614 + int lastLineIdx = bytesRead;615 + for (int i = bytesRead - 1; i > 0 ; i--) {616 + byte b = content[i];617 + if (b == newLine) {618 + if (i < (bytesRead - 1) || partialLine != null) {619 + String line = new String(Arrays.copyOfRange(content, i, lastLineIdx)).trim();620 + if (partialLine != null) {621 + line += partialLine;622 + partialLine = null;623 + }624 + lines.addFirst(line);625 + if (lines.size() == numLines) {626 + return lines;627 + }628 + }629 + lastLineIdx = i;630 + }631 + }632 + if (lastLineIdx > 0) {633 + if (partialLine == null) {634 + partialLine = new String(Arrays.copyOfRange(content, 0, lastLineIdx)).trim();635 + } else {636 + partialLine = new String(Arrays.copyOfRange(content, 0, lastLineIdx)) + partialLine;637 + }638 + }639 + if (seekPos == 0) {640 + lines.addFirst(partialLine);641 + break;642 + }643 + if (seekPos < BLOCK_SIZE) {644 + chunk = (int)seekPos;645 + seekPos = 0;646 + } else {647 + seekPos -= BLOCK_SIZE;648 + }649 + }650 + return lines;651 + }652 +653 + /**654 + * removeDuplicates<p>655 + * Returns a sorted list without duplicates using a set roundtrip.656 + *657 + * @param list input list658 + * @param <T> type must be Comparable659 + * @return sorted distinct list660 + */661 + public static <T> List<T> removeDuplicates(List<T> list) {662 + return new HashSet<>(list).stream().sorted().collect(Collectors.toList());663 + }664 +665 + /* --------------------- Reflection helpers --------------------- */666 +667 + /**668 + * setProperty<p>669 + * Sets a bean property by name using Apache BeanUtils PropertyUtils.670 + *671 + * @param bean target bean672 + * @param fieldName property name (nested supported)673 + * @param value value to assign674 + * @throws RuntimeException wrapping reflection errors675 + */676 + public static void setProperty(Object bean, String fieldName, Object value) {677 + try {678 + PropertyUtils.setProperty(bean, fieldName, value);679 + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {680 + LOG.error("Missing field:" + fieldName);681 + throw new RuntimeException(e);682 + }683 + }684 +685 + /**686 + * getProperty<p>687 + * Reads a bean property by name using Apache PropertyUtils.688 + *689 + * @param bean target bean690 + * @param fieldName property name691 + * @return value692 + * @throws RuntimeException wrapping reflection errors693 + */694 + public static Object getProperty(Object bean, String fieldName) {695 + try {696 + return PropertyUtils.getProperty(bean, fieldName);697 + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {698 + LOG.error("Missing field:" + fieldName);699 + throw new RuntimeException(e);700 + }701 + }702 +703 + /**704 + * cloneBean<p>705 + * Clones a bean using Apache BeanUtils' copy mechanisms (shallow copy).706 + *707 + * @param bean source bean708 + * @return new cloned bean709 + * @throws CurisRuntimeException on cloning errors710 + */711 + public static Object cloneBean(Object bean) {712 + try {713 + return BeanUtils.cloneBean(bean);714 + } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) {715 + throw new CurisRuntimeException("Error cloning bean", e);716 + }717 + }718 +}719 +securis/src/main/webapp/WEB-INF/web.xml
.. .. @@ -36,11 +36,13 @@ 36 36 <param-name>resteasy.providers</param-name> 37 37 <param-value>net.curisit.securis.DefaultExceptionHandler</param-value> 38 38 </context-param> 39 + <!--39 40 <context-param> 40 41 <param-name>resteasy.injector.factory</param-name> 41 42 <param-value>org.jboss.resteasy.cdi.CdiInjectorFactory</param-value> 42 43 </context-param> 43 -44 + -->45 +44 46 <filter> 45 47 <filter-name>DevFilter</filter-name> 46 48 <filter-class> .. .. @@ -49,7 +51,7 @@ 49 51 </filter> 50 52 <filter-mapping> 51 53 <filter-name>DevFilter</filter-name> 52 - <url-pattern>/*</url-pattern>54 + <url-pattern>/api/*</url-pattern>53 55 </filter-mapping> 54 56 55 57 <filter> .. .. @@ -63,49 +65,50 @@ 63 65 <url-pattern>/main-bundle.js</url-pattern> 64 66 </filter-mapping> 65 67 66 - <filter>67 - <filter-name>Resteasy</filter-name>68 - <filter-class>69 - org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher70 - </filter-class>71 - <init-param>72 - <param-name>jakarta.ws.rs.Application</param-name>73 - <param-value>net.curisit.securis.RestServicesApplication</param-value>74 - </init-param>75 - </filter>76 -77 - <filter-mapping>78 - <filter-name>Resteasy</filter-name>79 - <url-pattern>/*</url-pattern>80 - </filter-mapping>68 + <servlet>69 + <servlet-name>Resteasy</servlet-name>70 + <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>71 + <init-param>72 + <param-name>jakarta.ws.rs.Application</param-name>73 + <param-value>net.curisit.securis.RestServicesApplication</param-value>74 + </init-param>75 + <load-on-startup>1</load-on-startup>76 + </servlet>77 +78 + <servlet-mapping>79 + <servlet-name>Resteasy</servlet-name>80 + <url-pattern>/api/*</url-pattern>81 + </servlet-mapping>81 82 82 83 <welcome-file-list> 83 - <welcome-file>/index.jsp</welcome-file>84 + <welcome-file>index.jsp</welcome-file>84 85 </welcome-file-list> 85 86 86 87 87 - <!-- Security roles referenced by this web application -->88 - <security-role>89 - <description>90 - Advance users, customers91 - </description>92 - <role-name>advance</role-name>93 - </security-role>94 - <security-role>95 - <description>96 - Administrator role97 - </description>98 - <role-name>admin</role-name>99 - </security-role>88 + <!-- Security roles referenced by this web application -->89 + <security-role>90 + <description>91 + Advance users, customers92 + </description>93 + <role-name>advance</role-name>94 + </security-role>95 + <security-role>96 + <description>97 + Administrator role98 + </description>99 + <role-name>admin</role-name>100 + </security-role>100 101 101 -<resource-env-ref>102 - <resource-env-ref-name>SeCurisDS</resource-env-ref-name>103 - <resource-env-ref-type>jakarta.sql.DataSource</resource-env-ref-type>104 -</resource-env-ref>105 -106 -<resource-env-ref>107 - <resource-env-ref-name>BeanManager</resource-env-ref-name>108 - <resource-env-ref-type>jakarta.enterprise.inject.spi.BeanManager</resource-env-ref-type>109 -</resource-env-ref>102 + <resource-env-ref>103 + <resource-env-ref-name>SeCurisDS</resource-env-ref-name>104 + <resource-env-ref-type>jakarta.sql.DataSource</resource-env-ref-type>105 + </resource-env-ref>106 +107 + <!--108 + <resource-env-ref>109 + <resource-env-ref-name>BeanManager</resource-env-ref-name>110 + <resource-env-ref-type>jakarta.enterprise.inject.spi.BeanManager</resource-env-ref-type>111 + </resource-env-ref>112 + -->110 113 111 114 </web-app>