#4410 - Comments on classes
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.IOException; |
|---|
| .. | .. |
|---|
| 17 | 20 | import org.apache.logging.log4j.LogManager; |
|---|
| 18 | 21 | import org.apache.logging.log4j.Logger; |
|---|
| 19 | 22 | |
|---|
| 23 | +/** |
|---|
| 24 | +* AuthFilter |
|---|
| 25 | +* <p> |
|---|
| 26 | +* Simple authentication/role wrapper for development and lightweight scenarios. |
|---|
| 27 | +* If a request parameter <code>user</code> or a session attribute <code>user</code> |
|---|
| 28 | +* is present, this filter wraps the current request with a custom {@link Principal} |
|---|
| 29 | +* and an ad-hoc role. The role assignment is temporary and follows the rule: |
|---|
| 30 | +* <ul> |
|---|
| 31 | +* <li>user == "advance" → role "advance"</li> |
|---|
| 32 | +* <li>otherwise → role "normal"</li> |
|---|
| 33 | +* </ul> |
|---|
| 34 | +* If no user is present, the request continues unmodified. |
|---|
| 35 | +* |
|---|
| 36 | +* <p><b>Security note:</b> This filter trusts a user name coming from a request parameter, |
|---|
| 37 | +* which must not be used in production. Replace with a proper authentication mechanism |
|---|
| 38 | +* (e.g., JWT, container security, SSO) and derive roles from authoritative claims. |
|---|
| 39 | +* |
|---|
| 40 | +* @author JRA |
|---|
| 41 | +* Last reviewed by JRA on Oct 6, 2025. |
|---|
| 42 | +*/ |
|---|
| 20 | 43 | @ApplicationScoped |
|---|
| 21 | 44 | @WebFilter(urlPatterns = "/*") |
|---|
| 22 | 45 | public class AuthFilter implements Filter { |
|---|
| 23 | 46 | |
|---|
| 24 | 47 | private static final Logger LOG = LogManager.getLogger(AuthFilter.class); |
|---|
| 25 | 48 | |
|---|
| 49 | + // --------------------------------------------------------------------- |
|---|
| 50 | + // Lifecycle |
|---|
| 51 | + // --------------------------------------------------------------------- |
|---|
| 52 | + |
|---|
| 53 | + /** |
|---|
| 54 | + * init<p> |
|---|
| 55 | + * Filter initialization hook (unused). |
|---|
| 56 | + */ |
|---|
| 26 | 57 | @Override |
|---|
| 27 | 58 | public void init(FilterConfig fc) throws ServletException { |
|---|
| 28 | 59 | } |
|---|
| 29 | 60 | |
|---|
| 61 | + // --------------------------------------------------------------------- |
|---|
| 62 | + // Filtering |
|---|
| 63 | + // --------------------------------------------------------------------- |
|---|
| 64 | + |
|---|
| 65 | + |
|---|
| 66 | + /** |
|---|
| 67 | + * doFilter |
|---|
| 68 | + * <p> |
|---|
| 69 | + * If a user is detected (request param or session attribute), wrap the request to: |
|---|
| 70 | + * <ul> |
|---|
| 71 | + * <li>Expose a {@link Principal} with the provided username.</li> |
|---|
| 72 | + * <li>Report a single role through {@link HttpServletRequest#isUserInRole(String)}.</li> |
|---|
| 73 | + * </ul> |
|---|
| 74 | + * Otherwise, pass-through. |
|---|
| 75 | + * |
|---|
| 76 | + * @param sr incoming request |
|---|
| 77 | + * @param sr1 outgoing response |
|---|
| 78 | + * @param fc filter chain |
|---|
| 79 | + */ |
|---|
| 30 | 80 | @Override |
|---|
| 31 | 81 | public void doFilter(ServletRequest sr, ServletResponse sr1, FilterChain fc) throws IOException, ServletException { |
|---|
| 32 | 82 | HttpServletRequest req = (HttpServletRequest) sr; |
|---|
| .. | .. |
|---|
| 46 | 96 | |
|---|
| 47 | 97 | } |
|---|
| 48 | 98 | |
|---|
| 99 | + /** |
|---|
| 100 | + * destroy<p> |
|---|
| 101 | + * Filter destruction hook (unused). |
|---|
| 102 | + */ |
|---|
| 49 | 103 | @Override |
|---|
| 50 | 104 | public void destroy() { |
|---|
| 51 | 105 | } |
|---|
| 52 | 106 | |
|---|
| 107 | + // --------------------------------------------------------------------- |
|---|
| 108 | + // Wrapper |
|---|
| 109 | + // --------------------------------------------------------------------- |
|---|
| 110 | + |
|---|
| 111 | + /** |
|---|
| 112 | + * UserRoleRequestWrapper |
|---|
| 113 | + * <p> |
|---|
| 114 | + * Wrapper that overrides role checks and the user principal when a synthetic user is present. |
|---|
| 115 | + */ |
|---|
| 53 | 116 | private class UserRoleRequestWrapper extends HttpServletRequestWrapper { |
|---|
| 54 | 117 | |
|---|
| 55 | 118 | private String role; |
|---|
| 56 | 119 | private String user; |
|---|
| 57 | 120 | |
|---|
| 121 | + /** |
|---|
| 122 | + * Constructor |
|---|
| 123 | + * <p> |
|---|
| 124 | + * @param role single role to expose via {@link #isUserInRole(String)} |
|---|
| 125 | + * @param user user name to expose via {@link #getUserPrincipal()} |
|---|
| 126 | + * @param request original request to wrap |
|---|
| 127 | + */ |
|---|
| 58 | 128 | public UserRoleRequestWrapper(String role, String user, HttpServletRequest request) { |
|---|
| 59 | 129 | super(request); |
|---|
| 60 | 130 | this.role = role; |
|---|
| 61 | 131 | this.user = user; |
|---|
| 62 | 132 | } |
|---|
| 63 | 133 | |
|---|
| 134 | + /** |
|---|
| 135 | + * isUserInRole |
|---|
| 136 | + * <p> |
|---|
| 137 | + * Returns {@code true} if the requested role equals the configured synthetic role. |
|---|
| 138 | + */ |
|---|
| 64 | 139 | @Override |
|---|
| 65 | 140 | public boolean isUserInRole(String role) { |
|---|
| 66 | 141 | LOG.info("isUserRole METHOD: {}, {}", role, this.role); |
|---|
| .. | .. |
|---|
| 70 | 145 | return this.role.equals(role); |
|---|
| 71 | 146 | } |
|---|
| 72 | 147 | |
|---|
| 148 | + /** |
|---|
| 149 | + * getUserPrincipal |
|---|
| 150 | + * <p> |
|---|
| 151 | + * Returns a minimal {@link Principal} with the configured user name; delegates otherwise. |
|---|
| 152 | + */ |
|---|
| 73 | 153 | @Override |
|---|
| 74 | 154 | public Principal getUserPrincipal() { |
|---|
| 75 | 155 | if (this.user == null) { |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import jakarta.persistence.EntityManager; |
|---|
| .. | .. |
|---|
| 17 | 20 | import net.curisit.securis.services.exception.SeCurisServiceException; |
|---|
| 18 | 21 | import net.curisit.securis.services.exception.SeCurisServiceException.ErrorCodes; |
|---|
| 19 | 22 | |
|---|
| 23 | +/** |
|---|
| 24 | +* DefaultExceptionHandler |
|---|
| 25 | +* <p> |
|---|
| 26 | +* JAX-RS {@link ExceptionMapper} that normalizes error responses across the API. |
|---|
| 27 | +* It also makes a best-effort to rollback and close a request-scoped {@link EntityManager} |
|---|
| 28 | +* if still open. |
|---|
| 29 | +* |
|---|
| 30 | +* <p>Response strategy: |
|---|
| 31 | +* <ul> |
|---|
| 32 | +* <li>{@link ForbiddenException} → 401 UNAUTHORIZED with app-specific error headers.</li> |
|---|
| 33 | +* <li>{@link SeCurisServiceException} → 418 (custom) with app error headers.</li> |
|---|
| 34 | +* <li>Other exceptions → 500 with generic message and request context logging.</li> |
|---|
| 35 | +* </ul> |
|---|
| 36 | +* |
|---|
| 37 | +* Headers: |
|---|
| 38 | +* <ul> |
|---|
| 39 | +* <li>{@code X-SECURIS-ERROR-MSG}</li> |
|---|
| 40 | +* <li>{@code X-SECURIS-ERROR-CODE}</li> |
|---|
| 41 | +* </ul> |
|---|
| 42 | +* |
|---|
| 43 | +* @author JRA |
|---|
| 44 | +* Last reviewed by JRA on Oct 6, 2025. |
|---|
| 45 | +*/ |
|---|
| 20 | 46 | @Provider |
|---|
| 21 | 47 | public class DefaultExceptionHandler implements ExceptionMapper<Exception> { |
|---|
| 48 | + |
|---|
| 22 | 49 | private static final Logger LOG = LogManager.getLogger(DefaultExceptionHandler.class); |
|---|
| 23 | | - |
|---|
| 50 | + |
|---|
| 51 | + /** Default status code used for application-defined errors. */ |
|---|
| 24 | 52 | public static final int DEFAULT_APP_ERROR_STATUS_CODE = 418; |
|---|
| 53 | + |
|---|
| 54 | + /** Header name carrying a human-readable error message. */ |
|---|
| 25 | 55 | public static final String ERROR_MESSAGE_HEADER = "X-SECURIS-ERROR-MSG"; |
|---|
| 56 | + |
|---|
| 57 | + /** Header name carrying a symbolic application error code. */ |
|---|
| 26 | 58 | public static final String ERROR_CODE_MESSAGE_HEADER = "X-SECURIS-ERROR-CODE"; |
|---|
| 27 | 59 | |
|---|
| 60 | + /** Default constructor (logs instantiation). */ |
|---|
| 28 | 61 | public DefaultExceptionHandler() { |
|---|
| 29 | 62 | LOG.info("Creating DefaultExceptionHandler "); |
|---|
| 30 | 63 | } |
|---|
| 31 | 64 | |
|---|
| 65 | + // Context objects injected by the runtime |
|---|
| 32 | 66 | @Context |
|---|
| 33 | 67 | HttpServletRequest request; |
|---|
| 34 | 68 | @Context |
|---|
| .. | .. |
|---|
| 36 | 70 | @Context |
|---|
| 37 | 71 | EntityManager em; |
|---|
| 38 | 72 | |
|---|
| 73 | + /** |
|---|
| 74 | + * toResponse |
|---|
| 75 | + * <p> |
|---|
| 76 | + * Map a thrown exception to an HTTP {@link Response}, releasing the {@link EntityManager} |
|---|
| 77 | + * if present. |
|---|
| 78 | + */ |
|---|
| 39 | 79 | @Override |
|---|
| 40 | 80 | public Response toResponse(Exception e) { |
|---|
| 41 | 81 | releaseEntityManager(); |
|---|
| .. | .. |
|---|
| 57 | 97 | return Response.serverError().header(ERROR_MESSAGE_HEADER, "Unexpected error: " + e.toString()).type(MediaType.APPLICATION_JSON).build(); |
|---|
| 58 | 98 | } |
|---|
| 59 | 99 | |
|---|
| 100 | + /** |
|---|
| 101 | + * releaseEntityManager |
|---|
| 102 | + * <p> |
|---|
| 103 | + * Best-effort cleanup: rollback active transaction (if joined) and close the {@link EntityManager}. |
|---|
| 104 | + */ |
|---|
| 60 | 105 | private void releaseEntityManager() { |
|---|
| 61 | 106 | try { |
|---|
| 62 | 107 | if (em != null && em.isOpen()) { |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.IOException; |
|---|
| .. | .. |
|---|
| 16 | 19 | import org.apache.logging.log4j.LogManager; |
|---|
| 17 | 20 | import org.apache.logging.log4j.Logger; |
|---|
| 18 | 21 | |
|---|
| 22 | +/** |
|---|
| 23 | +* DevFilter |
|---|
| 24 | +* <p> |
|---|
| 25 | +* Development-time CORS helper. Adds permissive CORS headers to allow front-end |
|---|
| 26 | +* resources (e.g. JS served from a different origin) to call the API. |
|---|
| 27 | +* Short-circuits <code>OPTIONS</code> preflight requests. |
|---|
| 28 | +* |
|---|
| 29 | +* <p><b>Security note:</b> This configuration is intentionally permissive and should be |
|---|
| 30 | +* restricted for production. |
|---|
| 31 | +* |
|---|
| 32 | +* @author JRA |
|---|
| 33 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 34 | +*/ |
|---|
| 19 | 35 | @ApplicationScoped |
|---|
| 20 | 36 | @WebFilter(urlPatterns = "/*") |
|---|
| 21 | 37 | public class DevFilter implements Filter { |
|---|
| .. | .. |
|---|
| 23 | 39 | @SuppressWarnings("unused") |
|---|
| 24 | 40 | private static final Logger log = LogManager.getLogger(DevFilter.class); |
|---|
| 25 | 41 | |
|---|
| 42 | + /** |
|---|
| 43 | + * init<p> |
|---|
| 44 | + * Filter init hook (unused). |
|---|
| 45 | + */ |
|---|
| 26 | 46 | @Override |
|---|
| 27 | 47 | public void init(FilterConfig fc) throws ServletException { |
|---|
| 28 | 48 | } |
|---|
| 29 | 49 | |
|---|
| 50 | + /** |
|---|
| 51 | + * doFilter |
|---|
| 52 | + * <p> |
|---|
| 53 | + * Add CORS headers and pass through non-OPTIONS methods to the next filter. |
|---|
| 54 | + */ |
|---|
| 30 | 55 | @Override |
|---|
| 31 | 56 | public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain fc) throws IOException, ServletException { |
|---|
| 32 | 57 | HttpServletRequest req = (HttpServletRequest) sreq; |
|---|
| .. | .. |
|---|
| 44 | 69 | } |
|---|
| 45 | 70 | } |
|---|
| 46 | 71 | |
|---|
| 72 | + /** |
|---|
| 73 | + * destroy<p> |
|---|
| 74 | + * Filter destroy hook (unused). |
|---|
| 75 | + */ |
|---|
| 47 | 76 | @Override |
|---|
| 48 | 77 | public void destroy() { |
|---|
| 49 | 78 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Date; |
|---|
| .. | .. |
|---|
| 9 | 12 | import net.curisit.securis.beans.SignedLicenseBean; |
|---|
| 10 | 13 | import net.curisit.securis.utils.JsonUtils; |
|---|
| 11 | 14 | |
|---|
| 15 | +/** |
|---|
| 16 | +* FreeLicenseGenerator |
|---|
| 17 | +* <p> |
|---|
| 18 | +* Helper to generate a signed FREE license (no expiration) for a given app and code. |
|---|
| 19 | +* |
|---|
| 20 | +* @author JRA |
|---|
| 21 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 22 | +*/ |
|---|
| 12 | 23 | public class FreeLicenseGenerator { |
|---|
| 13 | 24 | |
|---|
| 25 | + /** Constant license type code for FREE licenses. */ |
|---|
| 14 | 26 | public static final String FREE_LICENSE_TYPE = "FREE"; |
|---|
| 15 | 27 | |
|---|
| 28 | + /** |
|---|
| 29 | + * generateLicense |
|---|
| 30 | + * <p> |
|---|
| 31 | + * Build and sign a FREE license using the default generator. Uses a <code>Date(-1)</code> |
|---|
| 32 | + * sentinel as "no expiration". |
|---|
| 33 | + * |
|---|
| 34 | + * @param appName application name |
|---|
| 35 | + * @param licCode license code |
|---|
| 36 | + * @param metadata additional metadata to embed |
|---|
| 37 | + * @return signed license bean wrapper |
|---|
| 38 | + * @throws SeCurisException on generation/signature errors |
|---|
| 39 | + */ |
|---|
| 16 | 40 | public static SignedLicenseBean generateLicense(String appName, String licCode, Map<String, Object> metadata) throws SeCurisException { |
|---|
| 17 | 41 | SignedLicenseBean sl = null; |
|---|
| 18 | 42 | RequestBean rb = new RequestBean(); |
|---|
| .. | .. |
|---|
| 24 | 48 | return sl; |
|---|
| 25 | 49 | } |
|---|
| 26 | 50 | |
|---|
| 51 | + |
|---|
| 52 | + /** |
|---|
| 53 | + * Demo main |
|---|
| 54 | + * |
|---|
| 55 | + * @param args |
|---|
| 56 | + * @throws SeCurisException |
|---|
| 57 | + */ |
|---|
| 27 | 58 | public static void main(String[] args) throws SeCurisException { |
|---|
| 28 | 59 | Map<String, Object> metadata = new HashMap<>(); |
|---|
| 29 | 60 | metadata.put("max_docs", 2000); |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.IOException; |
|---|
| .. | .. |
|---|
| 18 | 21 | |
|---|
| 19 | 22 | import net.curisit.securis.utils.GZipServletResponseWrapper; |
|---|
| 20 | 23 | |
|---|
| 24 | +/** |
|---|
| 25 | +* GzipFilter |
|---|
| 26 | +* <p> |
|---|
| 27 | +* Servlet filter that compresses <code>*.js</code> responses with GZIP when the client |
|---|
| 28 | +* advertises <code>Accept-Encoding: gzip</code>. |
|---|
| 29 | +*/ |
|---|
| 21 | 30 | @ApplicationScoped |
|---|
| 22 | 31 | @WebFilter(urlPatterns = "*.js") |
|---|
| 23 | 32 | public class GzipFilter implements Filter { |
|---|
| .. | .. |
|---|
| 25 | 34 | @SuppressWarnings("unused") |
|---|
| 26 | 35 | private static final Logger LOG = LogManager.getLogger(GzipFilter.class); |
|---|
| 27 | 36 | |
|---|
| 37 | + /** init<p>Filter init hook (unused). */ |
|---|
| 28 | 38 | @Override |
|---|
| 29 | 39 | public void init(FilterConfig fc) throws ServletException { |
|---|
| 30 | 40 | } |
|---|
| 31 | 41 | |
|---|
| 42 | + /** |
|---|
| 43 | + * doFilter |
|---|
| 44 | + * <p> |
|---|
| 45 | + * Wrap the response with a GZIP-compressing wrapper if supported by the client. |
|---|
| 46 | + */ |
|---|
| 32 | 47 | @Override |
|---|
| 33 | 48 | public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain chain) throws IOException, ServletException { |
|---|
| 34 | 49 | HttpServletRequest httpRequest = (HttpServletRequest) sreq; |
|---|
| .. | .. |
|---|
| 44 | 59 | } |
|---|
| 45 | 60 | } |
|---|
| 46 | 61 | |
|---|
| 62 | + /** |
|---|
| 63 | + * acceptsGZipEncoding |
|---|
| 64 | + * <p> |
|---|
| 65 | + * @return {@code true} when request header contains "gzip" in <code>Accept-Encoding</code>. |
|---|
| 66 | + */ |
|---|
| 47 | 67 | private boolean acceptsGZipEncoding(HttpServletRequest httpRequest) { |
|---|
| 48 | 68 | String acceptEncoding = httpRequest.getHeader("Accept-Encoding"); |
|---|
| 49 | 69 | |
|---|
| 50 | 70 | return acceptEncoding != null && acceptEncoding.indexOf("gzip") != -1; |
|---|
| 51 | 71 | } |
|---|
| 52 | 72 | |
|---|
| 73 | + /** destroy<p>Filter destroy hook (unused). */ |
|---|
| 53 | 74 | @Override |
|---|
| 54 | 75 | public void destroy() { |
|---|
| 55 | 76 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.File; |
|---|
| .. | .. |
|---|
| 24 | 27 | import org.apache.logging.log4j.LogManager; |
|---|
| 25 | 28 | import org.apache.logging.log4j.Logger; |
|---|
| 26 | 29 | |
|---|
| 30 | +import jakarta.inject.Singleton; |
|---|
| 31 | + |
|---|
| 27 | 32 | /** |
|---|
| 28 | | - * License generator and signer |
|---|
| 29 | | - * |
|---|
| 30 | | - * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 31 | | - */ |
|---|
| 32 | | -@javax.inject.Singleton |
|---|
| 33 | +* LicenseGenerator |
|---|
| 34 | +* <p> |
|---|
| 35 | +* Factory for building and signing {@link LicenseBean} instances. Uses a process-wide |
|---|
| 36 | +* singleton and expects a PKCS#8 private key at: |
|---|
| 37 | +* <code>~/.SeCuris/keys/securis_private_key.pkcs8</code>. |
|---|
| 38 | +* |
|---|
| 39 | +* @author JRA |
|---|
| 40 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 41 | +*/ |
|---|
| 42 | +@Singleton |
|---|
| 33 | 43 | public class LicenseGenerator { |
|---|
| 34 | 44 | |
|---|
| 35 | 45 | private static final Logger LOG = LogManager.getLogger(LicenseGenerator.class); |
|---|
| 36 | 46 | |
|---|
| 37 | 47 | private static LicenseGenerator singleton = new LicenseGenerator(); |
|---|
| 38 | 48 | |
|---|
| 49 | + /** |
|---|
| 50 | + * getInstance<p> |
|---|
| 51 | + * Singleton accessor. |
|---|
| 52 | + */ |
|---|
| 39 | 53 | public static LicenseGenerator getInstance() { |
|---|
| 40 | 54 | return singleton; |
|---|
| 41 | 55 | } |
|---|
| 42 | 56 | |
|---|
| 43 | 57 | /** |
|---|
| 58 | + * generateLicense<p> |
|---|
| 44 | 59 | * Generate a license bean with the specified data |
|---|
| 45 | 60 | * |
|---|
| 46 | 61 | * @param req |
|---|
| .. | .. |
|---|
| 66 | 81 | } |
|---|
| 67 | 82 | |
|---|
| 68 | 83 | /** |
|---|
| 69 | | - * Generate a license file using a {@link LicenseBean} |
|---|
| 70 | | - * |
|---|
| 71 | | - * @param license |
|---|
| 72 | | - * @param file |
|---|
| 73 | | - * @throws SeCurisException |
|---|
| 74 | | - */ |
|---|
| 84 | + * save |
|---|
| 85 | + * <p> |
|---|
| 86 | + * Persist a pretty-printed JSON representation of the signed license to disk. |
|---|
| 87 | + * |
|---|
| 88 | + * @param license source license |
|---|
| 89 | + * @param file target file path |
|---|
| 90 | + * @throws SeCurisException if serialization or IO fails |
|---|
| 91 | + */ |
|---|
| 75 | 92 | public void save(LicenseBean license, File file) throws SeCurisException { |
|---|
| 76 | 93 | SignedLicenseBean signedLic = new SignedLicenseBean(license); |
|---|
| 77 | 94 | byte[] json; |
|---|
| .. | .. |
|---|
| 91 | 108 | } |
|---|
| 92 | 109 | |
|---|
| 93 | 110 | /** |
|---|
| 94 | | - * |
|---|
| 95 | | - * @param licBean |
|---|
| 96 | | - * @return |
|---|
| 97 | | - * @throws NoSuchAlgorithmException |
|---|
| 98 | | - * @throws IOException |
|---|
| 99 | | - * @throws InvalidKeySpecException |
|---|
| 100 | | - * @throws InvalidKeyException |
|---|
| 101 | | - * @throws SignatureException |
|---|
| 102 | | - */ |
|---|
| 111 | + * sign |
|---|
| 112 | + * <p> |
|---|
| 113 | + * Compute a Base64 signature for the given license and set it into the bean. |
|---|
| 114 | + * |
|---|
| 115 | + * @param licBean license to sign (in-place) |
|---|
| 116 | + * @return Base64 signature string |
|---|
| 117 | + * @throws SeCurisException if the signature process fails |
|---|
| 118 | + */ |
|---|
| 103 | 119 | public String sign(LicenseBean licBean) throws SeCurisException { |
|---|
| 104 | 120 | SignatureHelper sh = SignatureHelper.getInstance(); |
|---|
| 105 | 121 | |
|---|
| .. | .. |
|---|
| 114 | 130 | byte[] signatureData = signature.sign(); |
|---|
| 115 | 131 | licBean.setSignature(Base64.encodeBase64String(signatureData)); |
|---|
| 116 | 132 | return licBean.getSignature(); |
|---|
| 117 | | - } catch (NoSuchAlgorithmException e) { |
|---|
| 118 | | - LOG.error("Error signing license for " + licBean, e); |
|---|
| 119 | | - } catch (InvalidKeyException e) { |
|---|
| 120 | | - LOG.error("Error signing license for " + licBean, e); |
|---|
| 121 | | - } catch (InvalidKeySpecException e) { |
|---|
| 122 | | - LOG.error("Error signing license for " + licBean, e); |
|---|
| 123 | | - } catch (IOException e) { |
|---|
| 124 | | - LOG.error("Error signing license for " + licBean, e); |
|---|
| 125 | | - } catch (SignatureException e) { |
|---|
| 126 | | - LOG.error("Error signing license for " + licBean, e); |
|---|
| 133 | + } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | IOException | SignatureException e) { |
|---|
| 134 | + LOG.error("Error signing license for {}", licBean, e); |
|---|
| 127 | 135 | } |
|---|
| 128 | 136 | throw new SeCurisException("License could not be generated"); |
|---|
| 129 | 137 | } |
|---|
| .. | .. |
|---|
| 1 | +/*
|
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
|
|---|
| 3 | + */
|
|---|
| 1 | 4 | package net.curisit.securis;
|
|---|
| 2 | 5 |
|
|---|
| 3 | 6 | import java.util.HashSet;
|
|---|
| .. | .. |
|---|
| 19 | 22 | import org.apache.logging.log4j.LogManager;
|
|---|
| 20 | 23 | import org.apache.logging.log4j.Logger;
|
|---|
| 21 | 24 |
|
|---|
| 25 | +/**
|
|---|
| 26 | +* RestServicesApplication
|
|---|
| 27 | +* <p>
|
|---|
| 28 | +* JAX-RS application configuring the REST resource classes and interceptors.
|
|---|
| 29 | +* Declares base path <code>/</code>.
|
|---|
| 30 | +*
|
|---|
| 31 | +* @author JRA
|
|---|
| 32 | +* Last reviewed by JRA on Oct 5, 2025.
|
|---|
| 33 | +*/
|
|---|
| 22 | 34 | @ApplicationPath("/")
|
|---|
| 23 | 35 | public class RestServicesApplication extends Application {
|
|---|
| 24 | 36 |
|
|---|
| 25 | 37 | private static final Logger LOG = LogManager.getLogger(RestServicesApplication.class);
|
|---|
| 26 | 38 |
|
|---|
| 39 | + /**
|
|---|
| 40 | + * getClasses
|
|---|
| 41 | + * <p>
|
|---|
| 42 | + * @return set of REST endpoints and filters to be registered by the runtime
|
|---|
| 43 | + */
|
|---|
| 27 | 44 | @Override
|
|---|
| 28 | 45 | public Set<Class<?>> getClasses() {
|
|---|
| 29 | 46 | Set<Class<?>> classes = new HashSet<>();
|
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.beans; |
|---|
| 2 | 5 | |
|---|
| 6 | +/** |
|---|
| 7 | +* User |
|---|
| 8 | +* <p> |
|---|
| 9 | +* Placeholder bean for a system user. Intentionally empty in this snapshot. |
|---|
| 10 | +* Extend with fields (username, roles, etc.) and proper JSON/JPA annotations as needed. |
|---|
| 11 | +* |
|---|
| 12 | +* Note: Kept as-is to preserve current behavior. |
|---|
| 13 | +*/ |
|---|
| 3 | 14 | public class User { |
|---|
| 4 | 15 | |
|---|
| 5 | 16 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 30 | 33 | import com.fasterxml.jackson.annotation.JsonProperty; |
|---|
| 31 | 34 | |
|---|
| 32 | 35 | /** |
|---|
| 33 | | - * Entity implementation class for Entity: application |
|---|
| 34 | | - * |
|---|
| 35 | | - */ |
|---|
| 36 | +* Application |
|---|
| 37 | +* <p> |
|---|
| 38 | +* JPA entity that represents an application registered in the licensing server. |
|---|
| 39 | +* Includes descriptive fields and relationships to <code>LicenseType</code>, |
|---|
| 40 | +* <code>ApplicationMetadata</code> and <code>User</code>. |
|---|
| 41 | +* |
|---|
| 42 | +* Mapping details: |
|---|
| 43 | +* <ul> |
|---|
| 44 | +* <li>Table: <code>application</code></li> |
|---|
| 45 | +* <li>Named queries: <code>list-applications</code>, <code>list-applications-by_ids</code></li> |
|---|
| 46 | +* <li>Relationships: |
|---|
| 47 | +* <ul> |
|---|
| 48 | +* <li><code>@OneToMany</code> <b>licenseTypes</b> (mappedBy="application")</li> |
|---|
| 49 | +* <li><code>@OneToMany</code> <b>metadata</b> with cascade PERSIST/REMOVE/REFRESH</li> |
|---|
| 50 | +* <li><code>@ManyToMany</code> <b>users</b> via join table <code>user_application</code></li> |
|---|
| 51 | +* </ul> |
|---|
| 52 | +* </li> |
|---|
| 53 | +* </ul> |
|---|
| 54 | +*/ |
|---|
| 36 | 55 | @JsonAutoDetect |
|---|
| 37 | 56 | @JsonInclude(Include.NON_NULL) |
|---|
| 38 | 57 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| .. | .. |
|---|
| 46 | 65 | |
|---|
| 47 | 66 | private static final long serialVersionUID = 1L; |
|---|
| 48 | 67 | |
|---|
| 68 | + // ------------------------------------------------------------------ |
|---|
| 69 | + // Columns |
|---|
| 70 | + // ------------------------------------------------------------------ |
|---|
| 71 | + |
|---|
| 72 | + |
|---|
| 73 | + /** Surrogate primary key. */ |
|---|
| 49 | 74 | @Id |
|---|
| 50 | 75 | @GeneratedValue |
|---|
| 51 | 76 | private Integer id; |
|---|
| 52 | 77 | |
|---|
| 78 | + /** Unique short code for the application (business identifier). */ |
|---|
| 53 | 79 | private String code; |
|---|
| 80 | + |
|---|
| 81 | + /** Human-friendly application name. */ |
|---|
| 54 | 82 | private String name; |
|---|
| 83 | + |
|---|
| 84 | + /** Optional description. */ |
|---|
| 55 | 85 | private String description; |
|---|
| 56 | 86 | |
|---|
| 87 | + /** Default license file name suggested for downloads/exports. */ |
|---|
| 57 | 88 | @Column(name = "license_filename") |
|---|
| 58 | 89 | @JsonProperty("license_filename") |
|---|
| 59 | 90 | private String licenseFilename; |
|---|
| 60 | 91 | |
|---|
| 92 | + /** Creation timestamp (server-side). */ |
|---|
| 61 | 93 | @Column(name = "creation_timestamp") |
|---|
| 62 | 94 | @JsonProperty("creation_timestamp") |
|---|
| 63 | 95 | private Date creationTimestamp; |
|---|
| 64 | 96 | |
|---|
| 65 | | - // We don't include the referenced entities to limit the size of each row at |
|---|
| 66 | | - // // the listing |
|---|
| 97 | + // ----------------------- Relationships --------------------------- |
|---|
| 98 | + |
|---|
| 99 | + |
|---|
| 100 | + /** |
|---|
| 101 | + * License types attached to this application (ignored in default JSON to keep listings small). |
|---|
| 102 | + * |
|---|
| 103 | + * We don't include the referenced entities to limit the size of each row at the listing |
|---|
| 104 | + */ |
|---|
| 67 | 105 | @JsonIgnore |
|---|
| 68 | 106 | @OneToMany(fetch = FetchType.LAZY, mappedBy = "application") |
|---|
| 69 | 107 | private Set<LicenseType> licenseTypes; |
|---|
| 70 | 108 | |
|---|
| 109 | + /** |
|---|
| 110 | + * Metadata key/value entries for this application. |
|---|
| 111 | + */ |
|---|
| 71 | 112 | @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "application") |
|---|
| 72 | 113 | @JsonManagedReference |
|---|
| 73 | 114 | private Set<ApplicationMetadata> metadata; |
|---|
| 74 | 115 | |
|---|
| 116 | + /** |
|---|
| 117 | + * Users that have access/relationship with this application (ignored in JSON listings). |
|---|
| 118 | + */ |
|---|
| 75 | 119 | @JsonIgnore |
|---|
| 76 | 120 | // We don't include the users to limit the size of each row a the listing |
|---|
| 77 | 121 | @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) |
|---|
| .. | .. |
|---|
| 80 | 124 | inverseJoinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") }) |
|---|
| 81 | 125 | private Set<User> users; |
|---|
| 82 | 126 | |
|---|
| 127 | + // ------------------------------------------------------------------ |
|---|
| 128 | + // Getters & setters |
|---|
| 129 | + // ------------------------------------------------------------------ |
|---|
| 130 | + |
|---|
| 131 | + /** |
|---|
| 132 | + * getId<p> |
|---|
| 133 | + * Return the primary key. |
|---|
| 134 | + * |
|---|
| 135 | + * @return id |
|---|
| 136 | + */ |
|---|
| 83 | 137 | public Integer getId() { |
|---|
| 84 | 138 | return id; |
|---|
| 85 | 139 | } |
|---|
| 86 | 140 | |
|---|
| 141 | + /** |
|---|
| 142 | + * setId<p> |
|---|
| 143 | + * Set the primary key |
|---|
| 144 | + * |
|---|
| 145 | + * @param id |
|---|
| 146 | + */ |
|---|
| 87 | 147 | public void setId(Integer id) { |
|---|
| 88 | 148 | this.id = id; |
|---|
| 89 | 149 | } |
|---|
| 90 | 150 | |
|---|
| 151 | + /** |
|---|
| 152 | + * getName<p> |
|---|
| 153 | + * Get the name |
|---|
| 154 | + * |
|---|
| 155 | + * @return name |
|---|
| 156 | + */ |
|---|
| 91 | 157 | public String getName() { |
|---|
| 92 | 158 | return name; |
|---|
| 93 | 159 | } |
|---|
| 94 | 160 | |
|---|
| 161 | + /** |
|---|
| 162 | + * setName<p> |
|---|
| 163 | + * Set the name |
|---|
| 164 | + * |
|---|
| 165 | + * @param name |
|---|
| 166 | + */ |
|---|
| 95 | 167 | public void setName(String name) { |
|---|
| 96 | 168 | this.name = name; |
|---|
| 97 | 169 | } |
|---|
| 98 | 170 | |
|---|
| 171 | + /** |
|---|
| 172 | + * getDescription<p> |
|---|
| 173 | + * Get the description |
|---|
| 174 | + * |
|---|
| 175 | + * @return description |
|---|
| 176 | + */ |
|---|
| 99 | 177 | public String getDescription() { |
|---|
| 100 | 178 | return description; |
|---|
| 101 | 179 | } |
|---|
| 102 | 180 | |
|---|
| 181 | + /** |
|---|
| 182 | + * setDescription<p> |
|---|
| 183 | + * Set the description |
|---|
| 184 | + * |
|---|
| 185 | + * @param description |
|---|
| 186 | + */ |
|---|
| 103 | 187 | public void setDescription(String description) { |
|---|
| 104 | 188 | this.description = description; |
|---|
| 105 | 189 | } |
|---|
| 106 | 190 | |
|---|
| 191 | + /** |
|---|
| 192 | + * getCreationTimestamp<p> |
|---|
| 193 | + * Get the creation timestamp |
|---|
| 194 | + * |
|---|
| 195 | + * @return creationTimestamp |
|---|
| 196 | + */ |
|---|
| 107 | 197 | public Date getCreationTimestamp() { |
|---|
| 108 | 198 | return creationTimestamp; |
|---|
| 109 | 199 | } |
|---|
| 110 | 200 | |
|---|
| 201 | + /** |
|---|
| 202 | + * setCreationTimestamp<p> |
|---|
| 203 | + * Set the creation timestamp |
|---|
| 204 | + * |
|---|
| 205 | + * @param creationTimestamp |
|---|
| 206 | + */ |
|---|
| 111 | 207 | public void setCreationTimestamp(Date creationTimestamp) { |
|---|
| 112 | 208 | this.creationTimestamp = creationTimestamp; |
|---|
| 113 | 209 | } |
|---|
| 114 | 210 | |
|---|
| 211 | + /** |
|---|
| 212 | + * getApplicationMetadata<p> |
|---|
| 213 | + * Set the application metadata |
|---|
| 214 | + * |
|---|
| 215 | + * @return appMetadata |
|---|
| 216 | + */ |
|---|
| 115 | 217 | @JsonProperty("metadata") |
|---|
| 116 | 218 | public Set<ApplicationMetadata> getApplicationMetadata() { |
|---|
| 117 | 219 | return metadata; |
|---|
| 118 | 220 | } |
|---|
| 119 | 221 | |
|---|
| 222 | + /** |
|---|
| 223 | + * setApplicationMetadata<p> |
|---|
| 224 | + * Set the application metadata |
|---|
| 225 | + * |
|---|
| 226 | + * @param metadata |
|---|
| 227 | + */ |
|---|
| 120 | 228 | @JsonProperty("metadata") |
|---|
| 121 | 229 | public void setApplicationMetadata(Set<ApplicationMetadata> metadata) { |
|---|
| 122 | 230 | this.metadata = metadata; |
|---|
| 123 | 231 | } |
|---|
| 124 | 232 | |
|---|
| 233 | + /** |
|---|
| 234 | + * getLicenseFilename<p> |
|---|
| 235 | + * Get the license file name |
|---|
| 236 | + * |
|---|
| 237 | + * @return licenseFilename |
|---|
| 238 | + */ |
|---|
| 125 | 239 | public String getLicenseFilename() { |
|---|
| 126 | 240 | return licenseFilename; |
|---|
| 127 | 241 | } |
|---|
| 128 | 242 | |
|---|
| 243 | + /** |
|---|
| 244 | + * setLicenseFilename<p> |
|---|
| 245 | + * Set the license file name |
|---|
| 246 | + * |
|---|
| 247 | + * @param licenseFilename |
|---|
| 248 | + */ |
|---|
| 129 | 249 | public void setLicenseFilename(String licenseFilename) { |
|---|
| 130 | 250 | this.licenseFilename = licenseFilename; |
|---|
| 131 | 251 | } |
|---|
| 132 | 252 | |
|---|
| 253 | + /** |
|---|
| 254 | + * getLicenseTypes<p> |
|---|
| 255 | + * Get the license types |
|---|
| 256 | + * |
|---|
| 257 | + * @return licenseTypes |
|---|
| 258 | + */ |
|---|
| 133 | 259 | public Set<LicenseType> getLicenseTypes() { |
|---|
| 134 | 260 | LOG.info("Getting list license types!!!!"); |
|---|
| 135 | 261 | return licenseTypes; |
|---|
| 136 | 262 | } |
|---|
| 137 | 263 | |
|---|
| 264 | + /** |
|---|
| 265 | + * setLicenseTypes<p> |
|---|
| 266 | + * Set the license types |
|---|
| 267 | + * |
|---|
| 268 | + * @param licenseTypes |
|---|
| 269 | + */ |
|---|
| 138 | 270 | public void setLicenseTypes(Set<LicenseType> licenseTypes) { |
|---|
| 139 | 271 | this.licenseTypes = licenseTypes; |
|---|
| 140 | 272 | } |
|---|
| 141 | 273 | |
|---|
| 274 | + /** |
|---|
| 275 | + * getCode<p> |
|---|
| 276 | + * Get the application code |
|---|
| 277 | + * |
|---|
| 278 | + * @return code |
|---|
| 279 | + */ |
|---|
| 142 | 280 | public String getCode() { |
|---|
| 143 | 281 | return code; |
|---|
| 144 | 282 | } |
|---|
| 145 | 283 | |
|---|
| 284 | + /** |
|---|
| 285 | + * setCode<p> |
|---|
| 286 | + * Set the application code |
|---|
| 287 | + * |
|---|
| 288 | + * @param code |
|---|
| 289 | + */ |
|---|
| 146 | 290 | public void setCode(String code) { |
|---|
| 147 | 291 | this.code = code; |
|---|
| 148 | 292 | } |
|---|
| 149 | 293 | |
|---|
| 294 | + /** |
|---|
| 295 | + * equals<p> |
|---|
| 296 | + * Compares the current object with the given object |
|---|
| 297 | + * |
|---|
| 298 | + * @param object |
|---|
| 299 | + * @return isEquals |
|---|
| 300 | + */ |
|---|
| 150 | 301 | @Override |
|---|
| 151 | 302 | public boolean equals(Object obj) { |
|---|
| 152 | 303 | if (!(obj instanceof Application)) |
|---|
| .. | .. |
|---|
| 155 | 306 | return id.equals(other.id); |
|---|
| 156 | 307 | } |
|---|
| 157 | 308 | |
|---|
| 309 | + /** |
|---|
| 310 | + * hashCode<p> |
|---|
| 311 | + * Get the object hashCode |
|---|
| 312 | + * |
|---|
| 313 | + * @param hashCode |
|---|
| 314 | + */ |
|---|
| 158 | 315 | @Override |
|---|
| 159 | 316 | public int hashCode() { |
|---|
| 160 | 317 | |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 26 | 29 | import net.curisit.securis.db.common.Metadata; |
|---|
| 27 | 30 | |
|---|
| 28 | 31 | /** |
|---|
| 29 | | - * Entity implementation class for Entity: application_metadata |
|---|
| 30 | | - * |
|---|
| 31 | | - */ |
|---|
| 32 | +* ApplicationMetadata |
|---|
| 33 | +* <p> |
|---|
| 34 | +* Single metadata entry (key/value/mandatory) attached to an {@link Application}. |
|---|
| 35 | +* Uses a composite PK: (application_id, key). |
|---|
| 36 | +* <p> |
|---|
| 37 | +* Mapping details: |
|---|
| 38 | +* - Table: application_metadata |
|---|
| 39 | +* - PK: application_id + key (two @Id fields). |
|---|
| 40 | +* - application: @ManyToOne with @JsonBackReference to avoid JSON cycles. |
|---|
| 41 | +* - creation_timestamp exposed as "creation_timestamp". |
|---|
| 42 | +* |
|---|
| 43 | +* @author JRA |
|---|
| 44 | +* Last reviewed by JRA on Oct 7, 2025. |
|---|
| 45 | +*/ |
|---|
| 32 | 46 | @JsonAutoDetect |
|---|
| 33 | 47 | @JsonInclude(Include.NON_NULL) |
|---|
| 34 | 48 | @Entity |
|---|
| 35 | 49 | @Table(name = "application_metadata") |
|---|
| 36 | 50 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| 37 | | -@NamedQueries({ @NamedQuery(name = "list-application-metadata", query = "SELECT a FROM ApplicationMetadata a where a.application.id = :applicationId") }) |
|---|
| 51 | +@NamedQueries({ |
|---|
| 52 | + @NamedQuery(name = "list-application-metadata", |
|---|
| 53 | + query = "SELECT a FROM ApplicationMetadata a where a.application.id = :applicationId") |
|---|
| 54 | +}) |
|---|
| 38 | 55 | public class ApplicationMetadata implements Serializable, Metadata { |
|---|
| 39 | 56 | |
|---|
| 40 | | - private static final Logger LOG = LogManager.getLogger(ApplicationMetadata.class); |
|---|
| 57 | + private static final Logger LOG = LogManager.getLogger(ApplicationMetadata.class); |
|---|
| 41 | 58 | |
|---|
| 42 | | - private static final long serialVersionUID = 1L; |
|---|
| 59 | + private static final long serialVersionUID = 1L; |
|---|
| 43 | 60 | |
|---|
| 44 | | - @Id |
|---|
| 45 | | - @ManyToOne |
|---|
| 46 | | - @JoinColumn(name = "application_id") |
|---|
| 47 | | - @JsonBackReference |
|---|
| 48 | | - private Application application; |
|---|
| 61 | + /** Part of PK: owning application. */ |
|---|
| 62 | + @Id |
|---|
| 63 | + @ManyToOne |
|---|
| 64 | + @JoinColumn(name = "application_id") |
|---|
| 65 | + @JsonBackReference |
|---|
| 66 | + private Application application; |
|---|
| 49 | 67 | |
|---|
| 50 | | - @Id |
|---|
| 51 | | - @Column(name = "\"key\"") |
|---|
| 52 | | - private String key; |
|---|
| 68 | + /** Part of PK: metadata key (quoted column name). */ |
|---|
| 69 | + @Id |
|---|
| 70 | + @Column(name = "\"key\"") |
|---|
| 71 | + private String key; |
|---|
| 53 | 72 | |
|---|
| 54 | | - private String value; |
|---|
| 73 | + /** Arbitrary metadata value. */ |
|---|
| 74 | + private String value; |
|---|
| 55 | 75 | |
|---|
| 56 | | - private boolean mandatory; |
|---|
| 76 | + /** Whether this key is required for the parent application. */ |
|---|
| 77 | + private boolean mandatory; |
|---|
| 57 | 78 | |
|---|
| 58 | | - @Column(name = "creation_timestamp") |
|---|
| 59 | | - @JsonProperty("creation_timestamp") |
|---|
| 60 | | - private Date creationTimestamp; |
|---|
| 79 | + /** Server-side creation timestamp. */ |
|---|
| 80 | + @Column(name = "creation_timestamp") |
|---|
| 81 | + @JsonProperty("creation_timestamp") |
|---|
| 82 | + private Date creationTimestamp; |
|---|
| 61 | 83 | |
|---|
| 62 | | - public String getKey() { |
|---|
| 63 | | - return key; |
|---|
| 64 | | - } |
|---|
| 84 | + // --------------------------------------------------------------------- |
|---|
| 85 | + // Getters & setters |
|---|
| 86 | + // --------------------------------------------------------------------- |
|---|
| 65 | 87 | |
|---|
| 66 | | - public void setKey(String key) { |
|---|
| 67 | | - this.key = key; |
|---|
| 68 | | - } |
|---|
| 88 | + /** |
|---|
| 89 | + * getKey<p> |
|---|
| 90 | + * Get the metadata key (PK part). |
|---|
| 91 | + * |
|---|
| 92 | + * @return key |
|---|
| 93 | + */ |
|---|
| 94 | + public String getKey() { return key; } |
|---|
| 69 | 95 | |
|---|
| 70 | | - public Application getApplication() { |
|---|
| 71 | | - LOG.info("Getting application from app metadata: {}", application); |
|---|
| 72 | | - return application; |
|---|
| 73 | | - } |
|---|
| 96 | + /** |
|---|
| 97 | + * setKey<p> |
|---|
| 98 | + * Set the metadata key (PK part). |
|---|
| 99 | + * |
|---|
| 100 | + * @param key |
|---|
| 101 | + */ |
|---|
| 102 | + public void setKey(String key) { this.key = key; } |
|---|
| 74 | 103 | |
|---|
| 75 | | - public void setApplication(Application application) { |
|---|
| 76 | | - this.application = application; |
|---|
| 77 | | - } |
|---|
| 104 | + /** |
|---|
| 105 | + * getApplication<p> |
|---|
| 106 | + * Get the owning application. |
|---|
| 107 | + * |
|---|
| 108 | + * @return application |
|---|
| 109 | + */ |
|---|
| 110 | + public Application getApplication() { |
|---|
| 111 | + LOG.info("Getting application from app metadata: {}", application); |
|---|
| 112 | + return application; |
|---|
| 113 | + } |
|---|
| 78 | 114 | |
|---|
| 79 | | - public Date getCreationTimestamp() { |
|---|
| 80 | | - return creationTimestamp; |
|---|
| 81 | | - } |
|---|
| 115 | + /** |
|---|
| 116 | + * setApplication<p> |
|---|
| 117 | + * Set the owning application (PK part). |
|---|
| 118 | + * |
|---|
| 119 | + * @param application |
|---|
| 120 | + */ |
|---|
| 121 | + public void setApplication(Application application) { this.application = application; } |
|---|
| 82 | 122 | |
|---|
| 83 | | - public void setCreationTimestamp(Date creationTimestamp) { |
|---|
| 84 | | - this.creationTimestamp = creationTimestamp; |
|---|
| 85 | | - } |
|---|
| 123 | + /** |
|---|
| 124 | + * getCreationTimestamp<p> |
|---|
| 125 | + * Get the creation timestamp. |
|---|
| 126 | + * |
|---|
| 127 | + * @return creationTimestamp |
|---|
| 128 | + */ |
|---|
| 129 | + public Date getCreationTimestamp() { return creationTimestamp; } |
|---|
| 86 | 130 | |
|---|
| 87 | | - public String getValue() { |
|---|
| 88 | | - return value; |
|---|
| 89 | | - } |
|---|
| 131 | + /** |
|---|
| 132 | + * setCreationTimestamp<p> |
|---|
| 133 | + * Set the creation timestamp. |
|---|
| 134 | + * |
|---|
| 135 | + * @param creationTimestamp |
|---|
| 136 | + */ |
|---|
| 137 | + public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; } |
|---|
| 90 | 138 | |
|---|
| 91 | | - public void setValue(String value) { |
|---|
| 92 | | - this.value = value; |
|---|
| 93 | | - } |
|---|
| 139 | + /** |
|---|
| 140 | + * getValue<p> |
|---|
| 141 | + * Get the metadata value. |
|---|
| 142 | + * |
|---|
| 143 | + * @return value |
|---|
| 144 | + */ |
|---|
| 145 | + public String getValue() { return value; } |
|---|
| 94 | 146 | |
|---|
| 95 | | - public boolean isMandatory() { |
|---|
| 96 | | - return mandatory; |
|---|
| 97 | | - } |
|---|
| 147 | + /** |
|---|
| 148 | + * setValue<p> |
|---|
| 149 | + * Set the metadata value. |
|---|
| 150 | + * |
|---|
| 151 | + * @param value |
|---|
| 152 | + */ |
|---|
| 153 | + public void setValue(String value) { this.value = value; } |
|---|
| 98 | 154 | |
|---|
| 99 | | - public void setMandatory(boolean mandatory) { |
|---|
| 100 | | - this.mandatory = mandatory; |
|---|
| 101 | | - } |
|---|
| 155 | + /** |
|---|
| 156 | + * isMandatory<p> |
|---|
| 157 | + * Whether this entry is required. |
|---|
| 158 | + * |
|---|
| 159 | + * @return mandatory |
|---|
| 160 | + */ |
|---|
| 161 | + public boolean isMandatory() { return mandatory; } |
|---|
| 102 | 162 | |
|---|
| 103 | | - @Override |
|---|
| 104 | | - public String toString() { |
|---|
| 163 | + /** |
|---|
| 164 | + * setMandatory<p> |
|---|
| 165 | + * Mark this entry as required or optional. |
|---|
| 166 | + * |
|---|
| 167 | + * @param mandatory |
|---|
| 168 | + */ |
|---|
| 169 | + public void setMandatory(boolean mandatory) { this.mandatory = mandatory; } |
|---|
| 105 | 170 | |
|---|
| 106 | | - return String.format("AppMd (%s: %s)", this.key, value); |
|---|
| 107 | | - } |
|---|
| 171 | + // --------------------------------------------------------------------- |
|---|
| 172 | + // Object methods |
|---|
| 173 | + // --------------------------------------------------------------------- |
|---|
| 108 | 174 | |
|---|
| 109 | | - @Override |
|---|
| 110 | | - public boolean equals(Object obj) { |
|---|
| 111 | | - if (!(obj instanceof ApplicationMetadata)) |
|---|
| 112 | | - return false; |
|---|
| 113 | | - ApplicationMetadata other = (ApplicationMetadata) obj; |
|---|
| 114 | | - return Objects.equals(key, other.key) && Objects.equals(application, other.application); |
|---|
| 115 | | - } |
|---|
| 175 | + /** |
|---|
| 176 | + * toString<p> |
|---|
| 177 | + * Get the string describing the current object |
|---|
| 178 | + * |
|---|
| 179 | + * @return object string |
|---|
| 180 | + */ |
|---|
| 181 | + @Override |
|---|
| 182 | + public String toString() { return String.format("AppMd (%s: %s)", this.key, value); } |
|---|
| 116 | 183 | |
|---|
| 117 | | - @Override |
|---|
| 118 | | - public int hashCode() { |
|---|
| 119 | | - return Objects.hash(key, application); |
|---|
| 120 | | - } |
|---|
| 184 | + /** |
|---|
| 185 | + * equals<p> |
|---|
| 186 | + * Compare the current object with the given object |
|---|
| 187 | + * |
|---|
| 188 | + * @param object |
|---|
| 189 | + * @return isEquals |
|---|
| 190 | + */ |
|---|
| 191 | + @Override |
|---|
| 192 | + public boolean equals(Object obj) { |
|---|
| 193 | + if (!(obj instanceof ApplicationMetadata)) return false; |
|---|
| 194 | + ApplicationMetadata other = (ApplicationMetadata) obj; |
|---|
| 195 | + return Objects.equals(key, other.key) && Objects.equals(application, other.application); |
|---|
| 196 | + } |
|---|
| 121 | 197 | |
|---|
| 198 | + /** |
|---|
| 199 | + * hashCode<p> |
|---|
| 200 | + * Get the object hashCode |
|---|
| 201 | + * |
|---|
| 202 | + * @return hashCode |
|---|
| 203 | + */ |
|---|
| 204 | + @Override |
|---|
| 205 | + public int hashCode() { return Objects.hash(key, application); } |
|---|
| 122 | 206 | } |
|---|
| 207 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 21 | 24 | import com.fasterxml.jackson.annotation.JsonProperty; |
|---|
| 22 | 25 | |
|---|
| 23 | 26 | /** |
|---|
| 24 | | - * Entity implementation class for Entity: pack |
|---|
| 25 | | - * |
|---|
| 26 | | - */ |
|---|
| 27 | +* BlockedRequest |
|---|
| 28 | +* <p> |
|---|
| 29 | +* Persistent record marking a request (by hash) as blocked. |
|---|
| 30 | +* Primary key is the SHA-256 of the original request_data. |
|---|
| 31 | +* Useful to avoid replay/duplicate processing. |
|---|
| 32 | +* |
|---|
| 33 | +* Mapping details: |
|---|
| 34 | +* - Table: blocked_request |
|---|
| 35 | +* - PK: hash (SHA-256(request_data)) |
|---|
| 36 | +* - Optional relation 'blockedBy' for auditing. |
|---|
| 37 | +* |
|---|
| 38 | +* @author JRA |
|---|
| 39 | +* Last reviewed by JRA on Oct 7, 2025. |
|---|
| 40 | +*/ |
|---|
| 27 | 41 | @JsonAutoDetect |
|---|
| 28 | 42 | @JsonInclude(Include.NON_NULL) |
|---|
| 29 | 43 | @Entity |
|---|
| .. | .. |
|---|
| 33 | 47 | |
|---|
| 34 | 48 | private static final long serialVersionUID = 1L; |
|---|
| 35 | 49 | |
|---|
| 50 | + /** Unique SHA-256 hash of {@link #requestData}. */ |
|---|
| 36 | 51 | @Id |
|---|
| 37 | 52 | private String hash; |
|---|
| 38 | 53 | |
|---|
| 54 | + /** Original request payload. */ |
|---|
| 39 | 55 | @Column(name = "request_data") |
|---|
| 40 | 56 | @JsonProperty("request_data") |
|---|
| 41 | 57 | private String requestData; |
|---|
| 42 | 58 | |
|---|
| 59 | + /** Server-side creation timestamp. */ |
|---|
| 43 | 60 | @Column(name = "creation_timestamp") |
|---|
| 44 | 61 | @JsonProperty("creation_timestamp") |
|---|
| 45 | 62 | private Date creationTimestamp; |
|---|
| 46 | 63 | |
|---|
| 64 | + /** User who blocked this request (optional, auditing). */ |
|---|
| 47 | 65 | @JsonIgnore |
|---|
| 48 | 66 | @ManyToOne |
|---|
| 49 | 67 | @JoinColumn(name = "blocked_by") |
|---|
| 50 | 68 | private User blockedBy; |
|---|
| 51 | 69 | |
|---|
| 52 | | - public Date getCreationTimestamp() { |
|---|
| 53 | | - return creationTimestamp; |
|---|
| 54 | | - } |
|---|
| 70 | + // --------------------------------------------------------------------- |
|---|
| 71 | + // Getters & setters |
|---|
| 72 | + // --------------------------------------------------------------------- |
|---|
| 55 | 73 | |
|---|
| 56 | | - public void setCreationTimestamp(Date creationTimestamp) { |
|---|
| 57 | | - this.creationTimestamp = creationTimestamp; |
|---|
| 58 | | - } |
|---|
| 74 | + /** |
|---|
| 75 | + * getCreationTimestamp<p> |
|---|
| 76 | + * Get the creation timestamp. |
|---|
| 77 | + * |
|---|
| 78 | + * @return creationTimestamp |
|---|
| 79 | + */ |
|---|
| 80 | + public Date getCreationTimestamp() { return creationTimestamp; } |
|---|
| 59 | 81 | |
|---|
| 82 | + /** |
|---|
| 83 | + * setCreationTimestamp<p> |
|---|
| 84 | + * Set the creation timestamp. |
|---|
| 85 | + * |
|---|
| 86 | + * @param creationTimestamp |
|---|
| 87 | + */ |
|---|
| 88 | + public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; } |
|---|
| 89 | + |
|---|
| 90 | + /** |
|---|
| 91 | + * equals<p> |
|---|
| 92 | + * Identity based on primary key (hash). |
|---|
| 93 | + */ |
|---|
| 60 | 94 | @Override |
|---|
| 61 | 95 | public boolean equals(Object obj) { |
|---|
| 62 | | - if (!(obj instanceof BlockedRequest)) |
|---|
| 63 | | - return false; |
|---|
| 96 | + if (!(obj instanceof BlockedRequest)) return false; |
|---|
| 64 | 97 | BlockedRequest other = (BlockedRequest) obj; |
|---|
| 65 | | - return (hash == null && other.hash == null) || hash.equals(other.hash); |
|---|
| 98 | + return (hash == null && other.hash == null) || (hash != null && hash.equals(other.hash)); |
|---|
| 66 | 99 | } |
|---|
| 67 | 100 | |
|---|
| 101 | + /** |
|---|
| 102 | + * hashCode<p> |
|---|
| 103 | + * Hash based on primary key (hash). |
|---|
| 104 | + */ |
|---|
| 68 | 105 | @Override |
|---|
| 69 | | - public int hashCode() { |
|---|
| 106 | + public int hashCode() { return (hash == null ? 0 : hash.hashCode()); } |
|---|
| 70 | 107 | |
|---|
| 71 | | - return (hash == null ? 0 : hash.hashCode()); |
|---|
| 72 | | - } |
|---|
| 108 | + /** |
|---|
| 109 | + * getRequestData<p> |
|---|
| 110 | + * Get the original serialized request data. |
|---|
| 111 | + * |
|---|
| 112 | + * @return requestData |
|---|
| 113 | + */ |
|---|
| 114 | + public String getRequestData() { return requestData; } |
|---|
| 73 | 115 | |
|---|
| 74 | | - public String getRequestData() { |
|---|
| 75 | | - return requestData; |
|---|
| 76 | | - } |
|---|
| 77 | | - |
|---|
| 116 | + /** |
|---|
| 117 | + * setRequestData<p> |
|---|
| 118 | + * Set the original request data and recompute the PK hash immediately. |
|---|
| 119 | + * Hash is computed as SHA-256 over the request string. |
|---|
| 120 | + * |
|---|
| 121 | + * @param requestData |
|---|
| 122 | + */ |
|---|
| 78 | 123 | public void setRequestData(String requestData) { |
|---|
| 79 | 124 | this.requestData = requestData; |
|---|
| 80 | 125 | this.hash = generateHash(this.requestData); |
|---|
| 81 | 126 | } |
|---|
| 82 | 127 | |
|---|
| 83 | | - public User getBlockedBy() { |
|---|
| 84 | | - return blockedBy; |
|---|
| 85 | | - } |
|---|
| 128 | + /** |
|---|
| 129 | + * getBlockedBy<p> |
|---|
| 130 | + * Return the user who blocked this request (if any). |
|---|
| 131 | + * |
|---|
| 132 | + * @return blockedBy |
|---|
| 133 | + */ |
|---|
| 134 | + public User getBlockedBy() { return blockedBy; } |
|---|
| 86 | 135 | |
|---|
| 87 | | - public void setBlockedBy(User blockedBy) { |
|---|
| 88 | | - this.blockedBy = blockedBy; |
|---|
| 89 | | - } |
|---|
| 136 | + /** |
|---|
| 137 | + * setBlockedBy<p> |
|---|
| 138 | + * Set the user who blocked this request. |
|---|
| 139 | + * |
|---|
| 140 | + * @param blockedBy |
|---|
| 141 | + */ |
|---|
| 142 | + public void setBlockedBy(User blockedBy) { this.blockedBy = blockedBy; } |
|---|
| 90 | 143 | |
|---|
| 144 | + // --------------------------------------------------------------------- |
|---|
| 145 | + // Static helpers |
|---|
| 146 | + // --------------------------------------------------------------------- |
|---|
| 147 | + |
|---|
| 148 | + /** |
|---|
| 149 | + * generateHash<p> |
|---|
| 150 | + * Compute the SHA-256 hex string for the given request data. |
|---|
| 151 | + * |
|---|
| 152 | + * @param reqData |
|---|
| 153 | + * @return sha256(reqData) or null if reqData is null |
|---|
| 154 | + */ |
|---|
| 91 | 155 | public static String generateHash(String reqData) { |
|---|
| 92 | 156 | return (reqData != null ? Utils.sha256(reqData) : null); |
|---|
| 93 | 157 | } |
|---|
| 94 | 158 | |
|---|
| 159 | + /** |
|---|
| 160 | + * isRequestBlocked<p> |
|---|
| 161 | + * Check if a request payload is blocked by looking up its hash as the PK. |
|---|
| 162 | + * |
|---|
| 163 | + * @param requestData original payload |
|---|
| 164 | + * @param em JPA entity manager |
|---|
| 165 | + * @return true if an entry exists with the same hash |
|---|
| 166 | + */ |
|---|
| 95 | 167 | public static boolean isRequestBlocked(String requestData, EntityManager em) { |
|---|
| 96 | 168 | String hash = generateHash(requestData); |
|---|
| 97 | 169 | BlockedRequest br = em.find(BlockedRequest.class, hash); |
|---|
| 98 | 170 | return br != null; |
|---|
| 99 | 171 | } |
|---|
| 100 | | - |
|---|
| 101 | 172 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 41 | 44 | import net.curisit.securis.services.exception.SeCurisServiceException; |
|---|
| 42 | 45 | |
|---|
| 43 | 46 | /** |
|---|
| 44 | | - * Entity implementation class for Entity: license |
|---|
| 45 | | - * |
|---|
| 46 | | - */ |
|---|
| 47 | +* License |
|---|
| 48 | +* <p> |
|---|
| 49 | +* Main license entity. Contains identity, ownership, timestamps and payload fields. |
|---|
| 50 | +* Includes convenience JSON properties for related IDs/names. |
|---|
| 51 | +* |
|---|
| 52 | +* Mapping details: |
|---|
| 53 | +* - Table: license |
|---|
| 54 | +* - Listeners: CreationTimestampListener, ModificationTimestampListener |
|---|
| 55 | +* - Named queries: license-by-code, license-by-activation-code, last-code-suffix-used-in-pack, ... |
|---|
| 56 | +* - Status column uses custom Hibernate type: net.curisit.securis.db.common.LicenseStatusType |
|---|
| 57 | +* |
|---|
| 58 | +* @author JRA |
|---|
| 59 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 60 | +*/ |
|---|
| 47 | 61 | @JsonAutoDetect |
|---|
| 48 | 62 | @JsonInclude(Include.NON_NULL) |
|---|
| 49 | 63 | @Entity |
|---|
| 50 | 64 | @EntityListeners({ CreationTimestampListener.class, ModificationTimestampListener.class }) |
|---|
| 51 | 65 | @Table(name = "license") |
|---|
| 52 | 66 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| 53 | | -@NamedQueries({ @NamedQuery(name = "license-by-code", query = "SELECT l FROM License l where l.code = :code"), |
|---|
| 54 | | - @NamedQuery(name = "license-by-activation-code", query = "SELECT l FROM License l where l.activationCode = :activationCode"), |
|---|
| 55 | | - @NamedQuery(name = "last-code-suffix-used-in-pack", query = "SELECT max(l.codeSuffix) FROM License l where l.pack.id = :packId"), |
|---|
| 56 | | - @NamedQuery(name = "list-licenses-by-pack", query = "SELECT l FROM License l where l.pack.id = :packId"), |
|---|
| 57 | | - @NamedQuery(name = "list-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash"), |
|---|
| 58 | | - @NamedQuery(name = "list-active-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash and l.status in ('AC', 'PA')"), |
|---|
| 59 | | - @NamedQuery(name = "list-valid-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash and l.status in ('RE', 'AC', 'PA')") |
|---|
| 60 | | - |
|---|
| 67 | +@NamedQueries({ |
|---|
| 68 | + @NamedQuery(name = "license-by-code", query = "SELECT l FROM License l where l.code = :code"), |
|---|
| 69 | + @NamedQuery(name = "license-by-activation-code", query = "SELECT l FROM License l where l.activationCode = :activationCode"), |
|---|
| 70 | + @NamedQuery(name = "last-code-suffix-used-in-pack", query = "SELECT max(l.codeSuffix) FROM License l where l.pack.id = :packId"), |
|---|
| 71 | + @NamedQuery(name = "list-licenses-by-pack", query = "SELECT l FROM License l where l.pack.id = :packId"), |
|---|
| 72 | + @NamedQuery(name = "list-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash"), |
|---|
| 73 | + @NamedQuery(name = "list-active-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash and l.status in ('AC', 'PA')"), |
|---|
| 74 | + @NamedQuery(name = "list-valid-licenses-by-req-data", query = "SELECT l FROM License l where l.reqDataHash = :hash and l.status in ('RE', 'AC', 'PA')") |
|---|
| 61 | 75 | }) |
|---|
| 62 | 76 | public class License implements CreationTimestampEntity, ModificationTimestampEntity, Serializable { |
|---|
| 63 | 77 | |
|---|
| 64 | | - private static final long serialVersionUID = 2700310404904877227L; |
|---|
| 78 | + private static final long serialVersionUID = 2700310404904877227L; |
|---|
| 65 | 79 | |
|---|
| 66 | | - private static final Logger LOG = LogManager.getLogger(License.class); |
|---|
| 80 | + private static final Logger LOG = LogManager.getLogger(License.class); |
|---|
| 67 | 81 | |
|---|
| 68 | | - @Id |
|---|
| 69 | | - @GeneratedValue |
|---|
| 70 | | - private Integer id; |
|---|
| 82 | + // ------------------------------------------------------------------ |
|---|
| 83 | + // Columns & relations |
|---|
| 84 | + // ------------------------------------------------------------------ |
|---|
| 71 | 85 | |
|---|
| 72 | | - private String code; |
|---|
| 86 | + @Id |
|---|
| 87 | + @GeneratedValue |
|---|
| 88 | + private Integer id; |
|---|
| 73 | 89 | |
|---|
| 74 | | - @Column(name = "metadata_obsolete") |
|---|
| 75 | | - @JsonProperty("metadata_obsolete") |
|---|
| 76 | | - private Boolean metadataObsolete; |
|---|
| 90 | + private String code; |
|---|
| 77 | 91 | |
|---|
| 78 | | - @Column(name = "activation_code") |
|---|
| 79 | | - @JsonProperty("activation_code") |
|---|
| 80 | | - private String activationCode; |
|---|
| 92 | + @Column(name = "metadata_obsolete") |
|---|
| 93 | + @JsonProperty("metadata_obsolete") |
|---|
| 94 | + private Boolean metadataObsolete; |
|---|
| 81 | 95 | |
|---|
| 82 | | - @Column(name = "code_suffix") |
|---|
| 83 | | - @JsonProperty("code_suffix") |
|---|
| 84 | | - private Integer codeSuffix; |
|---|
| 96 | + @Column(name = "activation_code") |
|---|
| 97 | + @JsonProperty("activation_code") |
|---|
| 98 | + private String activationCode; |
|---|
| 85 | 99 | |
|---|
| 86 | | - @JsonIgnore |
|---|
| 87 | | - @ManyToOne |
|---|
| 88 | | - @JoinColumn(name = "pack_id") |
|---|
| 89 | | - private Pack pack; |
|---|
| 100 | + @Column(name = "code_suffix") |
|---|
| 101 | + @JsonProperty("code_suffix") |
|---|
| 102 | + private Integer codeSuffix; |
|---|
| 90 | 103 | |
|---|
| 91 | | - @JsonIgnore |
|---|
| 92 | | - @ManyToOne |
|---|
| 93 | | - @JoinColumn(name = "created_by") |
|---|
| 94 | | - private User createdBy; |
|---|
| 104 | + @JsonIgnore |
|---|
| 105 | + @ManyToOne |
|---|
| 106 | + @JoinColumn(name = "pack_id") |
|---|
| 107 | + private Pack pack; |
|---|
| 95 | 108 | |
|---|
| 96 | | - @JsonIgnore |
|---|
| 97 | | - @ManyToOne |
|---|
| 98 | | - @JoinColumn(name = "cancelled_by") |
|---|
| 99 | | - private User cancelledBy; |
|---|
| 109 | + @JsonIgnore |
|---|
| 110 | + @ManyToOne |
|---|
| 111 | + @JoinColumn(name = "created_by") |
|---|
| 112 | + private User createdBy; |
|---|
| 100 | 113 | |
|---|
| 101 | | - @Type(type = "net.curisit.securis.db.common.LicenseStatusType") |
|---|
| 102 | | - private LicenseStatus status; |
|---|
| 114 | + @JsonIgnore |
|---|
| 115 | + @ManyToOne |
|---|
| 116 | + @JoinColumn(name = "cancelled_by") |
|---|
| 117 | + private User cancelledBy; |
|---|
| 103 | 118 | |
|---|
| 104 | | - @Column(name = "full_name") |
|---|
| 105 | | - @JsonProperty("full_name") |
|---|
| 106 | | - private String fullName; |
|---|
| 119 | + @Type(type = "net.curisit.securis.db.common.LicenseStatusType") |
|---|
| 120 | + private LicenseStatus status; |
|---|
| 107 | 121 | |
|---|
| 108 | | - private String email; |
|---|
| 122 | + @Column(name = "full_name") |
|---|
| 123 | + @JsonProperty("full_name") |
|---|
| 124 | + private String fullName; |
|---|
| 109 | 125 | |
|---|
| 110 | | - @Column(name = "request_data") |
|---|
| 111 | | - @JsonProperty("request_data") |
|---|
| 112 | | - private String requestData; |
|---|
| 126 | + private String email; |
|---|
| 113 | 127 | |
|---|
| 114 | | - /** |
|---|
| 115 | | - * request data hash is automatically set when we use |
|---|
| 116 | | - * {@link License#setRequestData(String)} method |
|---|
| 117 | | - */ |
|---|
| 118 | | - @Column(name = "request_data_hash") |
|---|
| 119 | | - @JsonIgnore |
|---|
| 120 | | - private String reqDataHash; |
|---|
| 128 | + @Column(name = "request_data") |
|---|
| 129 | + @JsonProperty("request_data") |
|---|
| 130 | + private String requestData; |
|---|
| 121 | 131 | |
|---|
| 122 | | - @Column(name = "license_data") |
|---|
| 123 | | - @JsonProperty("license_data") |
|---|
| 124 | | - @JsonIgnore |
|---|
| 125 | | - // The license data is sent to user as a separate file, It doesn't need to |
|---|
| 126 | | - // be included as License attribute on browser |
|---|
| 127 | | - private String licenseData; |
|---|
| 132 | + /** |
|---|
| 133 | + * Request data hash (not serialized). Automatically updated by setRequestData(). |
|---|
| 134 | + */ |
|---|
| 135 | + @Column(name = "request_data_hash") |
|---|
| 136 | + @JsonIgnore |
|---|
| 137 | + private String reqDataHash; |
|---|
| 128 | 138 | |
|---|
| 129 | | - @Column(name = "creation_timestamp") |
|---|
| 130 | | - @JsonProperty("creation_timestamp") |
|---|
| 131 | | - private Date creationTimestamp; |
|---|
| 139 | + @Column(name = "license_data") |
|---|
| 140 | + @JsonProperty("license_data") |
|---|
| 141 | + @JsonIgnore |
|---|
| 142 | + // License data is delivered separately (e.g., file download). Not sent in list views. |
|---|
| 143 | + private String licenseData; |
|---|
| 132 | 144 | |
|---|
| 133 | | - @Column(name = "modification_timestamp") |
|---|
| 134 | | - @JsonProperty("modification_timestamp") |
|---|
| 135 | | - private Date modificationTimestamp; |
|---|
| 145 | + @Column(name = "creation_timestamp") |
|---|
| 146 | + @JsonProperty("creation_timestamp") |
|---|
| 147 | + private Date creationTimestamp; |
|---|
| 136 | 148 | |
|---|
| 137 | | - @Column(name = "last_access_timestamp") |
|---|
| 138 | | - @JsonProperty("last_access_timestamp") |
|---|
| 139 | | - private Date lastAccessTimestamp; |
|---|
| 149 | + @Column(name = "modification_timestamp") |
|---|
| 150 | + @JsonProperty("modification_timestamp") |
|---|
| 151 | + private Date modificationTimestamp; |
|---|
| 140 | 152 | |
|---|
| 141 | | - @Column(name = "expiration_date") |
|---|
| 142 | | - @JsonProperty("expiration_date") |
|---|
| 143 | | - private Date expirationDate; |
|---|
| 153 | + @Column(name = "last_access_timestamp") |
|---|
| 154 | + @JsonProperty("last_access_timestamp") |
|---|
| 155 | + private Date lastAccessTimestamp; |
|---|
| 144 | 156 | |
|---|
| 145 | | - private String comments; |
|---|
| 157 | + @Column(name = "expiration_date") |
|---|
| 158 | + @JsonProperty("expiration_date") |
|---|
| 159 | + private Date expirationDate; |
|---|
| 146 | 160 | |
|---|
| 147 | | - @OneToMany(fetch = FetchType.LAZY, mappedBy = "license") |
|---|
| 148 | | - @JsonIgnore |
|---|
| 149 | | - private List<LicenseHistory> history; |
|---|
| 161 | + private String comments; |
|---|
| 150 | 162 | |
|---|
| 151 | | - public Integer getId() { |
|---|
| 152 | | - return id; |
|---|
| 153 | | - } |
|---|
| 163 | + @OneToMany(fetch = FetchType.LAZY, mappedBy = "license") |
|---|
| 164 | + @JsonIgnore |
|---|
| 165 | + private List<LicenseHistory> history; |
|---|
| 154 | 166 | |
|---|
| 155 | | - public String getCode() { |
|---|
| 156 | | - return code; |
|---|
| 157 | | - } |
|---|
| 167 | + // ------------------------------------------------------------------ |
|---|
| 168 | + // Basic accessors |
|---|
| 169 | + // ------------------------------------------------------------------ |
|---|
| 158 | 170 | |
|---|
| 159 | | - public void setCode(String code) { |
|---|
| 160 | | - this.code = code; |
|---|
| 161 | | - } |
|---|
| 171 | + /** |
|---|
| 172 | + * getId<p> |
|---|
| 173 | + * Return primary key. |
|---|
| 174 | + * |
|---|
| 175 | + * @return id |
|---|
| 176 | + */ |
|---|
| 177 | + public Integer getId() { return id; } |
|---|
| 162 | 178 | |
|---|
| 163 | | - @Override |
|---|
| 164 | | - public Date getCreationTimestamp() { |
|---|
| 165 | | - return creationTimestamp; |
|---|
| 166 | | - } |
|---|
| 179 | + /** |
|---|
| 180 | + * getCode<p> |
|---|
| 181 | + * Return human-readable license code. |
|---|
| 182 | + * |
|---|
| 183 | + * @return code |
|---|
| 184 | + */ |
|---|
| 185 | + public String getCode() { return code; } |
|---|
| 167 | 186 | |
|---|
| 168 | | - @Override |
|---|
| 169 | | - public void setCreationTimestamp(Date creationTimestamp) { |
|---|
| 170 | | - this.creationTimestamp = creationTimestamp; |
|---|
| 171 | | - } |
|---|
| 187 | + /** |
|---|
| 188 | + * setCode<p> |
|---|
| 189 | + * Set human-readable license code. |
|---|
| 190 | + * |
|---|
| 191 | + * @param code |
|---|
| 192 | + */ |
|---|
| 193 | + public void setCode(String code) { this.code = code; } |
|---|
| 172 | 194 | |
|---|
| 173 | | - public User getCreatedBy() { |
|---|
| 174 | | - return createdBy; |
|---|
| 175 | | - } |
|---|
| 195 | + /** |
|---|
| 196 | + * getCreationTimestamp<p> |
|---|
| 197 | + * Required by CreationTimestampEntity. |
|---|
| 198 | + * |
|---|
| 199 | + * @return creationTimestamp |
|---|
| 200 | + */ |
|---|
| 201 | + @Override |
|---|
| 202 | + public Date getCreationTimestamp() { return creationTimestamp; } |
|---|
| 176 | 203 | |
|---|
| 177 | | - public void setCreatedBy(User createdBy) { |
|---|
| 178 | | - this.createdBy = createdBy; |
|---|
| 179 | | - } |
|---|
| 204 | + /** |
|---|
| 205 | + * setCreationTimestamp<p> |
|---|
| 206 | + * Set creation timestamp. |
|---|
| 207 | + * |
|---|
| 208 | + * @param creationTimestamp |
|---|
| 209 | + */ |
|---|
| 210 | + @Override |
|---|
| 211 | + public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; } |
|---|
| 180 | 212 | |
|---|
| 181 | | - public Pack getPack() { |
|---|
| 182 | | - return pack; |
|---|
| 183 | | - } |
|---|
| 213 | + /** |
|---|
| 214 | + * getCreatedBy<p> |
|---|
| 215 | + * Return creator user (entity). |
|---|
| 216 | + * |
|---|
| 217 | + * @return user |
|---|
| 218 | + */ |
|---|
| 219 | + public User getCreatedBy() { return createdBy; } |
|---|
| 184 | 220 | |
|---|
| 185 | | - public void setPack(Pack pack) { |
|---|
| 186 | | - this.pack = pack; |
|---|
| 187 | | - } |
|---|
| 221 | + /** |
|---|
| 222 | + * setCreatedBy<p> |
|---|
| 223 | + * Set creator user (entity). |
|---|
| 224 | + * |
|---|
| 225 | + * @param user |
|---|
| 226 | + */ |
|---|
| 227 | + public void setCreatedBy(User createdBy) { this.createdBy = createdBy; } |
|---|
| 188 | 228 | |
|---|
| 189 | | - @JsonProperty("created_by_id") |
|---|
| 190 | | - public String getCreatedById() { |
|---|
| 191 | | - return createdBy == null ? null : createdBy.getUsername(); |
|---|
| 192 | | - } |
|---|
| 229 | + /** |
|---|
| 230 | + * getPack<p> |
|---|
| 231 | + * Return owning pack. |
|---|
| 232 | + * |
|---|
| 233 | + * @return pack |
|---|
| 234 | + */ |
|---|
| 235 | + public Pack getPack() { return pack; } |
|---|
| 193 | 236 | |
|---|
| 194 | | - @JsonProperty("created_by_id") |
|---|
| 195 | | - public void setCreatedById(String username) { |
|---|
| 196 | | - if (username == null) { |
|---|
| 197 | | - createdBy = null; |
|---|
| 198 | | - } else { |
|---|
| 199 | | - createdBy = new User(); |
|---|
| 200 | | - createdBy.setUsername(username); |
|---|
| 201 | | - } |
|---|
| 202 | | - } |
|---|
| 237 | + /** |
|---|
| 238 | + * setPack<p> |
|---|
| 239 | + * Set owning pack. |
|---|
| 240 | + * |
|---|
| 241 | + * @param pack |
|---|
| 242 | + */ |
|---|
| 243 | + public void setPack(Pack pack) { this.pack = pack; } |
|---|
| 203 | 244 | |
|---|
| 204 | | - @JsonProperty("cancelled_by_id") |
|---|
| 205 | | - public String getCancelledById() { |
|---|
| 206 | | - return cancelledBy == null ? null : cancelledBy.getUsername(); |
|---|
| 207 | | - } |
|---|
| 245 | + /** |
|---|
| 246 | + * getCreatedById<p> |
|---|
| 247 | + * Expose creator username as JSON. |
|---|
| 248 | + * |
|---|
| 249 | + * @return username |
|---|
| 250 | + */ |
|---|
| 251 | + @JsonProperty("created_by_id") |
|---|
| 252 | + public String getCreatedById() { return createdBy == null ? null : createdBy.getUsername(); } |
|---|
| 208 | 253 | |
|---|
| 209 | | - @JsonProperty("cancelled_by_id") |
|---|
| 210 | | - public void setCancelledById(String username) { |
|---|
| 211 | | - if (username == null) { |
|---|
| 212 | | - cancelledBy = null; |
|---|
| 213 | | - } else { |
|---|
| 214 | | - cancelledBy = new User(); |
|---|
| 215 | | - cancelledBy.setUsername(username); |
|---|
| 216 | | - } |
|---|
| 217 | | - } |
|---|
| 254 | + /** |
|---|
| 255 | + * setCreatedById<p> |
|---|
| 256 | + * Setter by username for JSON binding. |
|---|
| 257 | + * |
|---|
| 258 | + * @param username |
|---|
| 259 | + */ |
|---|
| 260 | + @JsonProperty("created_by_id") |
|---|
| 261 | + public void setCreatedById(String username) { |
|---|
| 262 | + if (username == null) { |
|---|
| 263 | + createdBy = null; |
|---|
| 264 | + } else { |
|---|
| 265 | + createdBy = new User(); |
|---|
| 266 | + createdBy.setUsername(username); |
|---|
| 267 | + } |
|---|
| 268 | + } |
|---|
| 218 | 269 | |
|---|
| 219 | | - @JsonProperty("pack_code") |
|---|
| 220 | | - public String getPackCode() { |
|---|
| 221 | | - return pack == null ? null : pack.getCode(); |
|---|
| 222 | | - } |
|---|
| 270 | + /** |
|---|
| 271 | + * getCancelledById<p> |
|---|
| 272 | + * Expose cancelling user username as JSON. |
|---|
| 273 | + * |
|---|
| 274 | + * @return username |
|---|
| 275 | + */ |
|---|
| 276 | + @JsonProperty("cancelled_by_id") |
|---|
| 277 | + public String getCancelledById() { return cancelledBy == null ? null : cancelledBy.getUsername(); } |
|---|
| 223 | 278 | |
|---|
| 224 | | - @JsonProperty("pack_id") |
|---|
| 225 | | - public Integer getPackId() { |
|---|
| 226 | | - return pack == null ? null : pack.getId(); |
|---|
| 227 | | - } |
|---|
| 279 | + /** |
|---|
| 280 | + * setCancelledById<p> |
|---|
| 281 | + * Setter by username for JSON binding. |
|---|
| 282 | + * |
|---|
| 283 | + * @param username |
|---|
| 284 | + */ |
|---|
| 285 | + @JsonProperty("cancelled_by_id") |
|---|
| 286 | + public void setCancelledById(String username) { |
|---|
| 287 | + if (username == null) { |
|---|
| 288 | + cancelledBy = null; |
|---|
| 289 | + } else { |
|---|
| 290 | + cancelledBy = new User(); |
|---|
| 291 | + cancelledBy.setUsername(username); |
|---|
| 292 | + } |
|---|
| 293 | + } |
|---|
| 228 | 294 | |
|---|
| 229 | | - @JsonProperty("pack_id") |
|---|
| 230 | | - public void setPackId(Integer idPack) { |
|---|
| 231 | | - if (idPack == null) { |
|---|
| 232 | | - pack = null; |
|---|
| 233 | | - } else { |
|---|
| 234 | | - pack = new Pack(); |
|---|
| 235 | | - pack.setId(idPack); |
|---|
| 236 | | - } |
|---|
| 237 | | - } |
|---|
| 295 | + /** |
|---|
| 296 | + * getPackCode<p> |
|---|
| 297 | + * Expose pack code for convenience. |
|---|
| 298 | + * |
|---|
| 299 | + * @return packCode |
|---|
| 300 | + */ |
|---|
| 301 | + @JsonProperty("pack_code") |
|---|
| 302 | + public String getPackCode() { return pack == null ? null : pack.getCode(); } |
|---|
| 238 | 303 | |
|---|
| 239 | | - public LicenseStatus getStatus() { |
|---|
| 240 | | - return status; |
|---|
| 241 | | - } |
|---|
| 304 | + /** |
|---|
| 305 | + * getPackId<p> |
|---|
| 306 | + * Expose pack id for convenience. |
|---|
| 307 | + * |
|---|
| 308 | + * @return packId |
|---|
| 309 | + */ |
|---|
| 310 | + @JsonProperty("pack_id") |
|---|
| 311 | + public Integer getPackId() { return pack == null ? null : pack.getId(); } |
|---|
| 242 | 312 | |
|---|
| 243 | | - public void setStatus(LicenseStatus status) { |
|---|
| 244 | | - this.status = status; |
|---|
| 245 | | - } |
|---|
| 313 | + /** |
|---|
| 314 | + * setPackId<p> |
|---|
| 315 | + * Setter by id for JSON binding (creates a shallow Pack). |
|---|
| 316 | + * |
|---|
| 317 | + * @param packId |
|---|
| 318 | + */ |
|---|
| 319 | + @JsonProperty("pack_id") |
|---|
| 320 | + public void setPackId(Integer idPack) { |
|---|
| 321 | + if (idPack == null) { |
|---|
| 322 | + pack = null; |
|---|
| 323 | + } else { |
|---|
| 324 | + pack = new Pack(); |
|---|
| 325 | + pack.setId(idPack); |
|---|
| 326 | + } |
|---|
| 327 | + } |
|---|
| 246 | 328 | |
|---|
| 247 | | - @Override |
|---|
| 248 | | - public Date getModificationTimestamp() { |
|---|
| 249 | | - return modificationTimestamp; |
|---|
| 250 | | - } |
|---|
| 329 | + /** |
|---|
| 330 | + * getStatus<p> |
|---|
| 331 | + * Return license status. |
|---|
| 332 | + * |
|---|
| 333 | + * @return licenseStatus |
|---|
| 334 | + */ |
|---|
| 335 | + public LicenseStatus getStatus() { return status; } |
|---|
| 251 | 336 | |
|---|
| 252 | | - @Override |
|---|
| 253 | | - public void setModificationTimestamp(Date modificationTimestamp) { |
|---|
| 254 | | - this.modificationTimestamp = modificationTimestamp; |
|---|
| 255 | | - } |
|---|
| 337 | + /** |
|---|
| 338 | + * setStatus<p> |
|---|
| 339 | + * Set license status. |
|---|
| 340 | + * |
|---|
| 341 | + * @param status |
|---|
| 342 | + */ |
|---|
| 343 | + public void setStatus(LicenseStatus status) { this.status = status; } |
|---|
| 256 | 344 | |
|---|
| 257 | | - public String getFullName() { |
|---|
| 258 | | - return fullName; |
|---|
| 259 | | - } |
|---|
| 345 | + /** |
|---|
| 346 | + * getModificationTimestamp<p> |
|---|
| 347 | + * Required by ModificationTimestampEntity. |
|---|
| 348 | + * |
|---|
| 349 | + * @return modificationTimestamp |
|---|
| 350 | + */ |
|---|
| 351 | + @Override |
|---|
| 352 | + public Date getModificationTimestamp() { return modificationTimestamp; } |
|---|
| 260 | 353 | |
|---|
| 261 | | - public void setFullName(String fullName) { |
|---|
| 262 | | - this.fullName = fullName; |
|---|
| 263 | | - } |
|---|
| 354 | + /** |
|---|
| 355 | + * setModificationTimestamp<p> |
|---|
| 356 | + * Set modification timestamp. |
|---|
| 357 | + * |
|---|
| 358 | + * @param modificationTimestamp |
|---|
| 359 | + */ |
|---|
| 360 | + @Override |
|---|
| 361 | + public void setModificationTimestamp(Date modificationTimestamp) { this.modificationTimestamp = modificationTimestamp; } |
|---|
| 264 | 362 | |
|---|
| 265 | | - public String getEmail() { |
|---|
| 266 | | - return email; |
|---|
| 267 | | - } |
|---|
| 363 | + /** |
|---|
| 364 | + * getFullName<p> |
|---|
| 365 | + * Return license holder full name. |
|---|
| 366 | + * |
|---|
| 367 | + * @return name |
|---|
| 368 | + */ |
|---|
| 369 | + public String getFullName() { return fullName; } |
|---|
| 268 | 370 | |
|---|
| 269 | | - public void setEmail(String email) { |
|---|
| 270 | | - this.email = email; |
|---|
| 271 | | - } |
|---|
| 371 | + /** |
|---|
| 372 | + * setFullName<p> |
|---|
| 373 | + * Set license holder full name. |
|---|
| 374 | + * |
|---|
| 375 | + * @param name |
|---|
| 376 | + */ |
|---|
| 377 | + public void setFullName(String fullName) { this.fullName = fullName; } |
|---|
| 272 | 378 | |
|---|
| 273 | | - public void setId(Integer id) { |
|---|
| 274 | | - this.id = id; |
|---|
| 275 | | - } |
|---|
| 379 | + /** |
|---|
| 380 | + * getEmail<p> |
|---|
| 381 | + * Return email address. |
|---|
| 382 | + * |
|---|
| 383 | + * @return email |
|---|
| 384 | + */ |
|---|
| 385 | + public String getEmail() { return email; } |
|---|
| 276 | 386 | |
|---|
| 277 | | - public User getCancelledBy() { |
|---|
| 278 | | - return cancelledBy; |
|---|
| 279 | | - } |
|---|
| 387 | + /** |
|---|
| 388 | + * setEmail<p> |
|---|
| 389 | + * Set email address. |
|---|
| 390 | + * |
|---|
| 391 | + * @param email |
|---|
| 392 | + */ |
|---|
| 393 | + public void setEmail(String email) { this.email = email; } |
|---|
| 394 | + |
|---|
| 395 | + /** |
|---|
| 396 | + * setId<p> |
|---|
| 397 | + * Set primary key (rarely used). |
|---|
| 398 | + * |
|---|
| 399 | + * @param id |
|---|
| 400 | + */ |
|---|
| 401 | + public void setId(Integer id) { this.id = id; } |
|---|
| 402 | + |
|---|
| 403 | + /** |
|---|
| 404 | + * getCancelledBy<p> |
|---|
| 405 | + * Return cancelling user (entity). |
|---|
| 406 | + * |
|---|
| 407 | + * @param user |
|---|
| 408 | + */ |
|---|
| 409 | + public User getCancelledBy() { return cancelledBy; } |
|---|
| 410 | + |
|---|
| 411 | + /** |
|---|
| 412 | + * setCancelledBy<p> |
|---|
| 413 | + * Set cancelling user (entity). |
|---|
| 414 | + * |
|---|
| 415 | + * @param cancelledBy |
|---|
| 416 | + */ |
|---|
| 417 | + public void setCancelledBy(User cancelledBy) { this.cancelledBy = cancelledBy; } |
|---|
| 280 | 418 | |
|---|
| 281 | | - public void setCancelledBy(User cancelledBy) { |
|---|
| 282 | | - this.cancelledBy = cancelledBy; |
|---|
| 283 | | - } |
|---|
| 419 | + /** |
|---|
| 420 | + * getLastAccessTimestamp<p> |
|---|
| 421 | + * Return last access timestamp. |
|---|
| 422 | + * |
|---|
| 423 | + * @return lastAccessTimestamp |
|---|
| 424 | + */ |
|---|
| 425 | + public Date getLastAccessTimestamp() { return lastAccessTimestamp; } |
|---|
| 284 | 426 | |
|---|
| 285 | | - public Date getLastAccessTimestamp() { |
|---|
| 286 | | - return lastAccessTimestamp; |
|---|
| 287 | | - } |
|---|
| 427 | + /** |
|---|
| 428 | + * setLastAccessTimestamp<p> |
|---|
| 429 | + * Set last access timestamp. |
|---|
| 430 | + * |
|---|
| 431 | + * @param lastAccessTimestamp |
|---|
| 432 | + */ |
|---|
| 433 | + public void setLastAccessTimestamp(Date lastAccessTimestamp) { this.lastAccessTimestamp = lastAccessTimestamp; } |
|---|
| 288 | 434 | |
|---|
| 289 | | - public void setLastAccessTimestamp(Date lastAccessTimestamp) { |
|---|
| 290 | | - this.lastAccessTimestamp = lastAccessTimestamp; |
|---|
| 291 | | - } |
|---|
| 435 | + /** |
|---|
| 436 | + * getRequestData<p> |
|---|
| 437 | + * Return raw request data. |
|---|
| 438 | + * |
|---|
| 439 | + * @return requestData |
|---|
| 440 | + */ |
|---|
| 441 | + public String getRequestData() { return requestData; } |
|---|
| 292 | 442 | |
|---|
| 293 | | - public String getRequestData() { |
|---|
| 294 | | - return requestData; |
|---|
| 295 | | - } |
|---|
| 443 | + /** |
|---|
| 444 | + * setRequestData<p> |
|---|
| 445 | + * Set raw request data and recompute {@link #reqDataHash} immediately using |
|---|
| 446 | + * the same hashing strategy as BlockedRequest (SHA-256). |
|---|
| 447 | + * |
|---|
| 448 | + * @param requestData |
|---|
| 449 | + */ |
|---|
| 450 | + public void setRequestData(String requestData) { |
|---|
| 451 | + this.requestData = requestData; |
|---|
| 452 | + this.reqDataHash = BlockedRequest.generateHash(this.requestData); |
|---|
| 453 | + } |
|---|
| 296 | 454 | |
|---|
| 297 | | - public void setRequestData(String requestData) { |
|---|
| 298 | | - this.requestData = requestData; |
|---|
| 299 | | - this.reqDataHash = BlockedRequest.generateHash(this.requestData); |
|---|
| 300 | | - } |
|---|
| 455 | + /** |
|---|
| 456 | + * getLicenseData<p> |
|---|
| 457 | + * Return opaque license data (not serialized in lists). |
|---|
| 458 | + * |
|---|
| 459 | + * @return licenseData |
|---|
| 460 | + */ |
|---|
| 461 | + public String getLicenseData() { return licenseData; } |
|---|
| 301 | 462 | |
|---|
| 302 | | - public String getLicenseData() { |
|---|
| 303 | | - return licenseData; |
|---|
| 304 | | - } |
|---|
| 463 | + /** |
|---|
| 464 | + * setLicenseData<p> |
|---|
| 465 | + * Set opaque license data (large content kept server-side). |
|---|
| 466 | + * |
|---|
| 467 | + * @param licenseDate |
|---|
| 468 | + */ |
|---|
| 469 | + public void setLicenseData(String licenseData) { this.licenseData = licenseData; } |
|---|
| 305 | 470 | |
|---|
| 306 | | - public void setLicenseData(String licenseData) { |
|---|
| 307 | | - this.licenseData = licenseData; |
|---|
| 308 | | - } |
|---|
| 471 | + /** |
|---|
| 472 | + * getComments<p> |
|---|
| 473 | + * Return optional comments. |
|---|
| 474 | + * |
|---|
| 475 | + * @return comments |
|---|
| 476 | + */ |
|---|
| 477 | + public String getComments() { return comments; } |
|---|
| 309 | 478 | |
|---|
| 310 | | - public String getComments() { |
|---|
| 311 | | - return comments; |
|---|
| 312 | | - } |
|---|
| 479 | + /** |
|---|
| 480 | + * setComments<p> |
|---|
| 481 | + * Set optional comments. |
|---|
| 482 | + * |
|---|
| 483 | + * @param comments |
|---|
| 484 | + */ |
|---|
| 485 | + public void setComments(String comments) { this.comments = comments; } |
|---|
| 313 | 486 | |
|---|
| 314 | | - public void setComments(String comments) { |
|---|
| 315 | | - this.comments = comments; |
|---|
| 316 | | - } |
|---|
| 487 | + /** |
|---|
| 488 | + * getHistory<p> |
|---|
| 489 | + * Return change history entries (lazy). |
|---|
| 490 | + * |
|---|
| 491 | + * @return history |
|---|
| 492 | + */ |
|---|
| 493 | + public List<LicenseHistory> getHistory() { return history; } |
|---|
| 317 | 494 | |
|---|
| 318 | | - public List<LicenseHistory> getHistory() { |
|---|
| 319 | | - return history; |
|---|
| 320 | | - } |
|---|
| 495 | + /** |
|---|
| 496 | + * setHistory<p> |
|---|
| 497 | + * Set change history entries. |
|---|
| 498 | + * |
|---|
| 499 | + * @param history |
|---|
| 500 | + */ |
|---|
| 501 | + public void setHistory(List<LicenseHistory> history) { this.history = history; } |
|---|
| 321 | 502 | |
|---|
| 322 | | - public void setHistory(List<LicenseHistory> history) { |
|---|
| 323 | | - this.history = history; |
|---|
| 324 | | - } |
|---|
| 503 | + /** |
|---|
| 504 | + * getExpirationDate<p> |
|---|
| 505 | + * Return expiration date (nullable). |
|---|
| 506 | + * |
|---|
| 507 | + * @return expirationDate |
|---|
| 508 | + */ |
|---|
| 509 | + public Date getExpirationDate() { return expirationDate; } |
|---|
| 325 | 510 | |
|---|
| 326 | | - public Date getExpirationDate() { |
|---|
| 327 | | - return expirationDate; |
|---|
| 328 | | - } |
|---|
| 511 | + /** |
|---|
| 512 | + * setExpirationDate<p> |
|---|
| 513 | + * Set expiration date (nullable). |
|---|
| 514 | + * |
|---|
| 515 | + * @param expirationDate |
|---|
| 516 | + */ |
|---|
| 517 | + public void setExpirationDate(Date expirationDate) { this.expirationDate = expirationDate; } |
|---|
| 329 | 518 | |
|---|
| 330 | | - public void setExpirationDate(Date expirationDate) { |
|---|
| 331 | | - this.expirationDate = expirationDate; |
|---|
| 332 | | - } |
|---|
| 519 | + /** |
|---|
| 520 | + * getReqDataHash<p> |
|---|
| 521 | + * Return cached hash of request data (not exposed in JSON). |
|---|
| 522 | + * |
|---|
| 523 | + * @return reqDataHash |
|---|
| 524 | + */ |
|---|
| 525 | + public String getReqDataHash() { return reqDataHash; } |
|---|
| 333 | 526 | |
|---|
| 334 | | - public String getReqDataHash() { |
|---|
| 335 | | - return reqDataHash; |
|---|
| 336 | | - } |
|---|
| 527 | + /** |
|---|
| 528 | + * getCodeSuffix<p> |
|---|
| 529 | + * Return numeric suffix of the code. |
|---|
| 530 | + * |
|---|
| 531 | + * @return codeSuffix |
|---|
| 532 | + */ |
|---|
| 533 | + public Integer getCodeSuffix() { return codeSuffix; } |
|---|
| 337 | 534 | |
|---|
| 338 | | - public static class Action { |
|---|
| 339 | | - public static final int CREATE = 1; |
|---|
| 340 | | - public static final int REQUEST = 2; |
|---|
| 341 | | - public static final int ACTIVATION = 3; |
|---|
| 342 | | - public static final int SEND = 4; |
|---|
| 343 | | - public static final int DOWNLOAD = 5; |
|---|
| 344 | | - public static final int CANCEL = 6; |
|---|
| 345 | | - public static final int DELETE = 7; |
|---|
| 346 | | - public static final int BLOCK = 8; |
|---|
| 347 | | - public static final int UNBLOCK = 9; |
|---|
| 348 | | - } |
|---|
| 535 | + /** |
|---|
| 536 | + * setCodeSuffix<p> |
|---|
| 537 | + * Set numeric suffix of the code. |
|---|
| 538 | + * |
|---|
| 539 | + * @param codeSuffix |
|---|
| 540 | + */ |
|---|
| 541 | + public void setCodeSuffix(Integer codeSuffix) { this.codeSuffix = codeSuffix; } |
|---|
| 349 | 542 | |
|---|
| 350 | | - public static class Status { |
|---|
| 543 | + /** |
|---|
| 544 | + * getActivationCode<p> |
|---|
| 545 | + * Return activation code. |
|---|
| 546 | + * |
|---|
| 547 | + * @return activationCode |
|---|
| 548 | + */ |
|---|
| 549 | + public String getActivationCode() { return activationCode; } |
|---|
| 351 | 550 | |
|---|
| 352 | | - private static final Map<Integer, List<LicenseStatus>> transitions = Utils.createMap( // |
|---|
| 353 | | - Action.REQUEST, Arrays.asList(LicenseStatus.CREATED, LicenseStatus.REQUESTED), // |
|---|
| 354 | | - Action.ACTIVATION, Arrays.asList(LicenseStatus.CREATED, LicenseStatus.REQUESTED, LicenseStatus.PRE_ACTIVE, LicenseStatus.EXPIRED), // |
|---|
| 355 | | - Action.SEND, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE), // |
|---|
| 356 | | - Action.DOWNLOAD, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE), // |
|---|
| 357 | | - Action.CANCEL, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE, LicenseStatus.REQUESTED, LicenseStatus.EXPIRED), // |
|---|
| 358 | | - Action.DELETE, Arrays.asList(LicenseStatus.CANCELLED, LicenseStatus.CREATED, LicenseStatus.BLOCKED), // |
|---|
| 359 | | - Action.UNBLOCK, Arrays.asList(LicenseStatus.BLOCKED), // |
|---|
| 360 | | - Action.BLOCK, Arrays.asList(LicenseStatus.CANCELLED) // |
|---|
| 361 | | - ); |
|---|
| 551 | + /** |
|---|
| 552 | + * setActivationCode<p> |
|---|
| 553 | + * Set activation code. |
|---|
| 554 | + * |
|---|
| 555 | + * @param activationCode |
|---|
| 556 | + */ |
|---|
| 557 | + public void setActivationCode(String activationCode) { this.activationCode = activationCode; } |
|---|
| 362 | 558 | |
|---|
| 363 | | - /** |
|---|
| 364 | | - * It checks if a given action is valid for the License, passing the |
|---|
| 365 | | - * action and the current license status |
|---|
| 366 | | - * |
|---|
| 367 | | - * @param oldStatus |
|---|
| 368 | | - * @param newStatus |
|---|
| 369 | | - * @return |
|---|
| 370 | | - */ |
|---|
| 371 | | - public static boolean isActionValid(Integer action, LicenseStatus currentStatus) { |
|---|
| 372 | | - List<LicenseStatus> validStatuses = transitions.get(action); |
|---|
| 373 | | - LOG.info("Action {} is valid ? => {} current: {} OK? {}", action, validStatuses, currentStatus, validStatuses.contains(currentStatus)); |
|---|
| 374 | | - return validStatuses != null && validStatuses.contains(currentStatus); |
|---|
| 375 | | - } |
|---|
| 376 | | - } |
|---|
| 559 | + /** |
|---|
| 560 | + * isMetadataObsolete<p> |
|---|
| 561 | + * Convenience Boolean → primitive with null-safe false. |
|---|
| 562 | + * |
|---|
| 563 | + * @return isMetadataObsolete |
|---|
| 564 | + */ |
|---|
| 565 | + public boolean isMetadataObsolete() { return metadataObsolete != null && metadataObsolete; } |
|---|
| 377 | 566 | |
|---|
| 378 | | - /** |
|---|
| 379 | | - * Return licenses with status: REquested, ACtive, Pre-Active for a given |
|---|
| 380 | | - * request data |
|---|
| 381 | | - * |
|---|
| 382 | | - * @param requestData |
|---|
| 383 | | - * @param em |
|---|
| 384 | | - * @return |
|---|
| 385 | | - * @throws SeCurisServiceException |
|---|
| 386 | | - */ |
|---|
| 387 | | - public static License findValidLicenseByRequestData(String requestData, EntityManager em) throws SeCurisServiceException { |
|---|
| 388 | | - TypedQuery<License> query = em.createNamedQuery("list-valid-licenses-by-req-data", License.class); |
|---|
| 389 | | - query.setParameter("hash", BlockedRequest.generateHash(requestData)); |
|---|
| 390 | | - try { |
|---|
| 391 | | - List<License> list = query.getResultList(); |
|---|
| 392 | | - if (list.size() == 0) { |
|---|
| 393 | | - return null; |
|---|
| 394 | | - } |
|---|
| 395 | | - if (list.size() > 1) { |
|---|
| 396 | | - LOG.error("There are more than 1 active or requested license for request data: {}\nHash: {}", requestData, BlockedRequest.generateHash(requestData)); |
|---|
| 397 | | - } |
|---|
| 398 | | - return list.get(0); |
|---|
| 399 | | - } catch (NoResultException e) { |
|---|
| 400 | | - // There is no license for request data |
|---|
| 401 | | - return null; |
|---|
| 402 | | - } |
|---|
| 403 | | - } |
|---|
| 567 | + /** |
|---|
| 568 | + * setMetadataObsolete<p> |
|---|
| 569 | + * Set metadata obsolete flag (nullable wrapper). |
|---|
| 570 | + * |
|---|
| 571 | + * @param obsolete |
|---|
| 572 | + */ |
|---|
| 573 | + public void setMetadataObsolete(Boolean obsolete) { this.metadataObsolete = obsolete; } |
|---|
| 404 | 574 | |
|---|
| 405 | | - /** |
|---|
| 406 | | - * Return licenses with status: REquested, ACtive, Pre-Active for a given |
|---|
| 407 | | - * request data |
|---|
| 408 | | - * |
|---|
| 409 | | - * @param requestData |
|---|
| 410 | | - * @param em |
|---|
| 411 | | - * @return |
|---|
| 412 | | - * @throws SeCurisServiceException |
|---|
| 413 | | - */ |
|---|
| 414 | | - public static License findLicenseByActivationCode(String activationCode, EntityManager em) throws SeCurisServiceException { |
|---|
| 415 | | - TypedQuery<License> query = em.createNamedQuery("license-by-activation-code", License.class); |
|---|
| 416 | | - query.setParameter("activationCode", activationCode); |
|---|
| 417 | | - try { |
|---|
| 418 | | - return query.getSingleResult(); |
|---|
| 419 | | - } catch (NoResultException e) { |
|---|
| 420 | | - // There is no license for request data |
|---|
| 421 | | - return null; |
|---|
| 422 | | - } |
|---|
| 423 | | - } |
|---|
| 575 | + // ------------------------------------------------------------------ |
|---|
| 576 | + // Status transitions helpers |
|---|
| 577 | + // ------------------------------------------------------------------ |
|---|
| 424 | 578 | |
|---|
| 425 | | - public static License findActiveLicenseByRequestData(String requestData, EntityManager em) throws SeCurisServiceException { |
|---|
| 426 | | - TypedQuery<License> query = em.createNamedQuery("list-active-licenses-by-req-data", License.class); |
|---|
| 427 | | - query.setParameter("hash", BlockedRequest.generateHash(requestData)); |
|---|
| 428 | | - try { |
|---|
| 429 | | - List<License> list = query.getResultList(); |
|---|
| 430 | | - if (list.size() == 0) { |
|---|
| 431 | | - return null; |
|---|
| 432 | | - } |
|---|
| 433 | | - if (list.size() > 1) { |
|---|
| 434 | | - LOG.error("There are more than 1 active license for request data: {}\nHash: {}", requestData, BlockedRequest.generateHash(requestData)); |
|---|
| 435 | | - } |
|---|
| 436 | | - return list.get(0); |
|---|
| 437 | | - } catch (NoResultException e) { |
|---|
| 438 | | - // There is no license for request data |
|---|
| 439 | | - return null; |
|---|
| 440 | | - } |
|---|
| 441 | | - } |
|---|
| 579 | + /** |
|---|
| 580 | + * Action<p> |
|---|
| 581 | + * Actions to take with the license |
|---|
| 582 | + */ |
|---|
| 583 | + public static class Action { |
|---|
| 584 | + public static final int CREATE = 1; |
|---|
| 585 | + public static final int REQUEST = 2; |
|---|
| 586 | + public static final int ACTIVATION = 3; |
|---|
| 587 | + public static final int SEND = 4; |
|---|
| 588 | + public static final int DOWNLOAD = 5; |
|---|
| 589 | + public static final int CANCEL = 6; |
|---|
| 590 | + public static final int DELETE = 7; |
|---|
| 591 | + public static final int BLOCK = 8; |
|---|
| 592 | + public static final int UNBLOCK = 9; |
|---|
| 593 | + } |
|---|
| 442 | 594 | |
|---|
| 443 | | - public static License findLicenseByCode(String code, EntityManager em) throws SeCurisServiceException { |
|---|
| 444 | | - TypedQuery<License> query = em.createNamedQuery("license-by-code", License.class); |
|---|
| 445 | | - query.setParameter("code", code); |
|---|
| 446 | | - try { |
|---|
| 447 | | - return query.getSingleResult(); |
|---|
| 448 | | - } catch (NoResultException e) { |
|---|
| 449 | | - // There is no license for request data |
|---|
| 450 | | - return null; |
|---|
| 451 | | - } |
|---|
| 452 | | - } |
|---|
| 595 | + /** |
|---|
| 596 | + * Status<p> |
|---|
| 597 | + * Status of the requested license |
|---|
| 598 | + */ |
|---|
| 599 | + public static class Status { |
|---|
| 453 | 600 | |
|---|
| 454 | | - public Integer getCodeSuffix() { |
|---|
| 455 | | - return codeSuffix; |
|---|
| 456 | | - } |
|---|
| 601 | + private static final Map<Integer, List<LicenseStatus>> transitions = Utils.createMap( |
|---|
| 602 | + Action.REQUEST, Arrays.asList(LicenseStatus.CREATED, LicenseStatus.REQUESTED), |
|---|
| 603 | + Action.ACTIVATION,Arrays.asList(LicenseStatus.CREATED, LicenseStatus.REQUESTED, LicenseStatus.PRE_ACTIVE, LicenseStatus.EXPIRED), |
|---|
| 604 | + Action.SEND, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE), |
|---|
| 605 | + Action.DOWNLOAD, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE), |
|---|
| 606 | + Action.CANCEL, Arrays.asList(LicenseStatus.ACTIVE, LicenseStatus.PRE_ACTIVE, LicenseStatus.REQUESTED, LicenseStatus.EXPIRED), |
|---|
| 607 | + Action.DELETE, Arrays.asList(LicenseStatus.CANCELLED, LicenseStatus.CREATED, LicenseStatus.BLOCKED), |
|---|
| 608 | + Action.UNBLOCK, Arrays.asList(LicenseStatus.BLOCKED), |
|---|
| 609 | + Action.BLOCK, Arrays.asList(LicenseStatus.CANCELLED) |
|---|
| 610 | + ); |
|---|
| 457 | 611 | |
|---|
| 458 | | - public void setCodeSuffix(Integer codeSuffix) { |
|---|
| 459 | | - this.codeSuffix = codeSuffix; |
|---|
| 460 | | - } |
|---|
| 612 | + /** |
|---|
| 613 | + * isActionValid<p> |
|---|
| 614 | + * Check whether an action is valid given the current license status. |
|---|
| 615 | + * |
|---|
| 616 | + * @param action action constant from {@link Action} |
|---|
| 617 | + * @param currentStatus current license status |
|---|
| 618 | + * @return true if allowed |
|---|
| 619 | + */ |
|---|
| 620 | + public static boolean isActionValid(Integer action, LicenseStatus currentStatus) { |
|---|
| 621 | + List<LicenseStatus> validStatuses = transitions.get(action); |
|---|
| 622 | + LOG.info("Action {} is valid ? => {} current: {} OK? {}", action, validStatuses, currentStatus, |
|---|
| 623 | + validStatuses != null && validStatuses.contains(currentStatus)); |
|---|
| 624 | + return validStatuses != null && validStatuses.contains(currentStatus); |
|---|
| 625 | + } |
|---|
| 626 | + } |
|---|
| 461 | 627 | |
|---|
| 462 | | - public String getActivationCode() { |
|---|
| 463 | | - return activationCode; |
|---|
| 464 | | - } |
|---|
| 628 | + // ------------------------------------------------------------------ |
|---|
| 629 | + // Repository helpers (static queries) |
|---|
| 630 | + // ------------------------------------------------------------------ |
|---|
| 465 | 631 | |
|---|
| 466 | | - public void setActivationCode(String activationCode) { |
|---|
| 467 | | - this.activationCode = activationCode; |
|---|
| 468 | | - } |
|---|
| 632 | + /** |
|---|
| 633 | + * findValidLicenseByRequestData<p> |
|---|
| 634 | + * Return the first license in statuses RE/AC/PA for the given request data hash. |
|---|
| 635 | + * |
|---|
| 636 | + * @param requestData raw request data |
|---|
| 637 | + * @param em entity manager |
|---|
| 638 | + * @return matching license or null |
|---|
| 639 | + * @throws SeCurisServiceException |
|---|
| 640 | + */ |
|---|
| 641 | + public static License findValidLicenseByRequestData(String requestData, EntityManager em) throws SeCurisServiceException { |
|---|
| 642 | + TypedQuery<License> query = em.createNamedQuery("list-valid-licenses-by-req-data", License.class); |
|---|
| 643 | + query.setParameter("hash", BlockedRequest.generateHash(requestData)); |
|---|
| 644 | + try { |
|---|
| 645 | + List<License> list = query.getResultList(); |
|---|
| 646 | + if (list.size() == 0) return null; |
|---|
| 647 | + if (list.size() > 1) { |
|---|
| 648 | + LOG.error("There are more than 1 active or requested license for request data: {}\nHash: {}", |
|---|
| 649 | + requestData, BlockedRequest.generateHash(requestData)); |
|---|
| 650 | + } |
|---|
| 651 | + return list.get(0); |
|---|
| 652 | + } catch (NoResultException e) { |
|---|
| 653 | + return null; |
|---|
| 654 | + } |
|---|
| 655 | + } |
|---|
| 469 | 656 | |
|---|
| 470 | | - public boolean isMetadataObsolete() { |
|---|
| 471 | | - return metadataObsolete != null && metadataObsolete; |
|---|
| 472 | | - } |
|---|
| 657 | + /** |
|---|
| 658 | + * findLicenseByActivationCode<p> |
|---|
| 659 | + * Retrieve a license by its activation code. |
|---|
| 660 | + * |
|---|
| 661 | + * @param activationCode |
|---|
| 662 | + * @param entityManager |
|---|
| 663 | + * @return license |
|---|
| 664 | + * @throws SeCurisServiceException |
|---|
| 665 | + */ |
|---|
| 666 | + public static License findLicenseByActivationCode(String activationCode, EntityManager em) throws SeCurisServiceException { |
|---|
| 667 | + TypedQuery<License> query = em.createNamedQuery("license-by-activation-code", License.class); |
|---|
| 668 | + query.setParameter("activationCode", activationCode); |
|---|
| 669 | + try { |
|---|
| 670 | + return query.getSingleResult(); |
|---|
| 671 | + } catch (NoResultException e) { |
|---|
| 672 | + return null; |
|---|
| 673 | + } |
|---|
| 674 | + } |
|---|
| 473 | 675 | |
|---|
| 474 | | - public void setMetadataObsolete(Boolean obsolete) { |
|---|
| 475 | | - this.metadataObsolete = obsolete; |
|---|
| 476 | | - } |
|---|
| 676 | + /** |
|---|
| 677 | + * findActiveLicenseByRequestData<p> |
|---|
| 678 | + * Return the first AC/PA license for a given requestData. |
|---|
| 679 | + * |
|---|
| 680 | + * @param requestData |
|---|
| 681 | + * @param entityManager |
|---|
| 682 | + * @return license |
|---|
| 683 | + * @throws SeCurisServiceException |
|---|
| 684 | + */ |
|---|
| 685 | + public static License findActiveLicenseByRequestData(String requestData, EntityManager em) throws SeCurisServiceException { |
|---|
| 686 | + TypedQuery<License> query = em.createNamedQuery("list-active-licenses-by-req-data", License.class); |
|---|
| 687 | + query.setParameter("hash", BlockedRequest.generateHash(requestData)); |
|---|
| 688 | + try { |
|---|
| 689 | + List<License> list = query.getResultList(); |
|---|
| 690 | + if (list.size() == 0) return null; |
|---|
| 691 | + if (list.size() > 1) { |
|---|
| 692 | + LOG.error("There are more than 1 active license for request data: {}\nHash: {}", |
|---|
| 693 | + requestData, BlockedRequest.generateHash(requestData)); |
|---|
| 694 | + } |
|---|
| 695 | + return list.get(0); |
|---|
| 696 | + } catch (NoResultException e) { |
|---|
| 697 | + return null; |
|---|
| 698 | + } |
|---|
| 699 | + } |
|---|
| 477 | 700 | |
|---|
| 701 | + /** |
|---|
| 702 | + * findLicenseByCode<p> |
|---|
| 703 | + * Retrieve a license by human-readable code. |
|---|
| 704 | + * |
|---|
| 705 | + * @param code |
|---|
| 706 | + * @param entityManager |
|---|
| 707 | + * @return license |
|---|
| 708 | + * @throws SeCurisServiceException |
|---|
| 709 | + */ |
|---|
| 710 | + public static License findLicenseByCode(String code, EntityManager em) throws SeCurisServiceException { |
|---|
| 711 | + TypedQuery<License> query = em.createNamedQuery("license-by-code", License.class); |
|---|
| 712 | + query.setParameter("code", code); |
|---|
| 713 | + try { |
|---|
| 714 | + return query.getSingleResult(); |
|---|
| 715 | + } catch (NoResultException e) { |
|---|
| 716 | + return null; |
|---|
| 717 | + } |
|---|
| 718 | + } |
|---|
| 478 | 719 | } |
|---|
| 720 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 21 | 24 | import com.fasterxml.jackson.annotation.JsonProperty; |
|---|
| 22 | 25 | |
|---|
| 23 | 26 | /** |
|---|
| 24 | | - * Entity implementation class for Entity: license |
|---|
| 25 | | - * |
|---|
| 26 | | - */ |
|---|
| 27 | +* LicenseHistory |
|---|
| 28 | +* <p> |
|---|
| 29 | +* Audit trail entries for a given license (who/what/when). |
|---|
| 30 | +* |
|---|
| 31 | +* Mapping details: |
|---|
| 32 | +* - Table: license_history |
|---|
| 33 | +* - Many-to-one to License and User (ignored in JSON). |
|---|
| 34 | +* - NamedQuery: list-license-history by license id. |
|---|
| 35 | +* |
|---|
| 36 | +* @author JRA |
|---|
| 37 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 38 | +*/ |
|---|
| 27 | 39 | @JsonAutoDetect |
|---|
| 28 | 40 | @JsonInclude(Include.NON_NULL) |
|---|
| 29 | 41 | @Entity |
|---|
| 30 | 42 | @Table(name = "license_history") |
|---|
| 31 | 43 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| 32 | 44 | @NamedQueries({ |
|---|
| 33 | | - @NamedQuery(name = "list-license-history", query = "SELECT lh FROM LicenseHistory lh where lh.license.id = :licId") |
|---|
| 45 | + @NamedQuery(name = "list-license-history", |
|---|
| 46 | + query = "SELECT lh FROM LicenseHistory lh where lh.license.id = :licId") |
|---|
| 34 | 47 | }) |
|---|
| 35 | 48 | public class LicenseHistory implements Serializable { |
|---|
| 36 | 49 | |
|---|
| .. | .. |
|---|
| 57 | 70 | @JsonProperty("creation_timestamp") |
|---|
| 58 | 71 | private Date creationTimestamp; |
|---|
| 59 | 72 | |
|---|
| 60 | | - public int getId() { |
|---|
| 61 | | - return id; |
|---|
| 62 | | - } |
|---|
| 73 | + // ---------------- Getters & setters ---------------- |
|---|
| 63 | 74 | |
|---|
| 64 | | - public License getLicense() { |
|---|
| 65 | | - return license; |
|---|
| 66 | | - } |
|---|
| 75 | + /** |
|---|
| 76 | + * getId<p> |
|---|
| 77 | + * Return primary key. |
|---|
| 78 | + * |
|---|
| 79 | + * @return id |
|---|
| 80 | + */ |
|---|
| 81 | + public int getId() { return id; } |
|---|
| 67 | 82 | |
|---|
| 68 | | - public void setLicense(License license) { |
|---|
| 69 | | - this.license = license; |
|---|
| 70 | | - } |
|---|
| 83 | + /** |
|---|
| 84 | + * getLicense<p> |
|---|
| 85 | + * Return parent license (entity). |
|---|
| 86 | + * |
|---|
| 87 | + * @return license |
|---|
| 88 | + */ |
|---|
| 89 | + public License getLicense() { return license; } |
|---|
| 71 | 90 | |
|---|
| 72 | | - public User getUser() { |
|---|
| 73 | | - return user; |
|---|
| 74 | | - } |
|---|
| 91 | + /** |
|---|
| 92 | + * setLicense<p> |
|---|
| 93 | + * Set parent license (entity). |
|---|
| 94 | + * |
|---|
| 95 | + * @return license |
|---|
| 96 | + */ |
|---|
| 97 | + public void setLicense(License license) { this.license = license; } |
|---|
| 75 | 98 | |
|---|
| 99 | + /** |
|---|
| 100 | + * getUser<p> |
|---|
| 101 | + * Return actor user (entity). |
|---|
| 102 | + * |
|---|
| 103 | + * @return user |
|---|
| 104 | + */ |
|---|
| 105 | + public User getUser() { return user; } |
|---|
| 106 | + |
|---|
| 107 | + /** |
|---|
| 108 | + * getUsername<p> |
|---|
| 109 | + * Expose username for JSON. |
|---|
| 110 | + * |
|---|
| 111 | + * @return username |
|---|
| 112 | + */ |
|---|
| 76 | 113 | @JsonProperty("username") |
|---|
| 77 | | - public String getUsername() { |
|---|
| 78 | | - return user == null ? null : user.getUsername(); |
|---|
| 79 | | - } |
|---|
| 114 | + public String getUsername() { return user == null ? null : user.getUsername(); } |
|---|
| 80 | 115 | |
|---|
| 81 | | - public void setUser(User user) { |
|---|
| 82 | | - this.user = user; |
|---|
| 83 | | - } |
|---|
| 116 | + /** |
|---|
| 117 | + * setUser<p> |
|---|
| 118 | + * Set actor user (entity). |
|---|
| 119 | + * |
|---|
| 120 | + * @param user |
|---|
| 121 | + */ |
|---|
| 122 | + public void setUser(User user) { this.user = user; } |
|---|
| 84 | 123 | |
|---|
| 85 | | - public String getAction() { |
|---|
| 86 | | - return action; |
|---|
| 87 | | - } |
|---|
| 124 | + /** |
|---|
| 125 | + * getAction<p> |
|---|
| 126 | + * Return action key (e.g., "activate"). |
|---|
| 127 | + * |
|---|
| 128 | + * @return action |
|---|
| 129 | + */ |
|---|
| 130 | + public String getAction() { return action; } |
|---|
| 88 | 131 | |
|---|
| 89 | | - public void setAction(String action) { |
|---|
| 90 | | - this.action = action; |
|---|
| 91 | | - } |
|---|
| 132 | + /** |
|---|
| 133 | + * setAction<p> |
|---|
| 134 | + * Set action key. |
|---|
| 135 | + * |
|---|
| 136 | + * @param action |
|---|
| 137 | + */ |
|---|
| 138 | + public void setAction(String action) { this.action = action; } |
|---|
| 92 | 139 | |
|---|
| 93 | | - public String getComments() { |
|---|
| 94 | | - return comments; |
|---|
| 95 | | - } |
|---|
| 140 | + /** |
|---|
| 141 | + * getComments<p> |
|---|
| 142 | + * Return optional comments. |
|---|
| 143 | + * |
|---|
| 144 | + * @return comments |
|---|
| 145 | + */ |
|---|
| 146 | + public String getComments() { return comments; } |
|---|
| 96 | 147 | |
|---|
| 97 | | - public void setComments(String comments) { |
|---|
| 98 | | - this.comments = comments; |
|---|
| 99 | | - } |
|---|
| 148 | + /** |
|---|
| 149 | + * setComments<p> |
|---|
| 150 | + * Set optional comments. |
|---|
| 151 | + * |
|---|
| 152 | + * @param comments |
|---|
| 153 | + */ |
|---|
| 154 | + public void setComments(String comments) { this.comments = comments; } |
|---|
| 100 | 155 | |
|---|
| 101 | | - public void setId(int id) { |
|---|
| 102 | | - this.id = id; |
|---|
| 103 | | - } |
|---|
| 156 | + /** |
|---|
| 157 | + * setId<p> |
|---|
| 158 | + * Set primary key (normally framework-managed) |
|---|
| 159 | + * |
|---|
| 160 | + * @param id. |
|---|
| 161 | + */ |
|---|
| 162 | + public void setId(int id) { this.id = id; } |
|---|
| 104 | 163 | |
|---|
| 105 | | - public Date getCreationTimestamp() { |
|---|
| 106 | | - return creationTimestamp; |
|---|
| 107 | | - } |
|---|
| 164 | + /** |
|---|
| 165 | + * getCreationTimestamp<p> |
|---|
| 166 | + * Return timestamp. |
|---|
| 167 | + * |
|---|
| 168 | + * @return creationTimestamp |
|---|
| 169 | + */ |
|---|
| 170 | + public Date getCreationTimestamp() { return creationTimestamp; } |
|---|
| 108 | 171 | |
|---|
| 109 | | - public void setCreationTimestamp(Date creationTimestamp) { |
|---|
| 110 | | - this.creationTimestamp = creationTimestamp; |
|---|
| 111 | | - } |
|---|
| 172 | + /** |
|---|
| 173 | + * setCreationTimestamp<p> |
|---|
| 174 | + * Set timestamp. |
|---|
| 175 | + * |
|---|
| 176 | + * @param creationTimestamp |
|---|
| 177 | + */ |
|---|
| 178 | + public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; } |
|---|
| 112 | 179 | |
|---|
| 180 | + /** |
|---|
| 181 | + * Actions<p> |
|---|
| 182 | + * Catalog of action names. |
|---|
| 183 | + */ |
|---|
| 113 | 184 | public static class Actions { |
|---|
| 114 | 185 | public static final String CREATE = "creation"; |
|---|
| 115 | 186 | public static final String ADD_REQUEST = "request"; |
|---|
| .. | .. |
|---|
| 125 | 196 | public static final String DELETE = "delete"; |
|---|
| 126 | 197 | } |
|---|
| 127 | 198 | } |
|---|
| 199 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import net.curisit.securis.db.common.CodedEnum; |
|---|
| .. | .. |
|---|
| 6 | 9 | import com.fasterxml.jackson.annotation.JsonValue; |
|---|
| 7 | 10 | |
|---|
| 8 | 11 | /** |
|---|
| 9 | | - * Contains the possible license statuses. For further details: |
|---|
| 10 | | - * https://redmine.curistec.com/projects/securis/wiki/LicensesServerManagement |
|---|
| 11 | | - * |
|---|
| 12 | | - * @author rob |
|---|
| 13 | | - */ |
|---|
| 12 | +* LicenseStatus |
|---|
| 13 | +* <p> |
|---|
| 14 | +* Enumerates the possible license states. JSON code/value is the short code (CR, RE, AC, ...). |
|---|
| 15 | +* See: https://redmine.curistec.com/projects/securis/wiki/LicensesServerManagement |
|---|
| 16 | +* |
|---|
| 17 | +* @author JRA |
|---|
| 18 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 19 | +*/ |
|---|
| 14 | 20 | public enum LicenseStatus implements CodedEnum { |
|---|
| 15 | 21 | CREATED("CR"), REQUESTED("RE"), ACTIVE("AC"), PRE_ACTIVE("PA"), EXPIRED("EX"), CANCELLED("CA"), BLOCKED("BL"); |
|---|
| 16 | 22 | |
|---|
| 17 | 23 | private final String code; |
|---|
| 18 | 24 | |
|---|
| 19 | | - LicenseStatus(String code) { |
|---|
| 20 | | - this.code = code; |
|---|
| 21 | | - } |
|---|
| 25 | + /** |
|---|
| 26 | + * LicenseStatus<p> |
|---|
| 27 | + * Constructor |
|---|
| 28 | + * |
|---|
| 29 | + * @param code |
|---|
| 30 | + */ |
|---|
| 31 | + LicenseStatus(String code) { this.code = code; } |
|---|
| 22 | 32 | |
|---|
| 23 | | - @Override |
|---|
| 24 | | - public String getCode() { |
|---|
| 25 | | - return code; |
|---|
| 26 | | - } |
|---|
| 33 | + /** |
|---|
| 34 | + * getCode<p> |
|---|
| 35 | + * Return the short code used in DB/JSON. |
|---|
| 36 | + * |
|---|
| 37 | + * @return code |
|---|
| 38 | + */ |
|---|
| 39 | + @Override public String getCode() { return code; } |
|---|
| 27 | 40 | |
|---|
| 41 | + /** |
|---|
| 42 | + * valueFromCode<p> |
|---|
| 43 | + * Factory from code string (null on unknown). |
|---|
| 44 | + * |
|---|
| 45 | + * @param code |
|---|
| 46 | + */ |
|---|
| 28 | 47 | @JsonCreator |
|---|
| 29 | 48 | public static LicenseStatus valueFromCode(String code) { |
|---|
| 30 | 49 | for (LicenseStatus ps : LicenseStatus.values()) { |
|---|
| 31 | | - if (ps.code.equals(code)) { |
|---|
| 32 | | - return ps; |
|---|
| 33 | | - } |
|---|
| 50 | + if (ps.code.equals(code)) return ps; |
|---|
| 34 | 51 | } |
|---|
| 35 | 52 | return null; |
|---|
| 36 | 53 | } |
|---|
| 37 | 54 | |
|---|
| 55 | + /** |
|---|
| 56 | + * getName<p> |
|---|
| 57 | + * Expose the code as JSON value. |
|---|
| 58 | + * |
|---|
| 59 | + * @return name |
|---|
| 60 | + */ |
|---|
| 38 | 61 | @JsonValue |
|---|
| 39 | | - public String getName() { |
|---|
| 40 | | - return this.code; |
|---|
| 41 | | - } |
|---|
| 62 | + public String getName() { return this.code; } |
|---|
| 42 | 63 | |
|---|
| 64 | + /** |
|---|
| 65 | + * toString<p> |
|---|
| 66 | + * Get the string describing the current object |
|---|
| 67 | + * |
|---|
| 68 | + * @return object string |
|---|
| 69 | + */ |
|---|
| 43 | 70 | @Override |
|---|
| 44 | | - public String toString() { |
|---|
| 45 | | - return code; |
|---|
| 46 | | - } |
|---|
| 71 | + public String toString() { return code; } |
|---|
| 47 | 72 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 29 | 32 | import com.fasterxml.jackson.annotation.JsonProperty; |
|---|
| 30 | 33 | |
|---|
| 31 | 34 | /** |
|---|
| 32 | | - * Entity implementation class for Entity: license_type |
|---|
| 33 | | - * |
|---|
| 34 | | - */ |
|---|
| 35 | +* LicenseType |
|---|
| 36 | +* <p> |
|---|
| 37 | +* Describes a license category within an application. Owns metadata entries. |
|---|
| 38 | +* |
|---|
| 39 | +* Mapping details: |
|---|
| 40 | +* - Table: license_type |
|---|
| 41 | +* - Many-to-one to Application (lazy). |
|---|
| 42 | +* - One-to-many metadata with cascade PERSIST/REMOVE/REFRESH. |
|---|
| 43 | +* - Named queries for listing and filtering by application(s). |
|---|
| 44 | +* |
|---|
| 45 | +* @author JRA |
|---|
| 46 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 47 | +*/ |
|---|
| 35 | 48 | @JsonAutoDetect |
|---|
| 36 | 49 | @JsonInclude(Include.NON_NULL) |
|---|
| 37 | 50 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| 38 | 51 | @Entity |
|---|
| 39 | 52 | @Table(name = "license_type") |
|---|
| 40 | | -@NamedQueries({ @NamedQuery(name = "list-license_types", query = "SELECT lt FROM LicenseType lt"), |
|---|
| 41 | | - @NamedQuery(name = "list-license_types-by_apps-id", query = "SELECT lt FROM LicenseType lt where lt.application.id in :list_ids"), |
|---|
| 42 | | - @NamedQuery(name = "list-application-license_types", query = "SELECT lt FROM LicenseType lt where lt.application.id = :appId") }) |
|---|
| 53 | +@NamedQueries({ |
|---|
| 54 | + @NamedQuery(name = "list-license_types", query = "SELECT lt FROM LicenseType lt"), |
|---|
| 55 | + @NamedQuery(name = "list-license_types-by_apps-id", query = "SELECT lt FROM LicenseType lt where lt.application.id in :list_ids"), |
|---|
| 56 | + @NamedQuery(name = "list-application-license_types", query = "SELECT lt FROM LicenseType lt where lt.application.id = :appId") |
|---|
| 57 | +}) |
|---|
| 43 | 58 | public class LicenseType implements Serializable { |
|---|
| 44 | 59 | |
|---|
| 45 | | - @SuppressWarnings("unused") |
|---|
| 46 | | - private static final Logger LOG = LogManager.getLogger(LicenseType.class); |
|---|
| 47 | | - private static final long serialVersionUID = 1L; |
|---|
| 60 | + @SuppressWarnings("unused") |
|---|
| 61 | + private static final Logger LOG = LogManager.getLogger(LicenseType.class); |
|---|
| 62 | + private static final long serialVersionUID = 1L; |
|---|
| 48 | 63 | |
|---|
| 49 | | - @Id |
|---|
| 50 | | - @GeneratedValue |
|---|
| 51 | | - private Integer id; |
|---|
| 64 | + @Id |
|---|
| 65 | + @GeneratedValue |
|---|
| 66 | + private Integer id; |
|---|
| 52 | 67 | |
|---|
| 53 | | - private String code; |
|---|
| 54 | | - private String name; |
|---|
| 55 | | - private String description; |
|---|
| 68 | + private String code; |
|---|
| 69 | + private String name; |
|---|
| 70 | + private String description; |
|---|
| 56 | 71 | |
|---|
| 57 | | - @Column(name = "creation_timestamp") |
|---|
| 58 | | - @JsonProperty("creation_timestamp") |
|---|
| 59 | | - private Date creationTimestamp; |
|---|
| 72 | + @Column(name = "creation_timestamp") |
|---|
| 73 | + @JsonProperty("creation_timestamp") |
|---|
| 74 | + private Date creationTimestamp; |
|---|
| 60 | 75 | |
|---|
| 61 | | - @JsonIgnore |
|---|
| 62 | | - @ManyToOne(fetch = FetchType.LAZY) |
|---|
| 63 | | - @JoinColumn(name = "application_id") |
|---|
| 64 | | - private Application application; |
|---|
| 76 | + @JsonIgnore |
|---|
| 77 | + @ManyToOne(fetch = FetchType.LAZY) |
|---|
| 78 | + @JoinColumn(name = "application_id") |
|---|
| 79 | + private Application application; |
|---|
| 65 | 80 | |
|---|
| 66 | | - @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "licenseType") |
|---|
| 67 | | - @JsonManagedReference |
|---|
| 68 | | - private Set<LicenseTypeMetadata> metadata; |
|---|
| 81 | + @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "licenseType") |
|---|
| 82 | + @JsonManagedReference |
|---|
| 83 | + private Set<LicenseTypeMetadata> metadata; |
|---|
| 69 | 84 | |
|---|
| 70 | | - public Set<LicenseTypeMetadata> getMetadata() { |
|---|
| 71 | | - return metadata; |
|---|
| 72 | | - } |
|---|
| 85 | + // ---------------- Getters & setters ---------------- |
|---|
| 73 | 86 | |
|---|
| 74 | | - public void setMetadata(Set<LicenseTypeMetadata> metadata) { |
|---|
| 75 | | - this.metadata = metadata; |
|---|
| 76 | | - } |
|---|
| 87 | + /** |
|---|
| 88 | + * getMetadata<p> |
|---|
| 89 | + * Return associated metadata entries. |
|---|
| 90 | + * |
|---|
| 91 | + * @return metadata |
|---|
| 92 | + */ |
|---|
| 93 | + public Set<LicenseTypeMetadata> getMetadata() { return metadata; } |
|---|
| 77 | 94 | |
|---|
| 78 | | - public Integer getId() { |
|---|
| 79 | | - return id; |
|---|
| 80 | | - } |
|---|
| 95 | + /** |
|---|
| 96 | + * setMetadata<p> |
|---|
| 97 | + * Set associated metadata entries. |
|---|
| 98 | + * |
|---|
| 99 | + * @param metadata |
|---|
| 100 | + */ |
|---|
| 101 | + public void setMetadata(Set<LicenseTypeMetadata> metadata) { this.metadata = metadata; } |
|---|
| 81 | 102 | |
|---|
| 82 | | - public void setId(Integer id) { |
|---|
| 83 | | - this.id = id; |
|---|
| 84 | | - } |
|---|
| 103 | + /** |
|---|
| 104 | + * getId<p> |
|---|
| 105 | + * Return primary key. |
|---|
| 106 | + * |
|---|
| 107 | + * @return id |
|---|
| 108 | + */ |
|---|
| 109 | + public Integer getId() { return id; } |
|---|
| 85 | 110 | |
|---|
| 86 | | - public String getName() { |
|---|
| 87 | | - return name; |
|---|
| 88 | | - } |
|---|
| 111 | + /** |
|---|
| 112 | + * setId<p> |
|---|
| 113 | + * Set primary key. |
|---|
| 114 | + * |
|---|
| 115 | + * @param id |
|---|
| 116 | + */ |
|---|
| 117 | + public void setId(Integer id) { this.id = id; } |
|---|
| 89 | 118 | |
|---|
| 90 | | - public void setName(String name) { |
|---|
| 91 | | - this.name = name; |
|---|
| 92 | | - } |
|---|
| 119 | + /** |
|---|
| 120 | + * getName<p> |
|---|
| 121 | + * Return display name. |
|---|
| 122 | + * |
|---|
| 123 | + * @return name |
|---|
| 124 | + */ |
|---|
| 125 | + public String getName() { return name; } |
|---|
| 93 | 126 | |
|---|
| 94 | | - public String getDescription() { |
|---|
| 95 | | - return description; |
|---|
| 96 | | - } |
|---|
| 127 | + /** |
|---|
| 128 | + * setName<p> |
|---|
| 129 | + * Set display name. |
|---|
| 130 | + * |
|---|
| 131 | + * @param name |
|---|
| 132 | + */ |
|---|
| 133 | + public void setName(String name) { this.name = name; } |
|---|
| 97 | 134 | |
|---|
| 98 | | - public void setDescription(String description) { |
|---|
| 99 | | - this.description = description; |
|---|
| 100 | | - } |
|---|
| 135 | + /** |
|---|
| 136 | + * getDescription<p> |
|---|
| 137 | + * Return description. |
|---|
| 138 | + * |
|---|
| 139 | + * @return description |
|---|
| 140 | + */ |
|---|
| 141 | + public String getDescription() { return description; } |
|---|
| 101 | 142 | |
|---|
| 102 | | - public String getCode() { |
|---|
| 103 | | - return code; |
|---|
| 104 | | - } |
|---|
| 143 | + /** |
|---|
| 144 | + * setDescription<p> |
|---|
| 145 | + * Set description. |
|---|
| 146 | + * |
|---|
| 147 | + * @param description |
|---|
| 148 | + */ |
|---|
| 149 | + public void setDescription(String description) { this.description = description; } |
|---|
| 105 | 150 | |
|---|
| 106 | | - public void setCode(String code) { |
|---|
| 107 | | - this.code = code; |
|---|
| 108 | | - } |
|---|
| 151 | + /** |
|---|
| 152 | + * getCode<p> |
|---|
| 153 | + * Return short code. |
|---|
| 154 | + * |
|---|
| 155 | + * @return code |
|---|
| 156 | + */ |
|---|
| 157 | + public String getCode() { return code; } |
|---|
| 109 | 158 | |
|---|
| 110 | | - public Application getApplication() { |
|---|
| 111 | | - return application; |
|---|
| 112 | | - } |
|---|
| 159 | + /** |
|---|
| 160 | + * setCode<p> |
|---|
| 161 | + * Set short code. |
|---|
| 162 | + * |
|---|
| 163 | + * @param code |
|---|
| 164 | + */ |
|---|
| 165 | + public void setCode(String code) { this.code = code; } |
|---|
| 113 | 166 | |
|---|
| 114 | | - @JsonProperty("application_name") |
|---|
| 115 | | - public String getApplicationName() { |
|---|
| 116 | | - return application == null ? null : application.getName(); |
|---|
| 117 | | - } |
|---|
| 167 | + /** |
|---|
| 168 | + * getApplication<p> |
|---|
| 169 | + * Return owning application (entity). |
|---|
| 170 | + * |
|---|
| 171 | + * @return application |
|---|
| 172 | + */ |
|---|
| 173 | + public Application getApplication() { return application; } |
|---|
| 118 | 174 | |
|---|
| 119 | | - @JsonProperty("application_id") |
|---|
| 120 | | - public Integer getApplicationId() { |
|---|
| 121 | | - return application == null ? null : application.getId(); |
|---|
| 122 | | - } |
|---|
| 175 | + /** |
|---|
| 176 | + * getApplicationName<p> |
|---|
| 177 | + * Expose app name for JSON. |
|---|
| 178 | + * |
|---|
| 179 | + * @return appName |
|---|
| 180 | + */ |
|---|
| 181 | + @JsonProperty("application_name") |
|---|
| 182 | + public String getApplicationName() { return application == null ? null : application.getName(); } |
|---|
| 123 | 183 | |
|---|
| 124 | | - @JsonProperty("application_id") |
|---|
| 125 | | - public void setApplicationId(Integer appId) { |
|---|
| 126 | | - if (appId == null) { |
|---|
| 127 | | - application = null; |
|---|
| 128 | | - } else { |
|---|
| 129 | | - application = new Application(); |
|---|
| 130 | | - application.setId(appId); |
|---|
| 131 | | - } |
|---|
| 132 | | - } |
|---|
| 184 | + /** |
|---|
| 185 | + * getApplicationId<p> |
|---|
| 186 | + * Expose app id for JSON. |
|---|
| 187 | + * |
|---|
| 188 | + * @return appId |
|---|
| 189 | + */ |
|---|
| 190 | + @JsonProperty("application_id") |
|---|
| 191 | + public Integer getApplicationId() { return application == null ? null : application.getId(); } |
|---|
| 133 | 192 | |
|---|
| 134 | | - public void setApplication(Application application) { |
|---|
| 135 | | - this.application = application; |
|---|
| 136 | | - } |
|---|
| 193 | + /** |
|---|
| 194 | + * setApplicationId<p> |
|---|
| 195 | + * Setter by id for JSON binding (creates shallow Application). |
|---|
| 196 | + * |
|---|
| 197 | + * @param appId |
|---|
| 198 | + */ |
|---|
| 199 | + @JsonProperty("application_id") |
|---|
| 200 | + public void setApplicationId(Integer appId) { |
|---|
| 201 | + if (appId == null) { |
|---|
| 202 | + application = null; |
|---|
| 203 | + } else { |
|---|
| 204 | + application = new Application(); |
|---|
| 205 | + application.setId(appId); |
|---|
| 206 | + } |
|---|
| 207 | + } |
|---|
| 137 | 208 | |
|---|
| 138 | | - public Date getCreationTimestamp() { |
|---|
| 139 | | - return creationTimestamp; |
|---|
| 140 | | - } |
|---|
| 209 | + /** |
|---|
| 210 | + * setApplication<p> |
|---|
| 211 | + * Set owning application (entity). |
|---|
| 212 | + * |
|---|
| 213 | + * @param application |
|---|
| 214 | + */ |
|---|
| 215 | + public void setApplication(Application application) { this.application = application; } |
|---|
| 141 | 216 | |
|---|
| 142 | | - public void setCreationTimestamp(Date creationTimestamp) { |
|---|
| 143 | | - this.creationTimestamp = creationTimestamp; |
|---|
| 144 | | - } |
|---|
| 217 | + /** |
|---|
| 218 | + * getCreationTimestamp<p> |
|---|
| 219 | + * Return creation timestamp. |
|---|
| 220 | + * |
|---|
| 221 | + * @return creationTimestamp |
|---|
| 222 | + */ |
|---|
| 223 | + public Date getCreationTimestamp() { return creationTimestamp; } |
|---|
| 145 | 224 | |
|---|
| 146 | | - @Override |
|---|
| 147 | | - public boolean equals(Object obj) { |
|---|
| 148 | | - if (!(obj instanceof LicenseType)) |
|---|
| 149 | | - return false; |
|---|
| 150 | | - LicenseType other = (LicenseType) obj; |
|---|
| 151 | | - return id.equals(other.id); |
|---|
| 152 | | - } |
|---|
| 225 | + /** |
|---|
| 226 | + * setCreationTimestamp<p> |
|---|
| 227 | + * Set creation timestamp. |
|---|
| 228 | + * |
|---|
| 229 | + * @param creationTimestamp |
|---|
| 230 | + */ |
|---|
| 231 | + public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; } |
|---|
| 153 | 232 | |
|---|
| 154 | | - @Override |
|---|
| 155 | | - public int hashCode() { |
|---|
| 156 | | - return (id == null ? 0 : id.hashCode()); |
|---|
| 157 | | - } |
|---|
| 233 | + // ---------------- Object methods ---------------- |
|---|
| 158 | 234 | |
|---|
| 159 | | - @Override |
|---|
| 160 | | - public String toString() { |
|---|
| 161 | | - return String.format("LT: ID: %d, code: %s", id, code); |
|---|
| 162 | | - } |
|---|
| 235 | + /** |
|---|
| 236 | + * equals<p> |
|---|
| 237 | + * Compare the current object with the given object |
|---|
| 238 | + * |
|---|
| 239 | + * @param object |
|---|
| 240 | + * @return isEquals |
|---|
| 241 | + */ |
|---|
| 242 | + @Override |
|---|
| 243 | + public boolean equals(Object obj) { |
|---|
| 244 | + if (!(obj instanceof LicenseType)) return false; |
|---|
| 245 | + LicenseType other = (LicenseType) obj; |
|---|
| 246 | + return id != null && id.equals(other.id); |
|---|
| 247 | + } |
|---|
| 248 | + |
|---|
| 249 | + /** |
|---|
| 250 | + * hashCode<p> |
|---|
| 251 | + * Get the object hashCode |
|---|
| 252 | + * |
|---|
| 253 | + * @return hashCode |
|---|
| 254 | + */ |
|---|
| 255 | + @Override |
|---|
| 256 | + public int hashCode() { return (id == null ? 0 : id.hashCode()); } |
|---|
| 257 | + |
|---|
| 258 | + /** |
|---|
| 259 | + * toString<p> |
|---|
| 260 | + * Get the string describing the current object |
|---|
| 261 | + * |
|---|
| 262 | + * @return object string |
|---|
| 263 | + */ |
|---|
| 264 | + @Override |
|---|
| 265 | + public String toString() { return String.format("LT: ID: %d, code: %s", id, code); } |
|---|
| 163 | 266 | } |
|---|
| 267 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 21 | 24 | import net.curisit.securis.db.common.Metadata; |
|---|
| 22 | 25 | |
|---|
| 23 | 26 | /** |
|---|
| 24 | | - * Entity implementation class for Entity: licensetype_metadata |
|---|
| 25 | | - * |
|---|
| 26 | | - */ |
|---|
| 27 | +* LicenseTypeMetadata |
|---|
| 28 | +* <p> |
|---|
| 29 | +* Single metadata entry attached to a {@link LicenseType}. |
|---|
| 30 | +* Composite PK: (license_type_id, key). |
|---|
| 31 | +* |
|---|
| 32 | +* Mapping details: |
|---|
| 33 | +* - Table: licensetype_metadata |
|---|
| 34 | +* - @JsonBackReference to avoid recursion with LicenseType.metadata. |
|---|
| 35 | +* - NamedQuery: list-licensetype-metadata by license type id. |
|---|
| 36 | +* |
|---|
| 37 | +* @author JRA |
|---|
| 38 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 39 | +*/ |
|---|
| 27 | 40 | @JsonAutoDetect |
|---|
| 28 | 41 | @JsonInclude(Include.NON_NULL) |
|---|
| 29 | 42 | @Entity |
|---|
| 30 | 43 | @Table(name = "licensetype_metadata") |
|---|
| 31 | 44 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| 32 | | -@NamedQueries({ @NamedQuery(name = "list-licensetype-metadata", query = "SELECT a FROM LicenseTypeMetadata a where a.licenseType.id = :licenseTypeId") }) |
|---|
| 45 | +@NamedQueries({ |
|---|
| 46 | + @NamedQuery(name = "list-licensetype-metadata", |
|---|
| 47 | + query = "SELECT a FROM LicenseTypeMetadata a where a.licenseType.id = :licenseTypeId") |
|---|
| 48 | +}) |
|---|
| 33 | 49 | public class LicenseTypeMetadata implements Serializable, Metadata { |
|---|
| 34 | 50 | |
|---|
| 35 | | - private static final long serialVersionUID = 1L; |
|---|
| 51 | + private static final long serialVersionUID = 1L; |
|---|
| 36 | 52 | |
|---|
| 37 | | - @Id |
|---|
| 38 | | - @ManyToOne |
|---|
| 39 | | - @JsonBackReference |
|---|
| 40 | | - @JoinColumn(name = "license_type_id") |
|---|
| 41 | | - private LicenseType licenseType; |
|---|
| 53 | + /** PK part: owning license type. */ |
|---|
| 54 | + @Id |
|---|
| 55 | + @ManyToOne |
|---|
| 56 | + @JsonBackReference |
|---|
| 57 | + @JoinColumn(name = "license_type_id") |
|---|
| 58 | + private LicenseType licenseType; |
|---|
| 42 | 59 | |
|---|
| 43 | | - @Id |
|---|
| 44 | | - @Column(name = "\"key\"") |
|---|
| 45 | | - private String key; |
|---|
| 60 | + /** PK part: metadata key (quoted). */ |
|---|
| 61 | + @Id |
|---|
| 62 | + @Column(name = "\"key\"") |
|---|
| 63 | + private String key; |
|---|
| 46 | 64 | |
|---|
| 47 | | - private String value; |
|---|
| 65 | + /** Metadata value. */ |
|---|
| 66 | + private String value; |
|---|
| 48 | 67 | |
|---|
| 49 | | - private boolean mandatory; |
|---|
| 68 | + /** Whether this key is mandatory for the license type. */ |
|---|
| 69 | + private boolean mandatory; |
|---|
| 50 | 70 | |
|---|
| 51 | | - public LicenseType getLicenseType() { |
|---|
| 52 | | - return licenseType; |
|---|
| 53 | | - } |
|---|
| 71 | + // ---------------- Getters & setters ---------------- |
|---|
| 54 | 72 | |
|---|
| 55 | | - public void setLicenseType(LicenseType licenseType) { |
|---|
| 56 | | - this.licenseType = licenseType; |
|---|
| 57 | | - } |
|---|
| 73 | + /** |
|---|
| 74 | + * getLicenseType<p> |
|---|
| 75 | + * Return owning license type. |
|---|
| 76 | + * |
|---|
| 77 | + * @return licenseType |
|---|
| 78 | + */ |
|---|
| 79 | + public LicenseType getLicenseType() { return licenseType; } |
|---|
| 58 | 80 | |
|---|
| 59 | | - public String getValue() { |
|---|
| 60 | | - return value; |
|---|
| 61 | | - } |
|---|
| 81 | + /** |
|---|
| 82 | + * setLicenseType<p> |
|---|
| 83 | + * Set owning license type. |
|---|
| 84 | + * |
|---|
| 85 | + * @param licenseType |
|---|
| 86 | + */ |
|---|
| 87 | + public void setLicenseType(LicenseType licenseType) { this.licenseType = licenseType; } |
|---|
| 62 | 88 | |
|---|
| 63 | | - public void setValue(String value) { |
|---|
| 64 | | - this.value = value; |
|---|
| 65 | | - } |
|---|
| 89 | + /** |
|---|
| 90 | + * getValue<p> |
|---|
| 91 | + * Return metadata value. |
|---|
| 92 | + * |
|---|
| 93 | + * @return value |
|---|
| 94 | + */ |
|---|
| 95 | + public String getValue() { return value; } |
|---|
| 66 | 96 | |
|---|
| 67 | | - public String getKey() { |
|---|
| 68 | | - return key; |
|---|
| 69 | | - } |
|---|
| 97 | + /** |
|---|
| 98 | + * setValue<p> |
|---|
| 99 | + * Set metadata value. |
|---|
| 100 | + * |
|---|
| 101 | + * @param value |
|---|
| 102 | + */ |
|---|
| 103 | + public void setValue(String value) { this.value = value; } |
|---|
| 70 | 104 | |
|---|
| 71 | | - public void setKey(String key) { |
|---|
| 72 | | - this.key = key; |
|---|
| 73 | | - } |
|---|
| 105 | + /** |
|---|
| 106 | + * getKey<p> |
|---|
| 107 | + * Return metadata key (PK part). |
|---|
| 108 | + * |
|---|
| 109 | + * @return key |
|---|
| 110 | + */ |
|---|
| 111 | + public String getKey() { return key; } |
|---|
| 74 | 112 | |
|---|
| 75 | | - public boolean isMandatory() { |
|---|
| 76 | | - return mandatory; |
|---|
| 77 | | - } |
|---|
| 113 | + /** |
|---|
| 114 | + * setKey<p> |
|---|
| 115 | + * Set metadata key (PK part). |
|---|
| 116 | + * |
|---|
| 117 | + * @param key |
|---|
| 118 | + */ |
|---|
| 119 | + public void setKey(String key) { this.key = key; } |
|---|
| 78 | 120 | |
|---|
| 79 | | - public void setMandatory(boolean mandatory) { |
|---|
| 80 | | - this.mandatory = mandatory; |
|---|
| 81 | | - } |
|---|
| 121 | + /** |
|---|
| 122 | + * isMandatory<p> |
|---|
| 123 | + * Return whether this entry is required. |
|---|
| 124 | + * |
|---|
| 125 | + * @return isMandatory |
|---|
| 126 | + */ |
|---|
| 127 | + public boolean isMandatory() { return mandatory; } |
|---|
| 82 | 128 | |
|---|
| 83 | | - @Override |
|---|
| 84 | | - public boolean equals(Object obj) { |
|---|
| 85 | | - if (!(obj instanceof LicenseTypeMetadata)) |
|---|
| 86 | | - return false; |
|---|
| 87 | | - LicenseTypeMetadata other = (LicenseTypeMetadata) obj; |
|---|
| 88 | | - return Objects.equals(key, other.key) && Objects.equals(licenseType, other.licenseType); |
|---|
| 89 | | - } |
|---|
| 129 | + /** |
|---|
| 130 | + * setMandatory<p> |
|---|
| 131 | + * Set whether this entry is required. |
|---|
| 132 | + * |
|---|
| 133 | + * @param mandatory |
|---|
| 134 | + */ |
|---|
| 135 | + public void setMandatory(boolean mandatory) { this.mandatory = mandatory; } |
|---|
| 90 | 136 | |
|---|
| 91 | | - @Override |
|---|
| 92 | | - public int hashCode() { |
|---|
| 93 | | - return Objects.hash(key, licenseType); |
|---|
| 94 | | - } |
|---|
| 137 | + // ---------------- Object methods ---------------- |
|---|
| 95 | 138 | |
|---|
| 96 | | - @Override |
|---|
| 97 | | - public String toString() { |
|---|
| 98 | | - return String.format("LTMD (%s: %s)", key, value); |
|---|
| 99 | | - } |
|---|
| 139 | + /** |
|---|
| 140 | + * equals<p> |
|---|
| 141 | + * Compare the current object with the given object |
|---|
| 142 | + * |
|---|
| 143 | + * @param object |
|---|
| 144 | + * @return isEquals |
|---|
| 145 | + */ |
|---|
| 146 | + @Override |
|---|
| 147 | + public boolean equals(Object obj) { |
|---|
| 148 | + if (!(obj instanceof LicenseTypeMetadata)) return false; |
|---|
| 149 | + LicenseTypeMetadata other = (LicenseTypeMetadata) obj; |
|---|
| 150 | + return Objects.equals(key, other.key) && Objects.equals(licenseType, other.licenseType); |
|---|
| 151 | + } |
|---|
| 152 | + |
|---|
| 153 | + /** |
|---|
| 154 | + * hashCode<p> |
|---|
| 155 | + * Get the object hashCode |
|---|
| 156 | + * |
|---|
| 157 | + * @return hashCode |
|---|
| 158 | + */ |
|---|
| 159 | + @Override |
|---|
| 160 | + public int hashCode() { return Objects.hash(key, licenseType); } |
|---|
| 161 | + |
|---|
| 162 | + /** |
|---|
| 163 | + * toString<p> |
|---|
| 164 | + * Get the string describing the current object |
|---|
| 165 | + * |
|---|
| 166 | + * @return object string |
|---|
| 167 | + */ |
|---|
| 168 | + @Override |
|---|
| 169 | + public String toString() { return String.format("LTMD (%s: %s)", key, value); } |
|---|
| 100 | 170 | } |
|---|
| 171 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 32 | 35 | import com.fasterxml.jackson.annotation.JsonProperty; |
|---|
| 33 | 36 | |
|---|
| 34 | 37 | /** |
|---|
| 35 | | - * Entity implementation class for Entity: organization |
|---|
| 36 | | - * |
|---|
| 37 | | - */ |
|---|
| 38 | +* Organization |
|---|
| 39 | +* <p> |
|---|
| 40 | +* Represents a customer/tenant organization. Manages parent/children hierarchy |
|---|
| 41 | +* and user membership. |
|---|
| 42 | +* |
|---|
| 43 | +* Mapping details: |
|---|
| 44 | +* - Table: organization |
|---|
| 45 | +* - ManyToMany users via user_organization (ignored in default JSON). |
|---|
| 46 | +* - Self-referencing parent/children relation. |
|---|
| 47 | +* - Named queries for listing, filtering by ids, and children discovery. |
|---|
| 48 | +* |
|---|
| 49 | +* @author JRA |
|---|
| 50 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 51 | +*/ |
|---|
| 38 | 52 | @JsonAutoDetect |
|---|
| 39 | 53 | @JsonInclude(Include.NON_NULL) |
|---|
| 40 | 54 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| 41 | 55 | @Entity |
|---|
| 42 | 56 | @Table(name = "organization") |
|---|
| 43 | | -@NamedQueries({ @NamedQuery(name = "list-organizations", query = "SELECT o FROM Organization o"), |
|---|
| 44 | | - @NamedQuery(name = "list-organizations-by-ids", query = "SELECT o FROM Organization o where id in :list_ids"), |
|---|
| 45 | | - @NamedQuery(name = "find-children-org", query = "SELECT o FROM Organization o where o.parentOrganization = :parentOrganization") }) |
|---|
| 57 | +@NamedQueries({ |
|---|
| 58 | + @NamedQuery(name = "list-organizations", query = "SELECT o FROM Organization o"), |
|---|
| 59 | + @NamedQuery(name = "list-organizations-by-ids", query = "SELECT o FROM Organization o where id in :list_ids"), |
|---|
| 60 | + @NamedQuery(name = "find-children-org", query = "SELECT o FROM Organization o where o.parentOrganization = :parentOrganization") |
|---|
| 61 | +}) |
|---|
| 46 | 62 | public class Organization implements Serializable { |
|---|
| 47 | 63 | |
|---|
| 48 | | - @SuppressWarnings("unused") |
|---|
| 49 | | - private static final Logger LOG = LogManager.getLogger(Organization.class); |
|---|
| 64 | + @SuppressWarnings("unused") |
|---|
| 65 | + private static final Logger LOG = LogManager.getLogger(Organization.class); |
|---|
| 50 | 66 | |
|---|
| 51 | | - private static final long serialVersionUID = 1L; |
|---|
| 67 | + private static final long serialVersionUID = 1L; |
|---|
| 52 | 68 | |
|---|
| 53 | | - @Id |
|---|
| 54 | | - @GeneratedValue |
|---|
| 55 | | - private Integer id; |
|---|
| 69 | + @Id |
|---|
| 70 | + @GeneratedValue |
|---|
| 71 | + private Integer id; |
|---|
| 56 | 72 | |
|---|
| 57 | | - private String code; |
|---|
| 58 | | - private String name; |
|---|
| 59 | | - private String description; |
|---|
| 73 | + private String code; |
|---|
| 74 | + private String name; |
|---|
| 75 | + private String description; |
|---|
| 60 | 76 | |
|---|
| 61 | | - @Column(name = "creation_timestamp") |
|---|
| 62 | | - @JsonProperty("creation_timestamp") |
|---|
| 63 | | - private Date creationTimestamp; |
|---|
| 77 | + @Column(name = "creation_timestamp") |
|---|
| 78 | + @JsonProperty("creation_timestamp") |
|---|
| 79 | + private Date creationTimestamp; |
|---|
| 64 | 80 | |
|---|
| 65 | | - @JsonIgnore |
|---|
| 66 | | - // We don't include the users to limit the size of each row a the listing |
|---|
| 67 | | - @ManyToMany(cascade = CascadeType.REMOVE) |
|---|
| 68 | | - @JoinTable(name = "user_organization", // |
|---|
| 69 | | - joinColumns = { @JoinColumn(name = "organization_id", referencedColumnName = "id") }, // |
|---|
| 70 | | - inverseJoinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") }) |
|---|
| 71 | | - private Set<User> users; |
|---|
| 81 | + @JsonIgnore |
|---|
| 82 | + @ManyToMany(cascade = CascadeType.REMOVE) |
|---|
| 83 | + @JoinTable(name = "user_organization", |
|---|
| 84 | + joinColumns = { @JoinColumn(name = "organization_id", referencedColumnName = "id") }, |
|---|
| 85 | + inverseJoinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") }) |
|---|
| 86 | + private Set<User> users; |
|---|
| 72 | 87 | |
|---|
| 73 | | - @JsonIgnore |
|---|
| 74 | | - // We don't include the users to limit the size of each row a the listing |
|---|
| 75 | | - @ManyToOne |
|---|
| 76 | | - @JoinColumn(name = "org_parent_id") |
|---|
| 77 | | - private Organization parentOrganization; |
|---|
| 88 | + @JsonIgnore |
|---|
| 89 | + @ManyToOne |
|---|
| 90 | + @JoinColumn(name = "org_parent_id") |
|---|
| 91 | + private Organization parentOrganization; |
|---|
| 78 | 92 | |
|---|
| 79 | | - @JsonIgnore |
|---|
| 80 | | - // We don't include the users to limit the size of each row a the listing |
|---|
| 81 | | - @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentOrganization") |
|---|
| 82 | | - private Set<Organization> childOrganizations; |
|---|
| 93 | + @JsonIgnore |
|---|
| 94 | + @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentOrganization") |
|---|
| 95 | + private Set<Organization> childOrganizations; |
|---|
| 83 | 96 | |
|---|
| 84 | | - public Integer getId() { |
|---|
| 85 | | - return id; |
|---|
| 86 | | - } |
|---|
| 97 | + // ---------------- Getters & setters ---------------- |
|---|
| 87 | 98 | |
|---|
| 88 | | - public void setId(Integer id) { |
|---|
| 89 | | - this.id = id; |
|---|
| 90 | | - } |
|---|
| 99 | + /** |
|---|
| 100 | + * getId<p> |
|---|
| 101 | + * Return primary key. |
|---|
| 102 | + * |
|---|
| 103 | + * @return id |
|---|
| 104 | + */ |
|---|
| 105 | + public Integer getId() { return id; } |
|---|
| 91 | 106 | |
|---|
| 92 | | - public String getName() { |
|---|
| 93 | | - return name; |
|---|
| 94 | | - } |
|---|
| 107 | + /** |
|---|
| 108 | + * setId<p> |
|---|
| 109 | + * Set primary key. |
|---|
| 110 | + * |
|---|
| 111 | + * @param id |
|---|
| 112 | + */ |
|---|
| 113 | + public void setId(Integer id) { this.id = id; } |
|---|
| 95 | 114 | |
|---|
| 96 | | - public void setName(String name) { |
|---|
| 97 | | - this.name = name; |
|---|
| 98 | | - } |
|---|
| 115 | + /** |
|---|
| 116 | + * getName<p> |
|---|
| 117 | + * Return display name. |
|---|
| 118 | + * |
|---|
| 119 | + * @return name |
|---|
| 120 | + */ |
|---|
| 121 | + public String getName() { return name; } |
|---|
| 99 | 122 | |
|---|
| 100 | | - public String getDescription() { |
|---|
| 101 | | - return description; |
|---|
| 102 | | - } |
|---|
| 123 | + /** |
|---|
| 124 | + * setName<p> |
|---|
| 125 | + * Set display name. |
|---|
| 126 | + * |
|---|
| 127 | + * @param name |
|---|
| 128 | + */ |
|---|
| 129 | + public void setName(String name) { this.name = name; } |
|---|
| 103 | 130 | |
|---|
| 104 | | - public void setDescription(String description) { |
|---|
| 105 | | - this.description = description; |
|---|
| 106 | | - } |
|---|
| 131 | + /** |
|---|
| 132 | + * getDescription<p> |
|---|
| 133 | + * Return optional description. |
|---|
| 134 | + * |
|---|
| 135 | + * @return description |
|---|
| 136 | + */ |
|---|
| 137 | + public String getDescription() { return description; } |
|---|
| 107 | 138 | |
|---|
| 108 | | - public String getCode() { |
|---|
| 109 | | - return code; |
|---|
| 110 | | - } |
|---|
| 139 | + /** |
|---|
| 140 | + * setDescription<p> |
|---|
| 141 | + * Set optional description. |
|---|
| 142 | + * |
|---|
| 143 | + * @param description |
|---|
| 144 | + */ |
|---|
| 145 | + public void setDescription(String description) { this.description = description; } |
|---|
| 111 | 146 | |
|---|
| 112 | | - public void setCode(String code) { |
|---|
| 113 | | - this.code = code; |
|---|
| 114 | | - } |
|---|
| 147 | + /** |
|---|
| 148 | + * getCode<p> |
|---|
| 149 | + * Return short code. |
|---|
| 150 | + * |
|---|
| 151 | + * @return code |
|---|
| 152 | + */ |
|---|
| 153 | + public String getCode() { return code; } |
|---|
| 115 | 154 | |
|---|
| 116 | | - public Date getCreationTimestamp() { |
|---|
| 117 | | - return creationTimestamp; |
|---|
| 118 | | - } |
|---|
| 155 | + /** |
|---|
| 156 | + * setCode<p> |
|---|
| 157 | + * Set short code. |
|---|
| 158 | + * |
|---|
| 159 | + * @param code |
|---|
| 160 | + */ |
|---|
| 161 | + public void setCode(String code) { this.code = code; } |
|---|
| 119 | 162 | |
|---|
| 120 | | - public void setCreationTimestamp(Date creationTimestamp) { |
|---|
| 121 | | - this.creationTimestamp = creationTimestamp; |
|---|
| 122 | | - } |
|---|
| 163 | + /** |
|---|
| 164 | + * getCreationTimestamp<p> |
|---|
| 165 | + * Return creation timestamp. |
|---|
| 166 | + * |
|---|
| 167 | + * @return creationTimeStamp |
|---|
| 168 | + */ |
|---|
| 169 | + public Date getCreationTimestamp() { return creationTimestamp; } |
|---|
| 123 | 170 | |
|---|
| 124 | | - public Set<User> getUsers() { |
|---|
| 125 | | - return users; |
|---|
| 126 | | - } |
|---|
| 171 | + /** |
|---|
| 172 | + * setCreationTimestamp<p> |
|---|
| 173 | + * Set creation timestamp. |
|---|
| 174 | + * |
|---|
| 175 | + * @param creationTimestamp |
|---|
| 176 | + */ |
|---|
| 177 | + public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; } |
|---|
| 127 | 178 | |
|---|
| 128 | | - public void setUsers(Set<User> users) { |
|---|
| 129 | | - this.users = users; |
|---|
| 130 | | - } |
|---|
| 179 | + /** |
|---|
| 180 | + * getUsers<p> |
|---|
| 181 | + * Return member users (entity set). |
|---|
| 182 | + * |
|---|
| 183 | + * @return users |
|---|
| 184 | + */ |
|---|
| 185 | + public Set<User> getUsers() { return users; } |
|---|
| 131 | 186 | |
|---|
| 132 | | - public Organization getParentOrganization() { |
|---|
| 133 | | - return parentOrganization; |
|---|
| 134 | | - } |
|---|
| 187 | + /** |
|---|
| 188 | + * setUsers<p> |
|---|
| 189 | + * Set member users. |
|---|
| 190 | + * |
|---|
| 191 | + * @param users |
|---|
| 192 | + */ |
|---|
| 193 | + public void setUsers(Set<User> users) { this.users = users; } |
|---|
| 135 | 194 | |
|---|
| 136 | | - public void setParentOrganization(Organization parentOrganization) { |
|---|
| 137 | | - this.parentOrganization = parentOrganization; |
|---|
| 138 | | - } |
|---|
| 195 | + /** |
|---|
| 196 | + * getParentOrganization<p> |
|---|
| 197 | + * Return parent org (entity). |
|---|
| 198 | + * |
|---|
| 199 | + * @return parentOrganization |
|---|
| 200 | + */ |
|---|
| 201 | + public Organization getParentOrganization() { return parentOrganization; } |
|---|
| 139 | 202 | |
|---|
| 140 | | - // Roberto: Following methods are necessary to include in the REST list |
|---|
| 141 | | - // response |
|---|
| 142 | | - // information about the referenced entities. |
|---|
| 143 | | - @JsonProperty("org_parent_id") |
|---|
| 144 | | - public void setParentOrgId(Integer orgId) { |
|---|
| 145 | | - if (orgId != null) { |
|---|
| 146 | | - parentOrganization = new Organization(); |
|---|
| 147 | | - parentOrganization.setId(orgId); |
|---|
| 148 | | - } else { |
|---|
| 149 | | - parentOrganization = null; |
|---|
| 150 | | - } |
|---|
| 151 | | - } |
|---|
| 203 | + /** |
|---|
| 204 | + * setParentOrganization<p> |
|---|
| 205 | + * Set parent org (entity). |
|---|
| 206 | + * |
|---|
| 207 | + * @param parentOrganization |
|---|
| 208 | + */ |
|---|
| 209 | + public void setParentOrganization(Organization parentOrganization) { this.parentOrganization = parentOrganization; } |
|---|
| 152 | 210 | |
|---|
| 153 | | - @JsonProperty("org_parent_id") |
|---|
| 154 | | - public Integer getParentOrgId() { |
|---|
| 155 | | - return parentOrganization == null ? null : parentOrganization.getId(); |
|---|
| 156 | | - } |
|---|
| 211 | + // JSON helpers for parent organization |
|---|
| 157 | 212 | |
|---|
| 158 | | - @JsonProperty("org_parent_name") |
|---|
| 159 | | - public String getParentOrgName() { |
|---|
| 160 | | - return parentOrganization == null ? null : parentOrganization.getName(); |
|---|
| 161 | | - } |
|---|
| 213 | + /** |
|---|
| 214 | + * setParentOrgId<p> |
|---|
| 215 | + * Setter by id (creates shallow Organization). |
|---|
| 216 | + * |
|---|
| 217 | + * @param orgId |
|---|
| 218 | + */ |
|---|
| 219 | + @JsonProperty("org_parent_id") |
|---|
| 220 | + public void setParentOrgId(Integer orgId) { |
|---|
| 221 | + if (orgId != null) { |
|---|
| 222 | + parentOrganization = new Organization(); |
|---|
| 223 | + parentOrganization.setId(orgId); |
|---|
| 224 | + } else { |
|---|
| 225 | + parentOrganization = null; |
|---|
| 226 | + } |
|---|
| 227 | + } |
|---|
| 162 | 228 | |
|---|
| 163 | | - @JsonProperty("users_ids") |
|---|
| 164 | | - public void setUsersIds(List<String> usersIds) { |
|---|
| 165 | | - users = new HashSet<>(); |
|---|
| 166 | | - if (usersIds != null) { |
|---|
| 167 | | - for (String userid : usersIds) { |
|---|
| 168 | | - User u = new User(); |
|---|
| 169 | | - u.setUsername(userid); |
|---|
| 170 | | - users.add(u); |
|---|
| 171 | | - } |
|---|
| 172 | | - } |
|---|
| 173 | | - } |
|---|
| 229 | + /** |
|---|
| 230 | + * getParentOrgId<p> |
|---|
| 231 | + * Expose parent org id. |
|---|
| 232 | + * |
|---|
| 233 | + * @return parentOrgId |
|---|
| 234 | + */ |
|---|
| 235 | + @JsonProperty("org_parent_id") |
|---|
| 236 | + public Integer getParentOrgId() { return parentOrganization == null ? null : parentOrganization.getId(); } |
|---|
| 174 | 237 | |
|---|
| 175 | | - @JsonProperty("users_ids") |
|---|
| 176 | | - public Set<String> getUsersIds() { |
|---|
| 177 | | - if (users == null) { |
|---|
| 178 | | - return null; |
|---|
| 179 | | - } |
|---|
| 180 | | - Set<String> ids = new HashSet<>(); |
|---|
| 181 | | - for (User user : users) { |
|---|
| 182 | | - ids.add(user.getUsername()); |
|---|
| 183 | | - } |
|---|
| 184 | | - return ids; |
|---|
| 185 | | - } |
|---|
| 238 | + /** |
|---|
| 239 | + * getParentOrgName<p> |
|---|
| 240 | + * Expose parent org name. |
|---|
| 241 | + * |
|---|
| 242 | + * @return parentOrgName |
|---|
| 243 | + */ |
|---|
| 244 | + @JsonProperty("org_parent_name") |
|---|
| 245 | + public String getParentOrgName() { return parentOrganization == null ? null : parentOrganization.getName(); } |
|---|
| 186 | 246 | |
|---|
| 187 | | - public Set<Organization> getChildOrganizations() { |
|---|
| 188 | | - return childOrganizations; |
|---|
| 189 | | - } |
|---|
| 247 | + // JSON helpers for users |
|---|
| 190 | 248 | |
|---|
| 191 | | - public void setChildOrganizations(Set<Organization> childOrganizations) { |
|---|
| 192 | | - this.childOrganizations = childOrganizations; |
|---|
| 193 | | - } |
|---|
| 249 | + /** |
|---|
| 250 | + * setUsersIds<p> |
|---|
| 251 | + * Replace users set from a list of usernames. |
|---|
| 252 | + * |
|---|
| 253 | + * @param userId list |
|---|
| 254 | + */ |
|---|
| 255 | + @JsonProperty("users_ids") |
|---|
| 256 | + public void setUsersIds(List<String> usersIds) { |
|---|
| 257 | + users = new HashSet<>(); |
|---|
| 258 | + if (usersIds != null) { |
|---|
| 259 | + for (String userid : usersIds) { |
|---|
| 260 | + User u = new User(); |
|---|
| 261 | + u.setUsername(userid); |
|---|
| 262 | + users.add(u); |
|---|
| 263 | + } |
|---|
| 264 | + } |
|---|
| 265 | + } |
|---|
| 194 | 266 | |
|---|
| 267 | + /** |
|---|
| 268 | + * getUsersIds<p> |
|---|
| 269 | + * Expose member usernames. |
|---|
| 270 | + * |
|---|
| 271 | + * @return userId list |
|---|
| 272 | + */ |
|---|
| 273 | + @JsonProperty("users_ids") |
|---|
| 274 | + public Set<String> getUsersIds() { |
|---|
| 275 | + if (users == null) return null; |
|---|
| 276 | + Set<String> ids = new HashSet<>(); |
|---|
| 277 | + for (User user : users) { ids.add(user.getUsername()); } |
|---|
| 278 | + return ids; |
|---|
| 279 | + } |
|---|
| 280 | + |
|---|
| 281 | + /** getChildOrganizations<p>Return children (entity set). */ |
|---|
| 282 | + public Set<Organization> getChildOrganizations() { return childOrganizations; } |
|---|
| 283 | + |
|---|
| 284 | + /** setChildOrganizations<p>Set children (entity set). */ |
|---|
| 285 | + public void setChildOrganizations(Set<Organization> childOrganizations) { this.childOrganizations = childOrganizations; } |
|---|
| 195 | 286 | } |
|---|
| 287 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 32 | 35 | import net.curisit.integrity.commons.Utils; |
|---|
| 33 | 36 | |
|---|
| 34 | 37 | /** |
|---|
| 35 | | - * Entity implementation class for Entity: pack |
|---|
| 36 | | - * |
|---|
| 37 | | - */ |
|---|
| 38 | +* Pack |
|---|
| 39 | +* <p> |
|---|
| 40 | +* Group/bundle of licenses for an organization and application (via LicenseType). |
|---|
| 41 | +* Tracks capacity, availability, status, and validity windows. |
|---|
| 42 | +* |
|---|
| 43 | +* Mapping details: |
|---|
| 44 | +* - Table: pack |
|---|
| 45 | +* - ManyToOne to Organization, LicenseType, User (createdBy) |
|---|
| 46 | +* - OneToMany licenses (lazy) |
|---|
| 47 | +* - Custom type: net.curisit.securis.db.common.PackStatusType |
|---|
| 48 | +* - Named queries for listing and filtering by org/app. |
|---|
| 49 | +* |
|---|
| 50 | +* @author JRA |
|---|
| 51 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 52 | +*/ |
|---|
| 38 | 53 | @JsonAutoDetect |
|---|
| 39 | 54 | @JsonInclude(Include.NON_NULL) |
|---|
| 40 | 55 | @Entity |
|---|
| 41 | 56 | @Table(name = "pack") |
|---|
| 42 | 57 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| 43 | | -@NamedQueries({ @NamedQuery(name = "list-packs", query = "SELECT pa FROM Pack pa"), // |
|---|
| 44 | | - @NamedQuery(name = "pack-by-code", query = "SELECT pa FROM Pack pa where pa.code = :code"), // |
|---|
| 45 | | - @NamedQuery(name = "list-packs-by-lic-type", query = "SELECT pa FROM Pack pa where pa.licenseType.id = :lt_id"), // |
|---|
| 46 | | - @NamedQuery(name = "list-packs-by-orgs-apps", query = "SELECT pa FROM Pack pa where pa.organization.id in :list_ids_org and pa.licenseType.application.id in :list_ids_app "), // |
|---|
| 47 | | - @NamedQuery(name = "list-packs-by-apps", query = "SELECT pa FROM Pack pa where pa.licenseType.application.id in :list_ids_app ") }) |
|---|
| 58 | +@NamedQueries({ |
|---|
| 59 | + @NamedQuery(name = "list-packs", query = "SELECT pa FROM Pack pa"), |
|---|
| 60 | + @NamedQuery(name = "pack-by-code", query = "SELECT pa FROM Pack pa where pa.code = :code"), |
|---|
| 61 | + @NamedQuery(name = "list-packs-by-lic-type", query = "SELECT pa FROM Pack pa where pa.licenseType.id = :lt_id"), |
|---|
| 62 | + @NamedQuery(name = "list-packs-by-orgs-apps", query = "SELECT pa FROM Pack pa where pa.organization.id in :list_ids_org and pa.licenseType.application.id in :list_ids_app "), |
|---|
| 63 | + @NamedQuery(name = "list-packs-by-apps", query = "SELECT pa FROM Pack pa where pa.licenseType.application.id in :list_ids_app ") |
|---|
| 64 | +}) |
|---|
| 48 | 65 | public class Pack implements Serializable { |
|---|
| 49 | 66 | |
|---|
| 50 | | - private static final long serialVersionUID = 1L; |
|---|
| 67 | + private static final long serialVersionUID = 1L; |
|---|
| 51 | 68 | |
|---|
| 52 | | - @Id |
|---|
| 53 | | - @GeneratedValue |
|---|
| 54 | | - private Integer id; |
|---|
| 69 | + @Id |
|---|
| 70 | + @GeneratedValue |
|---|
| 71 | + private Integer id; |
|---|
| 55 | 72 | |
|---|
| 56 | | - private String code; |
|---|
| 73 | + private String code; |
|---|
| 74 | + private String comments; |
|---|
| 75 | + private Boolean frozen; |
|---|
| 57 | 76 | |
|---|
| 58 | | - private String comments; |
|---|
| 77 | + @Column(name = "creation_timestamp") |
|---|
| 78 | + @JsonProperty("creation_timestamp") |
|---|
| 79 | + private Date creationTimestamp; |
|---|
| 59 | 80 | |
|---|
| 60 | | - private Boolean frozen; |
|---|
| 81 | + @JsonIgnore |
|---|
| 82 | + @ManyToOne |
|---|
| 83 | + @JoinColumn(name = "organization_id") |
|---|
| 84 | + private Organization organization; |
|---|
| 61 | 85 | |
|---|
| 62 | | - @Column(name = "creation_timestamp") |
|---|
| 63 | | - @JsonProperty("creation_timestamp") |
|---|
| 64 | | - private Date creationTimestamp; |
|---|
| 86 | + @JsonIgnore |
|---|
| 87 | + @ManyToOne |
|---|
| 88 | + @JoinColumn(name = "license_type_id") |
|---|
| 89 | + private LicenseType licenseType; |
|---|
| 65 | 90 | |
|---|
| 66 | | - @JsonIgnore |
|---|
| 67 | | - @ManyToOne |
|---|
| 68 | | - @JoinColumn(name = "organization_id") |
|---|
| 69 | | - private Organization organization; |
|---|
| 91 | + @JsonIgnore |
|---|
| 92 | + @ManyToOne |
|---|
| 93 | + @JoinColumn(name = "created_by") |
|---|
| 94 | + private User createdBy; |
|---|
| 70 | 95 | |
|---|
| 71 | | - @JsonIgnore |
|---|
| 72 | | - @ManyToOne |
|---|
| 73 | | - @JoinColumn(name = "license_type_id") |
|---|
| 74 | | - private LicenseType licenseType; |
|---|
| 96 | + @JsonIgnore |
|---|
| 97 | + @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "pack") |
|---|
| 98 | + private Set<License> licenses; |
|---|
| 75 | 99 | |
|---|
| 76 | | - @JsonIgnore |
|---|
| 77 | | - @ManyToOne |
|---|
| 78 | | - @JoinColumn(name = "created_by") |
|---|
| 79 | | - private User createdBy; |
|---|
| 100 | + @Column(name = "num_licenses") |
|---|
| 101 | + @JsonProperty("num_licenses") |
|---|
| 102 | + private int numLicenses; |
|---|
| 80 | 103 | |
|---|
| 81 | | - @JsonIgnore |
|---|
| 82 | | - @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "pack") |
|---|
| 83 | | - private Set<License> licenses; |
|---|
| 104 | + @Column(name = "init_valid_date") |
|---|
| 105 | + @JsonProperty("init_valid_date") |
|---|
| 106 | + private Date initValidDate; |
|---|
| 84 | 107 | |
|---|
| 85 | | - @Column(name = "num_licenses") |
|---|
| 86 | | - @JsonProperty("num_licenses") |
|---|
| 87 | | - private int numLicenses; |
|---|
| 108 | + @Column(name = "end_valid_date") |
|---|
| 109 | + @JsonProperty("end_valid_date") |
|---|
| 110 | + private Date endValidDate; |
|---|
| 88 | 111 | |
|---|
| 89 | | - @Column(name = "init_valid_date") |
|---|
| 90 | | - @JsonProperty("init_valid_date") |
|---|
| 91 | | - private Date initValidDate; |
|---|
| 112 | + @Type(type = "net.curisit.securis.db.common.PackStatusType") |
|---|
| 113 | + private PackStatus status; |
|---|
| 92 | 114 | |
|---|
| 93 | | - @Column(name = "end_valid_date") |
|---|
| 94 | | - @JsonProperty("end_valid_date") |
|---|
| 95 | | - private Date endValidDate; |
|---|
| 115 | + @Column(name = "license_preactivation") |
|---|
| 116 | + @JsonProperty("license_preactivation") |
|---|
| 117 | + private boolean licensePreactivation; |
|---|
| 96 | 118 | |
|---|
| 97 | | - @Type(type = "net.curisit.securis.db.common.PackStatusType") |
|---|
| 98 | | - private PackStatus status; |
|---|
| 119 | + @Column(name = "preactivation_valid_period") |
|---|
| 120 | + @JsonProperty("preactivation_valid_period") |
|---|
| 121 | + private Integer preactivationValidPeriod; |
|---|
| 99 | 122 | |
|---|
| 100 | | - @Column(name = "license_preactivation") |
|---|
| 101 | | - @JsonProperty("license_preactivation") |
|---|
| 102 | | - private boolean licensePreactivation; |
|---|
| 123 | + @Column(name = "renew_valid_period") |
|---|
| 124 | + @JsonProperty("renew_valid_period") |
|---|
| 125 | + private Integer renewValidPeriod; |
|---|
| 103 | 126 | |
|---|
| 104 | | - @Column(name = "preactivation_valid_period") |
|---|
| 105 | | - @JsonProperty("preactivation_valid_period") |
|---|
| 106 | | - private Integer preactivationValidPeriod; |
|---|
| 127 | + @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "pack") |
|---|
| 128 | + private Set<PackMetadata> metadata; |
|---|
| 107 | 129 | |
|---|
| 108 | | - @Column(name = "renew_valid_period") |
|---|
| 109 | | - @JsonProperty("renew_valid_period") |
|---|
| 110 | | - private Integer renewValidPeriod; |
|---|
| 130 | + // ---------------- Getters & setters ---------------- |
|---|
| 111 | 131 | |
|---|
| 112 | | - @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH }, mappedBy = "pack") |
|---|
| 113 | | - private Set<PackMetadata> metadata; |
|---|
| 132 | + /** |
|---|
| 133 | + * getId<p> |
|---|
| 134 | + * Return primary key. |
|---|
| 135 | + * |
|---|
| 136 | + * @return id |
|---|
| 137 | + */ |
|---|
| 138 | + public Integer getId() { return id; } |
|---|
| 114 | 139 | |
|---|
| 115 | | - public Integer getId() { |
|---|
| 116 | | - return id; |
|---|
| 117 | | - } |
|---|
| 140 | + /** |
|---|
| 141 | + * setId<p> |
|---|
| 142 | + * Set primary key. |
|---|
| 143 | + * |
|---|
| 144 | + * @param id |
|---|
| 145 | + */ |
|---|
| 146 | + public void setId(Integer id) { this.id = id; } |
|---|
| 118 | 147 | |
|---|
| 119 | | - public void setId(Integer id) { |
|---|
| 120 | | - this.id = id; |
|---|
| 121 | | - } |
|---|
| 148 | + /** |
|---|
| 149 | + * getCode<p> |
|---|
| 150 | + * Return pack code. |
|---|
| 151 | + * |
|---|
| 152 | + * @return packCode |
|---|
| 153 | + */ |
|---|
| 154 | + public String getCode() { return code; } |
|---|
| 122 | 155 | |
|---|
| 123 | | - public String getCode() { |
|---|
| 124 | | - return code; |
|---|
| 125 | | - } |
|---|
| 156 | + /** |
|---|
| 157 | + * setCode<p> |
|---|
| 158 | + * Set pack code. |
|---|
| 159 | + * |
|---|
| 160 | + * @param packCode |
|---|
| 161 | + */ |
|---|
| 162 | + public void setCode(String code) { this.code = code; } |
|---|
| 126 | 163 | |
|---|
| 127 | | - public void setCode(String code) { |
|---|
| 128 | | - this.code = code; |
|---|
| 129 | | - } |
|---|
| 164 | + /** |
|---|
| 165 | + * getCreationTimestamp<p> |
|---|
| 166 | + * Return creation timestamp. |
|---|
| 167 | + * |
|---|
| 168 | + * @return creationTimestamp |
|---|
| 169 | + */ |
|---|
| 170 | + public Date getCreationTimestamp() { return creationTimestamp; } |
|---|
| 130 | 171 | |
|---|
| 131 | | - public Date getCreationTimestamp() { |
|---|
| 132 | | - return creationTimestamp; |
|---|
| 133 | | - } |
|---|
| 172 | + /** |
|---|
| 173 | + * setCreationTimestamp<p> |
|---|
| 174 | + * Set creation timestamp. |
|---|
| 175 | + * |
|---|
| 176 | + * @param creationTimestamp |
|---|
| 177 | + */ |
|---|
| 178 | + public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; } |
|---|
| 134 | 179 | |
|---|
| 135 | | - public void setCreationTimestamp(Date creationTimestamp) { |
|---|
| 136 | | - this.creationTimestamp = creationTimestamp; |
|---|
| 137 | | - } |
|---|
| 180 | + /** |
|---|
| 181 | + * getOrganization<p> |
|---|
| 182 | + * Return owning organization (entity). |
|---|
| 183 | + * |
|---|
| 184 | + * @return organization |
|---|
| 185 | + */ |
|---|
| 186 | + public Organization getOrganization() { return organization; } |
|---|
| 138 | 187 | |
|---|
| 139 | | - public Organization getOrganization() { |
|---|
| 140 | | - return organization; |
|---|
| 141 | | - } |
|---|
| 188 | + /** |
|---|
| 189 | + * setOrganization<p> |
|---|
| 190 | + * Set owning organization (entity). |
|---|
| 191 | + * |
|---|
| 192 | + * @param organization |
|---|
| 193 | + */ |
|---|
| 194 | + public void setOrganization(Organization organization) { this.organization = organization; } |
|---|
| 142 | 195 | |
|---|
| 143 | | - public void setOrganization(Organization organization) { |
|---|
| 144 | | - this.organization = organization; |
|---|
| 145 | | - } |
|---|
| 196 | + /** |
|---|
| 197 | + * getLicenseType<p> |
|---|
| 198 | + * Return license type (entity). |
|---|
| 199 | + * |
|---|
| 200 | + * @return licenseType |
|---|
| 201 | + */ |
|---|
| 202 | + public LicenseType getLicenseType() { return licenseType; } |
|---|
| 146 | 203 | |
|---|
| 147 | | - public LicenseType getLicenseType() { |
|---|
| 148 | | - return licenseType; |
|---|
| 149 | | - } |
|---|
| 204 | + /** |
|---|
| 205 | + * setLicenseType<p> |
|---|
| 206 | + * Set license type (entity). |
|---|
| 207 | + * |
|---|
| 208 | + * @param licenseType |
|---|
| 209 | + */ |
|---|
| 210 | + public void setLicenseType(LicenseType licenseType) { this.licenseType = licenseType; } |
|---|
| 150 | 211 | |
|---|
| 151 | | - public void setLicenseType(LicenseType licenseType) { |
|---|
| 152 | | - this.licenseType = licenseType; |
|---|
| 153 | | - } |
|---|
| 212 | + /** |
|---|
| 213 | + * getCreatedBy<p> |
|---|
| 214 | + * Return creator (entity). |
|---|
| 215 | + * |
|---|
| 216 | + * @return user |
|---|
| 217 | + */ |
|---|
| 218 | + public User getCreatedBy() { return createdBy; } |
|---|
| 154 | 219 | |
|---|
| 155 | | - public User getCreatedBy() { |
|---|
| 156 | | - return createdBy; |
|---|
| 157 | | - } |
|---|
| 220 | + /** |
|---|
| 221 | + * setCreatedBy<p> |
|---|
| 222 | + * Set creator (entity). |
|---|
| 223 | + * |
|---|
| 224 | + * @param user |
|---|
| 225 | + */ |
|---|
| 226 | + public void setCreatedBy(User createdBy) { this.createdBy = createdBy; } |
|---|
| 158 | 227 | |
|---|
| 159 | | - public void setCreatedBy(User createdBy) { |
|---|
| 160 | | - this.createdBy = createdBy; |
|---|
| 161 | | - } |
|---|
| 228 | + /** |
|---|
| 229 | + * getNumLicenses<p> |
|---|
| 230 | + * Return capacity (licenses). |
|---|
| 231 | + * |
|---|
| 232 | + * @return numLicenses |
|---|
| 233 | + * Number of licenses |
|---|
| 234 | + */ |
|---|
| 235 | + public int getNumLicenses() { return numLicenses; } |
|---|
| 162 | 236 | |
|---|
| 163 | | - public int getNumLicenses() { |
|---|
| 164 | | - return numLicenses; |
|---|
| 165 | | - } |
|---|
| 237 | + /** |
|---|
| 238 | + * setNumLicenses<p> |
|---|
| 239 | + * Set capacity (licenses). |
|---|
| 240 | + * |
|---|
| 241 | + * @param numLicenses |
|---|
| 242 | + * Number of licenses |
|---|
| 243 | + */ |
|---|
| 244 | + public void setNumLicenses(int numLicenses) { this.numLicenses = numLicenses; } |
|---|
| 166 | 245 | |
|---|
| 167 | | - public void setNumLicenses(int numLicenses) { |
|---|
| 168 | | - this.numLicenses = numLicenses; |
|---|
| 169 | | - } |
|---|
| 246 | + /** |
|---|
| 247 | + * getNumActivations<p> |
|---|
| 248 | + * Count ACTIVE/PRE_ACTIVE licenses in this pack. |
|---|
| 249 | + * |
|---|
| 250 | + * @return numActivations |
|---|
| 251 | + * number of activated licenses |
|---|
| 252 | + */ |
|---|
| 253 | + @JsonProperty("num_activations") |
|---|
| 254 | + public int getNumActivations() { |
|---|
| 255 | + if (licenses == null) return 0; |
|---|
| 256 | + int num = 0; |
|---|
| 257 | + for (License lic : licenses) { |
|---|
| 258 | + if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) num++; |
|---|
| 259 | + } |
|---|
| 260 | + return num; |
|---|
| 261 | + } |
|---|
| 170 | 262 | |
|---|
| 171 | | - @JsonProperty("num_activations") |
|---|
| 172 | | - public int getNumActivations() { |
|---|
| 173 | | - if (licenses == null) { |
|---|
| 174 | | - return 0; |
|---|
| 175 | | - } |
|---|
| 176 | | - int num = 0; |
|---|
| 177 | | - for (License lic : licenses) { |
|---|
| 178 | | - if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) { |
|---|
| 179 | | - num++; |
|---|
| 180 | | - } |
|---|
| 181 | | - } |
|---|
| 182 | | - return num; |
|---|
| 183 | | - } |
|---|
| 263 | + /** |
|---|
| 264 | + * getNumCreations<p> |
|---|
| 265 | + * Count all created licenses (including waiting for activation). Ignores CANCELLED. |
|---|
| 266 | + * |
|---|
| 267 | + * @return numCreations |
|---|
| 268 | + * number of created licenses |
|---|
| 269 | + */ |
|---|
| 270 | + @JsonProperty("num_creations") |
|---|
| 271 | + public int getNumCreations() { |
|---|
| 272 | + if (licenses == null) return 0; |
|---|
| 273 | + int num = 0; |
|---|
| 274 | + for (License lic : licenses) { |
|---|
| 275 | + if (lic.getStatus() != LicenseStatus.CANCELLED) num++; |
|---|
| 276 | + } |
|---|
| 277 | + return num; |
|---|
| 278 | + } |
|---|
| 184 | 279 | |
|---|
| 185 | | - /** |
|---|
| 186 | | - * Counts all created licenses, It counts active licenses and licenses |
|---|
| 187 | | - * waiting for activation This number will be used to control the max number |
|---|
| 188 | | - * of licenses created. Ignore canceled licenses. |
|---|
| 189 | | - * |
|---|
| 190 | | - * @return |
|---|
| 191 | | - */ |
|---|
| 192 | | - @JsonProperty("num_creations") |
|---|
| 193 | | - public int getNumCreations() { |
|---|
| 194 | | - if (licenses == null) { |
|---|
| 195 | | - return 0; |
|---|
| 196 | | - } |
|---|
| 197 | | - int num = 0; |
|---|
| 198 | | - for (License lic : licenses) { |
|---|
| 199 | | - if (lic.getStatus() != LicenseStatus.CANCELLED) { |
|---|
| 200 | | - num++; |
|---|
| 201 | | - } |
|---|
| 202 | | - } |
|---|
| 203 | | - return num; |
|---|
| 204 | | - } |
|---|
| 280 | + /** |
|---|
| 281 | + * getNumAvailables<p> |
|---|
| 282 | + * Number of available licenses in this pack: capacity - activations. |
|---|
| 283 | + * |
|---|
| 284 | + * @return numAvailable |
|---|
| 285 | + * Number of available licenses |
|---|
| 286 | + */ |
|---|
| 287 | + @JsonProperty("num_available") |
|---|
| 288 | + public int getNumAvailables() { return numLicenses - getNumActivations(); } |
|---|
| 205 | 289 | |
|---|
| 206 | | - /** |
|---|
| 207 | | - * Number of available licenses in this pack |
|---|
| 208 | | - * |
|---|
| 209 | | - * @return |
|---|
| 210 | | - */ |
|---|
| 211 | | - @JsonProperty("num_available") |
|---|
| 212 | | - public int getNumAvailables() { |
|---|
| 213 | | - return numLicenses - getNumActivations(); |
|---|
| 214 | | - } |
|---|
| 290 | + /** |
|---|
| 291 | + * getOrgName<p> |
|---|
| 292 | + * Expose organization name. |
|---|
| 293 | + * |
|---|
| 294 | + * @return orgName |
|---|
| 295 | + */ |
|---|
| 296 | + @JsonProperty("organization_name") |
|---|
| 297 | + public String getOrgName() { return organization == null ? null : organization.getName(); } |
|---|
| 215 | 298 | |
|---|
| 216 | | - @JsonProperty("organization_name") |
|---|
| 217 | | - public String getOrgName() { |
|---|
| 218 | | - return organization == null ? null : organization.getName(); |
|---|
| 219 | | - } |
|---|
| 299 | + /** |
|---|
| 300 | + * getAppName<p> |
|---|
| 301 | + * Expose application name via license type. |
|---|
| 302 | + * |
|---|
| 303 | + * @return appName |
|---|
| 304 | + */ |
|---|
| 305 | + @JsonProperty("application_name") |
|---|
| 306 | + public String getAppName() { |
|---|
| 307 | + if (licenseType == null) return null; |
|---|
| 308 | + Application app = licenseType.getApplication(); |
|---|
| 309 | + return app == null ? null : app.getName(); |
|---|
| 310 | + } |
|---|
| 220 | 311 | |
|---|
| 221 | | - @JsonProperty("application_name") |
|---|
| 222 | | - public String getAppName() { |
|---|
| 223 | | - if (licenseType == null) { |
|---|
| 224 | | - return null; |
|---|
| 225 | | - } |
|---|
| 226 | | - Application app = licenseType.getApplication(); |
|---|
| 227 | | - return app == null ? null : app.getName(); |
|---|
| 228 | | - } |
|---|
| 312 | + /** |
|---|
| 313 | + * getOrgId<p> |
|---|
| 314 | + * Expose organization id. |
|---|
| 315 | + * |
|---|
| 316 | + * @return orgId |
|---|
| 317 | + */ |
|---|
| 318 | + @JsonProperty("organization_id") |
|---|
| 319 | + public Integer getOrgId() { return organization == null ? null : organization.getId(); } |
|---|
| 229 | 320 | |
|---|
| 230 | | - @JsonProperty("organization_id") |
|---|
| 231 | | - public Integer getOrgId() { |
|---|
| 232 | | - return organization == null ? null : organization.getId(); |
|---|
| 233 | | - } |
|---|
| 321 | + /** |
|---|
| 322 | + * setOrgId<p> |
|---|
| 323 | + * Setter by id for JSON binding (creates shallow Organization). |
|---|
| 324 | + * |
|---|
| 325 | + * @param orgId |
|---|
| 326 | + */ |
|---|
| 327 | + @JsonProperty("organization_id") |
|---|
| 328 | + public void setOrgId(Integer idOrg) { |
|---|
| 329 | + if (idOrg == null) { |
|---|
| 330 | + organization = null; |
|---|
| 331 | + } else { |
|---|
| 332 | + organization = new Organization(); |
|---|
| 333 | + organization.setId(idOrg); |
|---|
| 334 | + } |
|---|
| 335 | + } |
|---|
| 234 | 336 | |
|---|
| 235 | | - @JsonProperty("organization_id") |
|---|
| 236 | | - public void setOrgId(Integer idOrg) { |
|---|
| 237 | | - if (idOrg == null) { |
|---|
| 238 | | - organization = null; |
|---|
| 239 | | - } else { |
|---|
| 240 | | - organization = new Organization(); |
|---|
| 241 | | - organization.setId(idOrg); |
|---|
| 242 | | - } |
|---|
| 243 | | - } |
|---|
| 337 | + /** |
|---|
| 338 | + * setLicTypeId<p> |
|---|
| 339 | + * Setter by id for JSON binding (creates shallow LicenseType). |
|---|
| 340 | + * |
|---|
| 341 | + * @param licTypeId |
|---|
| 342 | + */ |
|---|
| 343 | + @JsonProperty("license_type_id") |
|---|
| 344 | + public void setLicTypeId(Integer idLT) { |
|---|
| 345 | + if (idLT == null) { |
|---|
| 346 | + licenseType = null; |
|---|
| 347 | + } else { |
|---|
| 348 | + licenseType = new LicenseType(); |
|---|
| 349 | + licenseType.setId(idLT); |
|---|
| 350 | + } |
|---|
| 351 | + } |
|---|
| 244 | 352 | |
|---|
| 245 | | - @JsonProperty("license_type_id") |
|---|
| 246 | | - public void setLicTypeId(Integer idLT) { |
|---|
| 247 | | - if (idLT == null) { |
|---|
| 248 | | - licenseType = null; |
|---|
| 249 | | - } else { |
|---|
| 250 | | - licenseType = new LicenseType(); |
|---|
| 251 | | - licenseType.setId(idLT); |
|---|
| 252 | | - } |
|---|
| 253 | | - } |
|---|
| 353 | + /** |
|---|
| 354 | + * getLicTypeId<p> |
|---|
| 355 | + * Expose license type id. |
|---|
| 356 | + * |
|---|
| 357 | + * @return licTypeId |
|---|
| 358 | + */ |
|---|
| 359 | + @JsonProperty("license_type_id") |
|---|
| 360 | + public Integer getLicTypeId() { return licenseType == null ? null : licenseType.getId(); } |
|---|
| 254 | 361 | |
|---|
| 255 | | - @JsonProperty("license_type_id") |
|---|
| 256 | | - public Integer getLicTypeId() { |
|---|
| 257 | | - return licenseType == null ? null : licenseType.getId(); |
|---|
| 258 | | - } |
|---|
| 362 | + /** |
|---|
| 363 | + * getCreatedById<p> |
|---|
| 364 | + * Expose creator username. |
|---|
| 365 | + * |
|---|
| 366 | + * @return username |
|---|
| 367 | + */ |
|---|
| 368 | + @JsonProperty("created_by_id") |
|---|
| 369 | + public String getCreatedById() { return createdBy == null ? null : createdBy.getUsername(); } |
|---|
| 259 | 370 | |
|---|
| 260 | | - @JsonProperty("created_by_id") |
|---|
| 261 | | - public String getCreatedById() { |
|---|
| 262 | | - return createdBy == null ? null : createdBy.getUsername(); |
|---|
| 263 | | - } |
|---|
| 371 | + /** |
|---|
| 372 | + * setCreatedById<p> |
|---|
| 373 | + * Setter by username (creates shallow User). |
|---|
| 374 | + * |
|---|
| 375 | + * @param username |
|---|
| 376 | + */ |
|---|
| 377 | + @JsonProperty("created_by_id") |
|---|
| 378 | + public void setCreatedById(String username) { |
|---|
| 379 | + createdBy = new User(); |
|---|
| 380 | + createdBy.setUsername(username); |
|---|
| 381 | + } |
|---|
| 264 | 382 | |
|---|
| 265 | | - @JsonProperty("created_by_id") |
|---|
| 266 | | - public void setCreatedById(String username) { |
|---|
| 267 | | - createdBy = new User(); |
|---|
| 268 | | - createdBy.setUsername(username); |
|---|
| 269 | | - } |
|---|
| 383 | + /** |
|---|
| 384 | + * getCreatedByname<p> |
|---|
| 385 | + * Expose creator full display name. |
|---|
| 386 | + * |
|---|
| 387 | + * @return userName |
|---|
| 388 | + */ |
|---|
| 389 | + @JsonProperty("created_by_name") |
|---|
| 390 | + public String getCreatedByname() { |
|---|
| 391 | + return createdBy == null ? null |
|---|
| 392 | + : String.format("%s %s (%s)", createdBy.getFirstName(), |
|---|
| 393 | + createdBy.getLastName() != null ? createdBy.getLastName() : "", |
|---|
| 394 | + createdBy.getUsername()); |
|---|
| 395 | + } |
|---|
| 270 | 396 | |
|---|
| 271 | | - @JsonProperty("created_by_name") |
|---|
| 272 | | - public String getCreatedByname() { |
|---|
| 273 | | - return createdBy == null ? null |
|---|
| 274 | | - : String.format("%s %s (%s)", createdBy.getFirstName(), createdBy.getLastName() != null ? createdBy.getLastName() : "", createdBy.getUsername()); |
|---|
| 275 | | - } |
|---|
| 397 | + /** |
|---|
| 398 | + * getLicenseTypeCode<p> |
|---|
| 399 | + * Expose license type code. |
|---|
| 400 | + * |
|---|
| 401 | + * @return licenseTypeCode |
|---|
| 402 | + */ |
|---|
| 403 | + @JsonProperty("licensetype_code") |
|---|
| 404 | + public String getLicenseTypeCode() { return licenseType == null ? null : licenseType.getCode(); } |
|---|
| 276 | 405 | |
|---|
| 277 | | - @JsonProperty("licensetype_code") |
|---|
| 278 | | - public String getLicenseTypeCode() { |
|---|
| 279 | | - return licenseType == null ? null : licenseType.getCode(); |
|---|
| 280 | | - } |
|---|
| 406 | + /** |
|---|
| 407 | + * getComments<p> |
|---|
| 408 | + * Return comments. |
|---|
| 409 | + * |
|---|
| 410 | + * @return comments |
|---|
| 411 | + */ |
|---|
| 412 | + public String getComments() { return comments; } |
|---|
| 281 | 413 | |
|---|
| 282 | | - public String getComments() { |
|---|
| 283 | | - return comments; |
|---|
| 284 | | - } |
|---|
| 414 | + /** |
|---|
| 415 | + * setComments<p> |
|---|
| 416 | + * Set comments. |
|---|
| 417 | + * |
|---|
| 418 | + * @param comments |
|---|
| 419 | + */ |
|---|
| 420 | + public void setComments(String comments) { this.comments = comments; } |
|---|
| 285 | 421 | |
|---|
| 286 | | - public void setComments(String comments) { |
|---|
| 287 | | - this.comments = comments; |
|---|
| 288 | | - } |
|---|
| 422 | + /** |
|---|
| 423 | + * isLicensePreactivation<p> |
|---|
| 424 | + * Whether licenses are pre-activated. |
|---|
| 425 | + * |
|---|
| 426 | + * @return isLicensePreactivation |
|---|
| 427 | + */ |
|---|
| 428 | + public boolean isLicensePreactivation() { return licensePreactivation; } |
|---|
| 289 | 429 | |
|---|
| 290 | | - public boolean isLicensePreactivation() { |
|---|
| 291 | | - return licensePreactivation; |
|---|
| 292 | | - } |
|---|
| 430 | + /** |
|---|
| 431 | + * setLicensePreactivation<p> |
|---|
| 432 | + * Set pre-activation flag. |
|---|
| 433 | + * |
|---|
| 434 | + * @param licensePreactivation |
|---|
| 435 | + */ |
|---|
| 436 | + public void setLicensePreactivation(boolean licensePreactivation) { this.licensePreactivation = licensePreactivation; } |
|---|
| 293 | 437 | |
|---|
| 294 | | - public void setLicensePreactivation(boolean licensePreactivation) { |
|---|
| 295 | | - this.licensePreactivation = licensePreactivation; |
|---|
| 296 | | - } |
|---|
| 438 | + /** |
|---|
| 439 | + * getMetadata<p> |
|---|
| 440 | + * Return pack metadata entries. |
|---|
| 441 | + * |
|---|
| 442 | + * @return metadata |
|---|
| 443 | + */ |
|---|
| 444 | + public Set<PackMetadata> getMetadata() { return metadata; } |
|---|
| 297 | 445 | |
|---|
| 298 | | - public Set<PackMetadata> getMetadata() { |
|---|
| 299 | | - return metadata; |
|---|
| 300 | | - } |
|---|
| 446 | + /** |
|---|
| 447 | + * setMetadata<p> |
|---|
| 448 | + * Set pack metadata entries. |
|---|
| 449 | + * |
|---|
| 450 | + * @param metadata |
|---|
| 451 | + */ |
|---|
| 452 | + public void setMetadata(Set<PackMetadata> metadata) { this.metadata = metadata; } |
|---|
| 301 | 453 | |
|---|
| 302 | | - public void setMetadata(Set<PackMetadata> metadata) { |
|---|
| 303 | | - this.metadata = metadata; |
|---|
| 304 | | - } |
|---|
| 454 | + /** |
|---|
| 455 | + * getStatus<p> |
|---|
| 456 | + * Return pack status. |
|---|
| 457 | + * |
|---|
| 458 | + * @return packStatus |
|---|
| 459 | + */ |
|---|
| 460 | + public PackStatus getStatus() { return status; } |
|---|
| 305 | 461 | |
|---|
| 306 | | - public PackStatus getStatus() { |
|---|
| 307 | | - return status; |
|---|
| 308 | | - } |
|---|
| 462 | + /** |
|---|
| 463 | + * setStatus<p> |
|---|
| 464 | + * Set pack status. |
|---|
| 465 | + * |
|---|
| 466 | + * @param packStatus |
|---|
| 467 | + */ |
|---|
| 468 | + public void setStatus(PackStatus status) { this.status = status; } |
|---|
| 309 | 469 | |
|---|
| 310 | | - public void setStatus(PackStatus status) { |
|---|
| 311 | | - this.status = status; |
|---|
| 312 | | - } |
|---|
| 470 | + /** |
|---|
| 471 | + * getInitValidDate<p> |
|---|
| 472 | + * Return start of validity window. |
|---|
| 473 | + * |
|---|
| 474 | + * @return initValidDate |
|---|
| 475 | + */ |
|---|
| 476 | + public Date getInitValidDate() { return initValidDate; } |
|---|
| 313 | 477 | |
|---|
| 314 | | - public Date getInitValidDate() { |
|---|
| 315 | | - return initValidDate; |
|---|
| 316 | | - } |
|---|
| 478 | + /** |
|---|
| 479 | + * setInitValidDate<p> |
|---|
| 480 | + * Set start of validity window. |
|---|
| 481 | + * |
|---|
| 482 | + * @param initValidDate |
|---|
| 483 | + */ |
|---|
| 484 | + public void setInitValidDate(Date initValidDate) { this.initValidDate = initValidDate; } |
|---|
| 317 | 485 | |
|---|
| 318 | | - public void setInitValidDate(Date initValidDate) { |
|---|
| 319 | | - this.initValidDate = initValidDate; |
|---|
| 320 | | - } |
|---|
| 486 | + /** |
|---|
| 487 | + * getEndValidDate<p> |
|---|
| 488 | + * Return end of validity window. |
|---|
| 489 | + * |
|---|
| 490 | + * @return endValidDate |
|---|
| 491 | + */ |
|---|
| 492 | + public Date getEndValidDate() { return endValidDate; } |
|---|
| 321 | 493 | |
|---|
| 322 | | - public Date getEndValidDate() { |
|---|
| 323 | | - return endValidDate; |
|---|
| 324 | | - } |
|---|
| 494 | + /** |
|---|
| 495 | + * setEndValidDate<p> |
|---|
| 496 | + * Set end of validity window. |
|---|
| 497 | + * |
|---|
| 498 | + * @param endValidDate |
|---|
| 499 | + */ |
|---|
| 500 | + public void setEndValidDate(Date endValidDate) { this.endValidDate = endValidDate; } |
|---|
| 325 | 501 | |
|---|
| 326 | | - public void setEndValidDate(Date endValidDate) { |
|---|
| 327 | | - this.endValidDate = endValidDate; |
|---|
| 328 | | - } |
|---|
| 502 | + /** |
|---|
| 503 | + * getLicenses<p> |
|---|
| 504 | + * Return contained licenses (entity set). |
|---|
| 505 | + * |
|---|
| 506 | + * @return licenses |
|---|
| 507 | + */ |
|---|
| 508 | + public Set<License> getLicenses() { return licenses; } |
|---|
| 329 | 509 | |
|---|
| 330 | | - public Set<License> getLicenses() { |
|---|
| 331 | | - return licenses; |
|---|
| 332 | | - } |
|---|
| 510 | + /** |
|---|
| 511 | + * setLicenses<p> |
|---|
| 512 | + * Set contained licenses (entity set). |
|---|
| 513 | + * |
|---|
| 514 | + * @param licenses |
|---|
| 515 | + */ |
|---|
| 516 | + public void setLicenses(Set<License> licenses) { this.licenses = licenses; } |
|---|
| 333 | 517 | |
|---|
| 334 | | - public void setLicenses(Set<License> licenses) { |
|---|
| 335 | | - this.licenses = licenses; |
|---|
| 336 | | - } |
|---|
| 518 | + /** |
|---|
| 519 | + * getPreactivationValidPeriod<p> |
|---|
| 520 | + * Return preactivation validity (days). |
|---|
| 521 | + * |
|---|
| 522 | + * @return preactivationValidPeriod |
|---|
| 523 | + */ |
|---|
| 524 | + public Integer getPreactivationValidPeriod() { return preactivationValidPeriod; } |
|---|
| 337 | 525 | |
|---|
| 338 | | - public Integer getPreactivationValidPeriod() { |
|---|
| 339 | | - return preactivationValidPeriod; |
|---|
| 340 | | - } |
|---|
| 526 | + /** |
|---|
| 527 | + * setPreactivationValidPeriod<p> |
|---|
| 528 | + * Set preactivation validity (days). |
|---|
| 529 | + * |
|---|
| 530 | + * @param preactivationValidPeriod |
|---|
| 531 | + */ |
|---|
| 532 | + public void setPreactivationValidPeriod(Integer preactivationValidPeriod) { this.preactivationValidPeriod = preactivationValidPeriod; } |
|---|
| 341 | 533 | |
|---|
| 342 | | - public void setPreactivationValidPeriod(Integer preactivationValidPeriod) { |
|---|
| 343 | | - this.preactivationValidPeriod = preactivationValidPeriod; |
|---|
| 344 | | - } |
|---|
| 534 | + /** |
|---|
| 535 | + * getRenewValidPeriod<p> |
|---|
| 536 | + * Return renewal validity (days). |
|---|
| 537 | + * |
|---|
| 538 | + * @return renewValidPeriod |
|---|
| 539 | + */ |
|---|
| 540 | + public Integer getRenewValidPeriod() { return renewValidPeriod; } |
|---|
| 345 | 541 | |
|---|
| 346 | | - public Integer getRenewValidPeriod() { |
|---|
| 347 | | - return renewValidPeriod; |
|---|
| 348 | | - } |
|---|
| 542 | + /** |
|---|
| 543 | + * setRenewValidPeriod<p> |
|---|
| 544 | + * Set renewal validity (days). |
|---|
| 545 | + * |
|---|
| 546 | + * @param renewValidPeriod |
|---|
| 547 | + */ |
|---|
| 548 | + public void setRenewValidPeriod(Integer renewValidPeriod) { this.renewValidPeriod = renewValidPeriod; } |
|---|
| 349 | 549 | |
|---|
| 350 | | - public void setRenewValidPeriod(Integer renewValidPeriod) { |
|---|
| 351 | | - this.renewValidPeriod = renewValidPeriod; |
|---|
| 352 | | - } |
|---|
| 550 | + // ---------------- Object methods ---------------- |
|---|
| 353 | 551 | |
|---|
| 354 | | - @Override |
|---|
| 355 | | - public boolean equals(Object obj) { |
|---|
| 356 | | - if (!(obj instanceof Application)) |
|---|
| 357 | | - return false; |
|---|
| 358 | | - return id.equals(Pack.class.cast(obj).id); |
|---|
| 359 | | - } |
|---|
| 552 | + /** |
|---|
| 553 | + * equals<p> |
|---|
| 554 | + * Compare the current object with the given object |
|---|
| 555 | + * |
|---|
| 556 | + * @param object |
|---|
| 557 | + * @return isEquals |
|---|
| 558 | + */ |
|---|
| 559 | + @Override |
|---|
| 560 | + public boolean equals(Object obj) { |
|---|
| 561 | + if (!(obj instanceof Application)) return false; |
|---|
| 562 | + return id != null && id.equals(Pack.class.cast(obj).id); |
|---|
| 563 | + } |
|---|
| 360 | 564 | |
|---|
| 361 | | - @Override |
|---|
| 362 | | - public int hashCode() { |
|---|
| 363 | | - return (id == null ? 0 : id.hashCode()); |
|---|
| 364 | | - } |
|---|
| 565 | + /** |
|---|
| 566 | + * hashCode<p> |
|---|
| 567 | + * Get the object hashCode |
|---|
| 568 | + * |
|---|
| 569 | + * @return hashCode |
|---|
| 570 | + */ |
|---|
| 571 | + @Override |
|---|
| 572 | + public int hashCode() { return (id == null ? 0 : id.hashCode()); } |
|---|
| 365 | 573 | |
|---|
| 366 | | - @Override |
|---|
| 367 | | - public String toString() { |
|---|
| 368 | | - return String.format("Pack: ID: %d, code: %s", id, code); |
|---|
| 369 | | - } |
|---|
| 574 | + /** |
|---|
| 575 | + * toString<p> |
|---|
| 576 | + * Get the string describing the current object |
|---|
| 577 | + * |
|---|
| 578 | + * @return object string |
|---|
| 579 | + */ |
|---|
| 580 | + @Override |
|---|
| 581 | + public String toString() { return String.format("Pack: ID: %d, code: %s", id, code); } |
|---|
| 370 | 582 | |
|---|
| 371 | | - public boolean isFrozen() { |
|---|
| 372 | | - return frozen != null && frozen; |
|---|
| 373 | | - } |
|---|
| 583 | + /** |
|---|
| 584 | + * isFrozen<p> |
|---|
| 585 | + * Null-safe boolean getter. |
|---|
| 586 | + * |
|---|
| 587 | + * @return isFrozen |
|---|
| 588 | + */ |
|---|
| 589 | + public boolean isFrozen() { return frozen != null && frozen; } |
|---|
| 374 | 590 | |
|---|
| 375 | | - public void setFrozen(Boolean frozen) { |
|---|
| 376 | | - this.frozen = frozen; |
|---|
| 377 | | - } |
|---|
| 591 | + /** |
|---|
| 592 | + * setFrozen<p> |
|---|
| 593 | + * Set frozen flag (nullable wrapper). |
|---|
| 594 | + * |
|---|
| 595 | + * @param frozen |
|---|
| 596 | + */ |
|---|
| 597 | + public void setFrozen(Boolean frozen) { this.frozen = frozen; } |
|---|
| 378 | 598 | |
|---|
| 379 | | - public static class Action { |
|---|
| 380 | | - public static final int CREATE = 1; |
|---|
| 381 | | - public static final int ACTIVATION = 2; |
|---|
| 382 | | - public static final int PUT_ONHOLD = 3; |
|---|
| 383 | | - public static final int CANCEL = 4; |
|---|
| 384 | | - public static final int DELETE = 5; |
|---|
| 385 | | - } |
|---|
| 599 | + // ---------------- Status transitions ---------------- |
|---|
| 386 | 600 | |
|---|
| 387 | | - public static class Status { |
|---|
| 601 | + /** |
|---|
| 602 | + * Action<p> |
|---|
| 603 | + * Available actions for the Pack |
|---|
| 604 | + */ |
|---|
| 605 | + public static class Action { |
|---|
| 606 | + public static final int CREATE = 1; |
|---|
| 607 | + public static final int ACTIVATION = 2; |
|---|
| 608 | + public static final int PUT_ONHOLD = 3; |
|---|
| 609 | + public static final int CANCEL = 4; |
|---|
| 610 | + public static final int DELETE = 5; |
|---|
| 611 | + } |
|---|
| 388 | 612 | |
|---|
| 389 | | - private static final Map<Integer, List<PackStatus>> transitions = Utils.createMap( // |
|---|
| 390 | | - Action.ACTIVATION, Arrays.asList(PackStatus.CREATED, PackStatus.ON_HOLD, PackStatus.EXPIRED), // |
|---|
| 391 | | - Action.PUT_ONHOLD, Arrays.asList(PackStatus.ACTIVE), // |
|---|
| 392 | | - Action.CANCEL, Arrays.asList(PackStatus.ACTIVE, PackStatus.ON_HOLD, PackStatus.EXPIRED), // |
|---|
| 393 | | - Action.DELETE, Arrays.asList(PackStatus.CANCELLED, PackStatus.CREATED) // |
|---|
| 394 | | - ); |
|---|
| 613 | + /** |
|---|
| 614 | + * Status<p> |
|---|
| 615 | + * Pack status |
|---|
| 616 | + */ |
|---|
| 617 | + public static class Status { |
|---|
| 395 | 618 | |
|---|
| 396 | | - /** |
|---|
| 397 | | - * It checks if a given action is valid for the License, passing the |
|---|
| 398 | | - * action and the current license status |
|---|
| 399 | | - * |
|---|
| 400 | | - * @param oldStatus |
|---|
| 401 | | - * @param newStatus |
|---|
| 402 | | - * @return |
|---|
| 403 | | - */ |
|---|
| 404 | | - public static boolean isActionValid(Integer action, PackStatus currentStatus) { |
|---|
| 405 | | - List<PackStatus> validStatuses = transitions.get(action); |
|---|
| 619 | + private static final Map<Integer, List<PackStatus>> transitions = Utils.createMap( |
|---|
| 620 | + Action.ACTIVATION, Arrays.asList(PackStatus.CREATED, PackStatus.ON_HOLD, PackStatus.EXPIRED), |
|---|
| 621 | + Action.PUT_ONHOLD, Arrays.asList(PackStatus.ACTIVE), |
|---|
| 622 | + Action.CANCEL, Arrays.asList(PackStatus.ACTIVE, PackStatus.ON_HOLD, PackStatus.EXPIRED), |
|---|
| 623 | + Action.DELETE, Arrays.asList(PackStatus.CANCELLED, PackStatus.CREATED) |
|---|
| 624 | + ); |
|---|
| 406 | 625 | |
|---|
| 407 | | - return validStatuses != null && validStatuses.contains(currentStatus); |
|---|
| 408 | | - } |
|---|
| 409 | | - } |
|---|
| 626 | + /** |
|---|
| 627 | + * isActionValid<p> |
|---|
| 628 | + * Validate whether an action is allowed given the current pack status. |
|---|
| 629 | + * |
|---|
| 630 | + * @param action action constant |
|---|
| 631 | + * @param currentStatus current pack status |
|---|
| 632 | + * @return true if allowed |
|---|
| 633 | + */ |
|---|
| 634 | + public static boolean isActionValid(Integer action, PackStatus currentStatus) { |
|---|
| 635 | + List<PackStatus> validStatuses = transitions.get(action); |
|---|
| 636 | + return validStatuses != null && validStatuses.contains(currentStatus); |
|---|
| 637 | + } |
|---|
| 638 | + } |
|---|
| 410 | 639 | } |
|---|
| 640 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 22 | 25 | import net.curisit.securis.db.common.Metadata; |
|---|
| 23 | 26 | |
|---|
| 24 | 27 | /** |
|---|
| 25 | | - * Entity implementation class for Entity: pack_metadata |
|---|
| 26 | | - * |
|---|
| 27 | | - */ |
|---|
| 28 | +* PackMetadata |
|---|
| 29 | +* <p> |
|---|
| 30 | +* Single metadata entry (key/value/flags) attached to a {@link Pack}. |
|---|
| 31 | +* Uses composite PK: (pack_id, key). |
|---|
| 32 | +* |
|---|
| 33 | +* Mapping details: |
|---|
| 34 | +* - Table: pack_metadata |
|---|
| 35 | +* - PK: pack_id + key (two @Id fields) |
|---|
| 36 | +* - 'pack' is @JsonIgnore to reduce payload size in list views |
|---|
| 37 | +* - NamedQuery: list-pack-metadata by pack id |
|---|
| 38 | +* |
|---|
| 39 | +* Flags: |
|---|
| 40 | +* - readonly: UI hint to prevent edits |
|---|
| 41 | +* - mandatory: requires value on pack creation/updates |
|---|
| 42 | +* |
|---|
| 43 | +* @author JRA |
|---|
| 44 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 45 | +*/ |
|---|
| 28 | 46 | @JsonAutoDetect |
|---|
| 29 | 47 | @JsonInclude(Include.NON_NULL) |
|---|
| 30 | 48 | @Entity |
|---|
| 31 | 49 | @Table(name = "pack_metadata") |
|---|
| 32 | 50 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| 33 | | -@NamedQueries({ @NamedQuery(name = "list-pack-metadata", query = "SELECT a FROM PackMetadata a where a.pack.id = :packId") }) |
|---|
| 51 | +@NamedQueries({ |
|---|
| 52 | + @NamedQuery(name = "list-pack-metadata", |
|---|
| 53 | + query = "SELECT a FROM PackMetadata a where a.pack.id = :packId") |
|---|
| 54 | +}) |
|---|
| 34 | 55 | public class PackMetadata implements Serializable, Metadata { |
|---|
| 35 | 56 | |
|---|
| 36 | | - private static final long serialVersionUID = 1L; |
|---|
| 57 | + private static final long serialVersionUID = 1L; |
|---|
| 37 | 58 | |
|---|
| 38 | | - @Id |
|---|
| 39 | | - @JsonIgnore |
|---|
| 40 | | - @ManyToOne |
|---|
| 41 | | - @JoinColumn(name = "pack_id") |
|---|
| 42 | | - private Pack pack; |
|---|
| 59 | + /** PK part: owning pack (ignored in JSON). */ |
|---|
| 60 | + @Id |
|---|
| 61 | + @JsonIgnore |
|---|
| 62 | + @ManyToOne |
|---|
| 63 | + @JoinColumn(name = "pack_id") |
|---|
| 64 | + private Pack pack; |
|---|
| 43 | 65 | |
|---|
| 44 | | - @Id |
|---|
| 45 | | - @Column(name = "\"key\"") |
|---|
| 46 | | - private String key; |
|---|
| 66 | + /** PK part: metadata key (quoted column name). */ |
|---|
| 67 | + @Id |
|---|
| 68 | + @Column(name = "\"key\"") |
|---|
| 69 | + private String key; |
|---|
| 47 | 70 | |
|---|
| 48 | | - private String value; |
|---|
| 71 | + /** Metadata value. */ |
|---|
| 72 | + private String value; |
|---|
| 49 | 73 | |
|---|
| 50 | | - private boolean readonly; |
|---|
| 74 | + /** Whether this field can be edited by clients. */ |
|---|
| 75 | + private boolean readonly; |
|---|
| 51 | 76 | |
|---|
| 52 | | - private boolean mandatory; |
|---|
| 77 | + /** Whether this field is required. */ |
|---|
| 78 | + private boolean mandatory; |
|---|
| 53 | 79 | |
|---|
| 54 | | - @JsonProperty("pack_id") |
|---|
| 55 | | - public Integer getPackId() { |
|---|
| 56 | | - return pack == null ? null : pack.getId(); |
|---|
| 57 | | - } |
|---|
| 80 | + // -------- JSON helpers to expose pack id -------- |
|---|
| 58 | 81 | |
|---|
| 59 | | - @JsonProperty("pack_id") |
|---|
| 60 | | - public void setLicenseTypeId(Integer idPack) { |
|---|
| 61 | | - if (idPack == null) { |
|---|
| 62 | | - pack = null; |
|---|
| 63 | | - } else { |
|---|
| 64 | | - pack = new Pack(); |
|---|
| 65 | | - pack.setId(idPack); |
|---|
| 66 | | - } |
|---|
| 67 | | - } |
|---|
| 82 | + /** |
|---|
| 83 | + * getPackId<p> |
|---|
| 84 | + * Expose pack id as JSON scalar. |
|---|
| 85 | + * |
|---|
| 86 | + * @return packId |
|---|
| 87 | + */ |
|---|
| 88 | + @JsonProperty("pack_id") |
|---|
| 89 | + public Integer getPackId() { |
|---|
| 90 | + return pack == null ? null : pack.getId(); |
|---|
| 91 | + } |
|---|
| 68 | 92 | |
|---|
| 69 | | - public Pack getPack() { |
|---|
| 70 | | - return pack; |
|---|
| 71 | | - } |
|---|
| 93 | + /** |
|---|
| 94 | + * setLicenseTypeId<p> |
|---|
| 95 | + * Setter by id (creates shallow Pack). |
|---|
| 96 | + * |
|---|
| 97 | + * @param packId |
|---|
| 98 | + */ |
|---|
| 99 | + @JsonProperty("pack_id") |
|---|
| 100 | + public void setLicenseTypeId(Integer idPack) { |
|---|
| 101 | + if (idPack == null) { |
|---|
| 102 | + pack = null; |
|---|
| 103 | + } else { |
|---|
| 104 | + pack = new Pack(); |
|---|
| 105 | + pack.setId(idPack); |
|---|
| 106 | + } |
|---|
| 107 | + } |
|---|
| 72 | 108 | |
|---|
| 73 | | - public void setPack(Pack pack) { |
|---|
| 74 | | - this.pack = pack; |
|---|
| 75 | | - } |
|---|
| 109 | + // -------- Getters & setters -------- |
|---|
| 76 | 110 | |
|---|
| 77 | | - public String getValue() { |
|---|
| 78 | | - return value; |
|---|
| 79 | | - } |
|---|
| 111 | + /** |
|---|
| 112 | + * getPack<p> |
|---|
| 113 | + * Return owning pack (entity). |
|---|
| 114 | + * |
|---|
| 115 | + * @return pack |
|---|
| 116 | + */ |
|---|
| 117 | + public Pack getPack() { return pack; } |
|---|
| 80 | 118 | |
|---|
| 81 | | - public void setValue(String value) { |
|---|
| 82 | | - this.value = value; |
|---|
| 83 | | - } |
|---|
| 119 | + /** |
|---|
| 120 | + * setPack<p> |
|---|
| 121 | + * Set owning pack (entity). |
|---|
| 122 | + * |
|---|
| 123 | + * @param pack |
|---|
| 124 | + */ |
|---|
| 125 | + public void setPack(Pack pack) { this.pack = pack; } |
|---|
| 84 | 126 | |
|---|
| 85 | | - public String getKey() { |
|---|
| 86 | | - return key; |
|---|
| 87 | | - } |
|---|
| 127 | + /** |
|---|
| 128 | + * getValue<p> |
|---|
| 129 | + * Return metadata value. |
|---|
| 130 | + * |
|---|
| 131 | + * @return metadataValue |
|---|
| 132 | + */ |
|---|
| 133 | + public String getValue() { return value; } |
|---|
| 88 | 134 | |
|---|
| 89 | | - public void setKey(String key) { |
|---|
| 90 | | - this.key = key; |
|---|
| 91 | | - } |
|---|
| 135 | + /** |
|---|
| 136 | + * setValue<p> |
|---|
| 137 | + * Set the metadata value. |
|---|
| 138 | + * |
|---|
| 139 | + * @param metadataValue |
|---|
| 140 | + */ |
|---|
| 141 | + public void setValue(String value) { this.value = value; } |
|---|
| 92 | 142 | |
|---|
| 93 | | - public boolean isReadonly() { |
|---|
| 94 | | - return readonly; |
|---|
| 95 | | - } |
|---|
| 143 | + /** |
|---|
| 144 | + * getKey<p> |
|---|
| 145 | + * Return metadata key (PK part). |
|---|
| 146 | + * |
|---|
| 147 | + * @return key |
|---|
| 148 | + */ |
|---|
| 149 | + public String getKey() { return key; } |
|---|
| 96 | 150 | |
|---|
| 97 | | - public void setReadonly(boolean readonly) { |
|---|
| 98 | | - this.readonly = readonly; |
|---|
| 99 | | - } |
|---|
| 151 | + /** |
|---|
| 152 | + * setKey<p> |
|---|
| 153 | + * Set metadata key (PK part). |
|---|
| 154 | + * |
|---|
| 155 | + * @param key |
|---|
| 156 | + */ |
|---|
| 157 | + public void setKey(String key) { this.key = key; } |
|---|
| 100 | 158 | |
|---|
| 101 | | - public boolean isMandatory() { |
|---|
| 102 | | - return mandatory; |
|---|
| 103 | | - } |
|---|
| 159 | + /** |
|---|
| 160 | + * isReadonly<p> |
|---|
| 161 | + * Return read-only flag. |
|---|
| 162 | + * |
|---|
| 163 | + * @return isReadonly |
|---|
| 164 | + */ |
|---|
| 165 | + public boolean isReadonly() { return readonly; } |
|---|
| 104 | 166 | |
|---|
| 105 | | - public void setMandatory(boolean mandatory) { |
|---|
| 106 | | - this.mandatory = mandatory; |
|---|
| 107 | | - } |
|---|
| 167 | + /** |
|---|
| 168 | + * setReadonly<p> |
|---|
| 169 | + * Set read-only flag. |
|---|
| 170 | + * |
|---|
| 171 | + * @param readonly |
|---|
| 172 | + */ |
|---|
| 173 | + public void setReadonly(boolean readonly) { this.readonly = readonly; } |
|---|
| 108 | 174 | |
|---|
| 109 | | - @Override |
|---|
| 110 | | - public String toString() { |
|---|
| 111 | | - return String.format("PackMD (%s: %s)", key, value); |
|---|
| 112 | | - } |
|---|
| 175 | + /** |
|---|
| 176 | + * isMandatory<p> |
|---|
| 177 | + * Return mandatory flag. |
|---|
| 178 | + * |
|---|
| 179 | + * @return isMandatory |
|---|
| 180 | + */ |
|---|
| 181 | + public boolean isMandatory() { return mandatory; } |
|---|
| 113 | 182 | |
|---|
| 114 | | - @Override |
|---|
| 115 | | - public boolean equals(Object obj) { |
|---|
| 116 | | - if (!(obj instanceof PackMetadata)) |
|---|
| 117 | | - return false; |
|---|
| 118 | | - PackMetadata other = (PackMetadata) obj; |
|---|
| 119 | | - return Objects.equals(key, other.key) && Objects.equals(pack, other.pack); |
|---|
| 120 | | - } |
|---|
| 183 | + /** |
|---|
| 184 | + * setMandatory<p> |
|---|
| 185 | + * Set mandatory flag. |
|---|
| 186 | + * |
|---|
| 187 | + * @param mandatory |
|---|
| 188 | + */ |
|---|
| 189 | + public void setMandatory(boolean mandatory) { this.mandatory = mandatory; } |
|---|
| 121 | 190 | |
|---|
| 122 | | - @Override |
|---|
| 123 | | - public int hashCode() { |
|---|
| 124 | | - return Objects.hash(key, pack); |
|---|
| 125 | | - } |
|---|
| 191 | + // -------- Object methods -------- |
|---|
| 126 | 192 | |
|---|
| 193 | + /** |
|---|
| 194 | + * toString<p> |
|---|
| 195 | + * Get the string describing the current object |
|---|
| 196 | + * |
|---|
| 197 | + * @return object string |
|---|
| 198 | + */ |
|---|
| 199 | + @Override |
|---|
| 200 | + public String toString() { return String.format("PackMD (%s: %s)", key, value); } |
|---|
| 201 | + |
|---|
| 202 | + /** |
|---|
| 203 | + * equals<p> |
|---|
| 204 | + * Compare the current object with the given object |
|---|
| 205 | + * |
|---|
| 206 | + * @param object |
|---|
| 207 | + * @return isEquals |
|---|
| 208 | + */ |
|---|
| 209 | + @Override |
|---|
| 210 | + public boolean equals(Object obj) { |
|---|
| 211 | + if (!(obj instanceof PackMetadata)) return false; |
|---|
| 212 | + PackMetadata other = (PackMetadata) obj; |
|---|
| 213 | + return Objects.equals(key, other.key) && Objects.equals(pack, other.pack); |
|---|
| 214 | + } |
|---|
| 215 | + |
|---|
| 216 | + /** |
|---|
| 217 | + * hashCode<p> |
|---|
| 218 | + * Get the object hashCode |
|---|
| 219 | + * |
|---|
| 220 | + * @return hashCode |
|---|
| 221 | + */ |
|---|
| 222 | + @Override |
|---|
| 223 | + public int hashCode() { return Objects.hash(key, pack); } |
|---|
| 127 | 224 | } |
|---|
| 225 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import net.curisit.securis.db.common.CodedEnum; |
|---|
| .. | .. |
|---|
| 6 | 9 | import com.fasterxml.jackson.annotation.JsonValue; |
|---|
| 7 | 10 | |
|---|
| 8 | 11 | /** |
|---|
| 9 | | - * Contains the possible pack statuses. For further details: |
|---|
| 10 | | - * https://redmine.curistec.com/projects/securis/wiki/LicensesServerManagement |
|---|
| 11 | | - * |
|---|
| 12 | | - * @author rob |
|---|
| 13 | | - */ |
|---|
| 12 | +* PackStatus |
|---|
| 13 | +* <p> |
|---|
| 14 | +* Enumerates possible pack lifecycle statuses. JSON representation is the short code. |
|---|
| 15 | +* See: https://redmine.curistec.com/projects/securis/wiki/LicensesServerManagement |
|---|
| 16 | +* |
|---|
| 17 | +* @author JRA |
|---|
| 18 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 19 | +*/ |
|---|
| 14 | 20 | public enum PackStatus implements CodedEnum { |
|---|
| 21 | + |
|---|
| 22 | + // Available status for the pack |
|---|
| 15 | 23 | CREATED("CR"), ACTIVE("AC"), ON_HOLD("OH"), EXPIRED("EX"), CANCELLED("CA"); |
|---|
| 16 | 24 | |
|---|
| 17 | 25 | private final String code; |
|---|
| 18 | 26 | |
|---|
| 19 | | - PackStatus(String code) { |
|---|
| 20 | | - this.code = code; |
|---|
| 21 | | - } |
|---|
| 27 | + /** |
|---|
| 28 | + * PackStatus<p> |
|---|
| 29 | + * Constructor |
|---|
| 30 | + * |
|---|
| 31 | + * @param code |
|---|
| 32 | + */ |
|---|
| 33 | + PackStatus(String code) { this.code = code; } |
|---|
| 22 | 34 | |
|---|
| 23 | | - @Override |
|---|
| 24 | | - public String getCode() { |
|---|
| 25 | | - return code; |
|---|
| 26 | | - } |
|---|
| 35 | + /** |
|---|
| 36 | + * getCode<p> |
|---|
| 37 | + * Short code stored in DB / used in JSON. |
|---|
| 38 | + * |
|---|
| 39 | + * @return packCode |
|---|
| 40 | + */ |
|---|
| 41 | + @Override public String getCode() { return code; } |
|---|
| 27 | 42 | |
|---|
| 43 | + /** |
|---|
| 44 | + * valueFromCode<p> |
|---|
| 45 | + * Factory method from short code. |
|---|
| 46 | + * |
|---|
| 47 | + * @param packCode |
|---|
| 48 | + * @return packStatus |
|---|
| 49 | + */ |
|---|
| 28 | 50 | @JsonCreator |
|---|
| 29 | 51 | public static PackStatus valueFromCode(String code) { |
|---|
| 30 | 52 | for (PackStatus ps : PackStatus.values()) { |
|---|
| 31 | | - if (ps.code.equals(code)) { |
|---|
| 32 | | - return ps; |
|---|
| 33 | | - } |
|---|
| 53 | + if (ps.code.equals(code)) return ps; |
|---|
| 34 | 54 | } |
|---|
| 35 | 55 | return null; |
|---|
| 36 | 56 | } |
|---|
| 37 | 57 | |
|---|
| 58 | + /** |
|---|
| 59 | + * getName<p> |
|---|
| 60 | + * Expose short code as JSON value. |
|---|
| 61 | + * |
|---|
| 62 | + * @return name |
|---|
| 63 | + */ |
|---|
| 38 | 64 | @JsonValue |
|---|
| 39 | | - public String getName() { |
|---|
| 40 | | - return this.code; |
|---|
| 41 | | - } |
|---|
| 42 | | - |
|---|
| 65 | + public String getName() { return this.code; } |
|---|
| 43 | 66 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 20 | 23 | import com.fasterxml.jackson.annotation.JsonProperty; |
|---|
| 21 | 24 | |
|---|
| 22 | 25 | /** |
|---|
| 23 | | - * Entity implementation class for Entity: settings settings is a table that has |
|---|
| 24 | | - * rows with 3 columns: "key", "value", "timestamp" |
|---|
| 25 | | - * |
|---|
| 26 | | - */ |
|---|
| 27 | | -@Entity() |
|---|
| 28 | | -@EntityListeners({ |
|---|
| 29 | | - ModificationTimestampListener.class |
|---|
| 30 | | -}) |
|---|
| 26 | +* Settings |
|---|
| 27 | +* <p> |
|---|
| 28 | +* Simple key/value store with last modification timestamp. |
|---|
| 29 | +* Table rows have columns: "key", "value", "modification_timestamp". |
|---|
| 30 | +* |
|---|
| 31 | +* Mapping details: |
|---|
| 32 | +* - Table: settings |
|---|
| 33 | +* - Listeners: {@link ModificationTimestampListener} |
|---|
| 34 | +* - NamedQuery: get-param by key |
|---|
| 35 | +* |
|---|
| 36 | +* @author JRA |
|---|
| 37 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 38 | +*/ |
|---|
| 39 | +@Entity |
|---|
| 40 | +@EntityListeners({ ModificationTimestampListener.class }) |
|---|
| 31 | 41 | @Table(name = "settings") |
|---|
| 32 | 42 | @NamedQueries({ |
|---|
| 33 | 43 | @NamedQuery(name = "get-param", query = "SELECT p FROM Settings p where p.key = :key") |
|---|
| 34 | 44 | }) |
|---|
| 35 | 45 | public class Settings implements ModificationTimestampEntity, Serializable { |
|---|
| 46 | + |
|---|
| 36 | 47 | @SuppressWarnings("unused") |
|---|
| 37 | 48 | private static final Logger LOG = LogManager.getLogger(Settings.class); |
|---|
| 38 | 49 | |
|---|
| 39 | 50 | private static final long serialVersionUID = 1L; |
|---|
| 40 | 51 | |
|---|
| 52 | + /** Primary key: setting key. */ |
|---|
| 41 | 53 | @Id |
|---|
| 42 | 54 | String key; |
|---|
| 43 | 55 | |
|---|
| 56 | + /** Setting value as string. */ |
|---|
| 44 | 57 | String value; |
|---|
| 45 | 58 | |
|---|
| 59 | + /** Last modification timestamp. */ |
|---|
| 46 | 60 | @Column(name = "modification_timestamp") |
|---|
| 47 | 61 | @JsonProperty("modification_timestamp") |
|---|
| 48 | 62 | private Date modificationTimestamp; |
|---|
| 49 | 63 | |
|---|
| 50 | | - public String getKey() { |
|---|
| 51 | | - return key; |
|---|
| 52 | | - } |
|---|
| 64 | + // -------- Getters/setters -------- |
|---|
| 53 | 65 | |
|---|
| 54 | | - public void setKey(String key) { |
|---|
| 55 | | - this.key = key; |
|---|
| 56 | | - } |
|---|
| 66 | + /** |
|---|
| 67 | + * getKey<p> |
|---|
| 68 | + * Return setting key. |
|---|
| 69 | + * |
|---|
| 70 | + * @return key |
|---|
| 71 | + */ |
|---|
| 72 | + public String getKey() { return key; } |
|---|
| 57 | 73 | |
|---|
| 58 | | - public String getValue() { |
|---|
| 59 | | - return value; |
|---|
| 60 | | - } |
|---|
| 74 | + /** |
|---|
| 75 | + * setKey<p> |
|---|
| 76 | + * Set setting key. |
|---|
| 77 | + * |
|---|
| 78 | + * @param key |
|---|
| 79 | + */ |
|---|
| 80 | + public void setKey(String key) { this.key = key; } |
|---|
| 61 | 81 | |
|---|
| 62 | | - public void setValue(String value) { |
|---|
| 63 | | - this.value = value; |
|---|
| 64 | | - } |
|---|
| 82 | + /** |
|---|
| 83 | + * getValue<p> |
|---|
| 84 | + * Return value. |
|---|
| 85 | + * |
|---|
| 86 | + * @return value |
|---|
| 87 | + */ |
|---|
| 88 | + public String getValue() { return value; } |
|---|
| 65 | 89 | |
|---|
| 90 | + /** |
|---|
| 91 | + * setValue<p> |
|---|
| 92 | + * Set value. |
|---|
| 93 | + * |
|---|
| 94 | + * @param value |
|---|
| 95 | + */ |
|---|
| 96 | + public void setValue(String value) { this.value = value; } |
|---|
| 97 | + |
|---|
| 98 | + /** |
|---|
| 99 | + * getModificationTimestamp<p> |
|---|
| 100 | + * Required by ModificationTimestampEntity. |
|---|
| 101 | + * |
|---|
| 102 | + * @return modificationTimestamp |
|---|
| 103 | + */ |
|---|
| 66 | 104 | @Override |
|---|
| 67 | | - public Date getModificationTimestamp() { |
|---|
| 68 | | - return modificationTimestamp; |
|---|
| 69 | | - } |
|---|
| 105 | + public Date getModificationTimestamp() { return modificationTimestamp; } |
|---|
| 70 | 106 | |
|---|
| 107 | + /** |
|---|
| 108 | + * setModificationTimestamp<p> |
|---|
| 109 | + * Required by ModificationTimestampEntity. |
|---|
| 110 | + * |
|---|
| 111 | + * @param modificationTimestamp |
|---|
| 112 | + */ |
|---|
| 71 | 113 | @Override |
|---|
| 72 | | - public void setModificationTimestamp(Date modificationTimestamp) { |
|---|
| 73 | | - this.modificationTimestamp = modificationTimestamp; |
|---|
| 74 | | - } |
|---|
| 114 | + public void setModificationTimestamp(Date modificationTimestamp) { this.modificationTimestamp = modificationTimestamp; } |
|---|
| 75 | 115 | |
|---|
| 116 | + /** |
|---|
| 117 | + * toString<p> |
|---|
| 118 | + * Get the string describing the current object |
|---|
| 119 | + * |
|---|
| 120 | + * @return object string |
|---|
| 121 | + */ |
|---|
| 76 | 122 | @Override |
|---|
| 77 | | - public String toString() { |
|---|
| 78 | | - |
|---|
| 79 | | - return String.format("{key: %s, value: %s, ts: %s}", key, value, modificationTimestamp); |
|---|
| 80 | | - } |
|---|
| 81 | | - |
|---|
| 123 | + public String toString() { return String.format("{key: %s, value: %s, ts: %s}", key, value, modificationTimestamp); } |
|---|
| 82 | 124 | } |
|---|
| 125 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 27 | 30 | import com.fasterxml.jackson.annotation.JsonProperty; |
|---|
| 28 | 31 | |
|---|
| 29 | 32 | /** |
|---|
| 30 | | - * Entity implementation class for Entity: Users |
|---|
| 31 | | - * |
|---|
| 32 | | - */ |
|---|
| 33 | +* User |
|---|
| 34 | +* <p> |
|---|
| 35 | +* Application user with bitmask-based roles and membership in organizations |
|---|
| 36 | +* and applications. Exposes convenience JSON properties to fetch/set related |
|---|
| 37 | +* entity IDs without fetching full entities. |
|---|
| 38 | +* |
|---|
| 39 | +* Mapping details: |
|---|
| 40 | +* - Table: user |
|---|
| 41 | +* - ManyToMany organizations via user_organization |
|---|
| 42 | +* - ManyToMany applications via user_application |
|---|
| 43 | +* - Named queries: list-users, get-user, auth-user, delete-all-users |
|---|
| 44 | +* |
|---|
| 45 | +* Roles: |
|---|
| 46 | +* - Stored as integer bitmask; see {@link Rol}. |
|---|
| 47 | +* |
|---|
| 48 | +* @author JRA |
|---|
| 49 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 50 | +*/ |
|---|
| 33 | 51 | @JsonAutoDetect |
|---|
| 34 | 52 | @JsonInclude(Include.NON_NULL) |
|---|
| 35 | 53 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| 36 | 54 | @Entity |
|---|
| 37 | 55 | @Table(name = "user") |
|---|
| 38 | | -@NamedQueries({ @NamedQuery(name = "list-users", query = "SELECT u FROM User u"), @NamedQuery(name = "get-user", query = "SELECT u FROM User u where u.username = :username"), |
|---|
| 39 | | - @NamedQuery(name = "auth-user", query = "SELECT u FROM User u where u.username = :username and u.password = :password"), |
|---|
| 40 | | - @NamedQuery(name = "delete-all-users", query = "delete FROM User u") }) |
|---|
| 56 | +@NamedQueries({ |
|---|
| 57 | + @NamedQuery(name = "list-users", query = "SELECT u FROM User u"), |
|---|
| 58 | + @NamedQuery(name = "get-user", query = "SELECT u FROM User u where u.username = :username"), |
|---|
| 59 | + @NamedQuery(name = "auth-user", query = "SELECT u FROM User u where u.username = :username and u.password = :password"), |
|---|
| 60 | + @NamedQuery(name = "delete-all-users", query = "delete FROM User u") |
|---|
| 61 | +}) |
|---|
| 41 | 62 | public class User implements Serializable { |
|---|
| 42 | 63 | |
|---|
| 43 | | - private static final long serialVersionUID = 1L; |
|---|
| 64 | + private static final long serialVersionUID = 1L; |
|---|
| 44 | 65 | |
|---|
| 45 | | - @Id |
|---|
| 46 | | - private String username; |
|---|
| 66 | + /** Username (PK). */ |
|---|
| 67 | + @Id |
|---|
| 68 | + private String username; |
|---|
| 47 | 69 | |
|---|
| 48 | | - private String password; |
|---|
| 70 | + /** Password hash/string (not exposed in JSON). */ |
|---|
| 71 | + private String password; |
|---|
| 49 | 72 | |
|---|
| 50 | | - @JsonProperty(value = "first_name") |
|---|
| 51 | | - @Column(name = "first_name") |
|---|
| 52 | | - private String firstName; |
|---|
| 73 | + @JsonProperty("first_name") |
|---|
| 74 | + @Column(name = "first_name") |
|---|
| 75 | + private String firstName; |
|---|
| 53 | 76 | |
|---|
| 54 | | - @JsonProperty(value = "last_name") |
|---|
| 55 | | - @Column(name = "last_name") |
|---|
| 56 | | - private String lastName; |
|---|
| 77 | + @JsonProperty("last_name") |
|---|
| 78 | + @Column(name = "last_name") |
|---|
| 79 | + private String lastName; |
|---|
| 57 | 80 | |
|---|
| 58 | | - private int roles; |
|---|
| 81 | + /** Roles bitmask (see Rol constants). */ |
|---|
| 82 | + private int roles; |
|---|
| 59 | 83 | |
|---|
| 60 | | - @Column(name = "last_login") |
|---|
| 61 | | - private Date lastLogin; |
|---|
| 84 | + @Column(name = "last_login") |
|---|
| 85 | + private Date lastLogin; |
|---|
| 62 | 86 | |
|---|
| 63 | | - @Column(name = "modification_timestamp") |
|---|
| 64 | | - private Date modificationTimestamp; |
|---|
| 87 | + @Column(name = "modification_timestamp") |
|---|
| 88 | + private Date modificationTimestamp; |
|---|
| 65 | 89 | |
|---|
| 66 | | - @Column(name = "creation_timestamp") |
|---|
| 67 | | - @JsonProperty("creation_timestamp") |
|---|
| 68 | | - private Date creationTimestamp; |
|---|
| 90 | + @Column(name = "creation_timestamp") |
|---|
| 91 | + @JsonProperty("creation_timestamp") |
|---|
| 92 | + private Date creationTimestamp; |
|---|
| 69 | 93 | |
|---|
| 70 | | - private String lang; |
|---|
| 94 | + private String lang; |
|---|
| 95 | + private String email; |
|---|
| 71 | 96 | |
|---|
| 72 | | - private String email; |
|---|
| 97 | + @JsonIgnore |
|---|
| 98 | + @ManyToMany |
|---|
| 99 | + @JoinTable(name = "user_organization", |
|---|
| 100 | + joinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") }, |
|---|
| 101 | + inverseJoinColumns = { @JoinColumn(name = "organization_id", referencedColumnName = "id") }) |
|---|
| 102 | + private Set<Organization> organizations; |
|---|
| 73 | 103 | |
|---|
| 74 | | - @JsonIgnore |
|---|
| 75 | | - @ManyToMany |
|---|
| 76 | | - @JoinTable(name = "user_organization", // |
|---|
| 77 | | - joinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") }, // |
|---|
| 78 | | - inverseJoinColumns = { @JoinColumn(name = "organization_id", referencedColumnName = "id") } // |
|---|
| 79 | | - ) |
|---|
| 80 | | - private Set<Organization> organizations; |
|---|
| 104 | + @JsonIgnore |
|---|
| 105 | + @ManyToMany |
|---|
| 106 | + @JoinTable(name = "user_application", |
|---|
| 107 | + joinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") }, |
|---|
| 108 | + inverseJoinColumns = { @JoinColumn(name = "application_id", referencedColumnName = "id") }) |
|---|
| 109 | + private Set<Application> applications; |
|---|
| 81 | 110 | |
|---|
| 82 | | - @JsonIgnore |
|---|
| 83 | | - @ManyToMany |
|---|
| 84 | | - @JoinTable(name = "user_application", // |
|---|
| 85 | | - joinColumns = { @JoinColumn(name = "username", referencedColumnName = "username") }, // |
|---|
| 86 | | - inverseJoinColumns = { @JoinColumn(name = "application_id", referencedColumnName = "id") } // |
|---|
| 87 | | - ) |
|---|
| 88 | | - private Set<Application> applications; |
|---|
| 111 | + // -------- Getters & setters -------- |
|---|
| 89 | 112 | |
|---|
| 90 | | - public String getUsername() { |
|---|
| 91 | | - return username; |
|---|
| 92 | | - } |
|---|
| 113 | + /** |
|---|
| 114 | + * getUsername<p> |
|---|
| 115 | + * Return username (PK). |
|---|
| 116 | + * |
|---|
| 117 | + * @return username |
|---|
| 118 | + */ |
|---|
| 119 | + public String getUsername() { return username; } |
|---|
| 93 | 120 | |
|---|
| 94 | | - public void setUsername(String username) { |
|---|
| 95 | | - this.username = username; |
|---|
| 96 | | - } |
|---|
| 121 | + /** |
|---|
| 122 | + * setUsername<p> |
|---|
| 123 | + * Set username (PK). |
|---|
| 124 | + * |
|---|
| 125 | + * @param username |
|---|
| 126 | + */ |
|---|
| 127 | + public void setUsername(String username) { this.username = username; } |
|---|
| 97 | 128 | |
|---|
| 98 | | - @JsonProperty("password") |
|---|
| 99 | | - public String getDummyPassword() { |
|---|
| 100 | | - return null; |
|---|
| 101 | | - } |
|---|
| 129 | + /** |
|---|
| 130 | + * getDummyPassword<p> |
|---|
| 131 | + * Forces password to be omitted in JSON responses. |
|---|
| 132 | + * |
|---|
| 133 | + * @return always null |
|---|
| 134 | + */ |
|---|
| 135 | + @JsonProperty("password") |
|---|
| 136 | + public String getDummyPassword() { return null; } |
|---|
| 102 | 137 | |
|---|
| 103 | | - public String getPassword() { |
|---|
| 104 | | - return password; |
|---|
| 105 | | - } |
|---|
| 138 | + /** |
|---|
| 139 | + * getPassword<p> |
|---|
| 140 | + * Return raw/hashed password (internal use). |
|---|
| 141 | + * |
|---|
| 142 | + * @return password |
|---|
| 143 | + */ |
|---|
| 144 | + public String getPassword() { return password; } |
|---|
| 106 | 145 | |
|---|
| 107 | | - public void setPassword(String password) { |
|---|
| 108 | | - this.password = password; |
|---|
| 109 | | - } |
|---|
| 146 | + /** |
|---|
| 147 | + * setPassword<p> |
|---|
| 148 | + * Set raw/hashed password (internal use). |
|---|
| 149 | + * |
|---|
| 150 | + * @param password |
|---|
| 151 | + */ |
|---|
| 152 | + public void setPassword(String password) { this.password = password; } |
|---|
| 110 | 153 | |
|---|
| 111 | | - public List<Integer> getRoles() { |
|---|
| 112 | | - if (roles == 0) { |
|---|
| 113 | | - return null; |
|---|
| 114 | | - } |
|---|
| 115 | | - List<Integer> aux = new ArrayList<>(); |
|---|
| 116 | | - for (int rol : Rol.ALL) { |
|---|
| 117 | | - if ((roles & rol) != 0) { // Each rol is a number with only 1 bit == |
|---|
| 118 | | - // 1 in binary representation |
|---|
| 119 | | - aux.add(rol); |
|---|
| 120 | | - } |
|---|
| 121 | | - } |
|---|
| 122 | | - return aux; |
|---|
| 123 | | - } |
|---|
| 154 | + /** |
|---|
| 155 | + * getRoles<p> |
|---|
| 156 | + * Return list of individual role flags contained in the bitmask. |
|---|
| 157 | + * |
|---|
| 158 | + * @return list of role integers or null if no roles |
|---|
| 159 | + */ |
|---|
| 160 | + public List<Integer> getRoles() { |
|---|
| 161 | + if (roles == 0) return null; |
|---|
| 162 | + List<Integer> aux = new ArrayList<>(); |
|---|
| 163 | + for (int rol : Rol.ALL) { |
|---|
| 164 | + if ((roles & rol) != 0) aux.add(rol); |
|---|
| 165 | + } |
|---|
| 166 | + return aux; |
|---|
| 167 | + } |
|---|
| 124 | 168 | |
|---|
| 125 | | - public void setRoles(List<Integer> roles) { |
|---|
| 126 | | - this.roles = 0; |
|---|
| 127 | | - if (roles != null) { |
|---|
| 128 | | - for (Integer rol : roles) { |
|---|
| 129 | | - this.roles |= rol; |
|---|
| 130 | | - } |
|---|
| 131 | | - } |
|---|
| 132 | | - } |
|---|
| 169 | + /** |
|---|
| 170 | + * setRoles<p> |
|---|
| 171 | + * Set the roles bitmask from a list of role flags. |
|---|
| 172 | + * |
|---|
| 173 | + * @param roles list of flags |
|---|
| 174 | + */ |
|---|
| 175 | + public void setRoles(List<Integer> roles) { |
|---|
| 176 | + this.roles = 0; |
|---|
| 177 | + if (roles != null) { |
|---|
| 178 | + for (Integer rol : roles) this.roles |= rol; |
|---|
| 179 | + } |
|---|
| 180 | + } |
|---|
| 133 | 181 | |
|---|
| 134 | | - public String getFirstName() { |
|---|
| 135 | | - return firstName; |
|---|
| 136 | | - } |
|---|
| 182 | + /** |
|---|
| 183 | + * getFirstName<p> |
|---|
| 184 | + * Return first name. |
|---|
| 185 | + * |
|---|
| 186 | + * @return firstName |
|---|
| 187 | + */ |
|---|
| 188 | + public String getFirstName() { return firstName; } |
|---|
| 137 | 189 | |
|---|
| 138 | | - public void setFirstName(String firstName) { |
|---|
| 139 | | - this.firstName = firstName; |
|---|
| 140 | | - } |
|---|
| 190 | + /** |
|---|
| 191 | + * setFirstName<p> |
|---|
| 192 | + * Set first name. |
|---|
| 193 | + * |
|---|
| 194 | + * @param firstName |
|---|
| 195 | + */ |
|---|
| 196 | + public void setFirstName(String firstName) { this.firstName = firstName; } |
|---|
| 141 | 197 | |
|---|
| 142 | | - public String getLastName() { |
|---|
| 143 | | - return lastName; |
|---|
| 144 | | - } |
|---|
| 198 | + /** |
|---|
| 199 | + * getLastName<p> |
|---|
| 200 | + * Return last name. |
|---|
| 201 | + * |
|---|
| 202 | + * @return lastName |
|---|
| 203 | + */ |
|---|
| 204 | + public String getLastName() { return lastName; } |
|---|
| 145 | 205 | |
|---|
| 146 | | - public void setLastName(String lastName) { |
|---|
| 147 | | - this.lastName = lastName; |
|---|
| 148 | | - } |
|---|
| 206 | + /** |
|---|
| 207 | + * setLastName<p> |
|---|
| 208 | + * Set last name. |
|---|
| 209 | + * |
|---|
| 210 | + * @param lastName |
|---|
| 211 | + */ |
|---|
| 212 | + public void setLastName(String lastName) { this.lastName = lastName; } |
|---|
| 149 | 213 | |
|---|
| 150 | | - public Date getLastLogin() { |
|---|
| 151 | | - return lastLogin; |
|---|
| 152 | | - } |
|---|
| 214 | + /** |
|---|
| 215 | + * getLastLogin<p> |
|---|
| 216 | + * Return last login timestamp. |
|---|
| 217 | + * |
|---|
| 218 | + * @return lastLogin |
|---|
| 219 | + */ |
|---|
| 220 | + public Date getLastLogin() { return lastLogin; } |
|---|
| 153 | 221 | |
|---|
| 154 | | - public void setLastLogin(Date lastLogin) { |
|---|
| 155 | | - this.lastLogin = lastLogin; |
|---|
| 156 | | - } |
|---|
| 222 | + /** |
|---|
| 223 | + * setLastLogin<p> |
|---|
| 224 | + * Set last login timestamp. |
|---|
| 225 | + * |
|---|
| 226 | + * @param lastLogin |
|---|
| 227 | + */ |
|---|
| 228 | + public void setLastLogin(Date lastLogin) { this.lastLogin = lastLogin; } |
|---|
| 157 | 229 | |
|---|
| 158 | | - public Date getModificationTimestamp() { |
|---|
| 159 | | - return modificationTimestamp; |
|---|
| 160 | | - } |
|---|
| 230 | + /** |
|---|
| 231 | + * getModificationTimestamp<p> |
|---|
| 232 | + * Return modification timestamp. |
|---|
| 233 | + * |
|---|
| 234 | + * @return modificationTimestamp |
|---|
| 235 | + */ |
|---|
| 236 | + public Date getModificationTimestamp() { return modificationTimestamp; } |
|---|
| 161 | 237 | |
|---|
| 162 | | - public void setModificationTimestamp(Date modificationTimestamp) { |
|---|
| 163 | | - this.modificationTimestamp = modificationTimestamp; |
|---|
| 164 | | - } |
|---|
| 238 | + /** |
|---|
| 239 | + * setModificationTimestamp<p> |
|---|
| 240 | + * Set modification timestamp. |
|---|
| 241 | + * |
|---|
| 242 | + * @param modificationTimestamp |
|---|
| 243 | + */ |
|---|
| 244 | + public void setModificationTimestamp(Date modificationTimestamp) { this.modificationTimestamp = modificationTimestamp; } |
|---|
| 165 | 245 | |
|---|
| 166 | | - public Date getCreationTimestamp() { |
|---|
| 167 | | - return creationTimestamp; |
|---|
| 168 | | - } |
|---|
| 246 | + /** |
|---|
| 247 | + * getCreationTimestamp<p> |
|---|
| 248 | + * Return creation timestamp. |
|---|
| 249 | + * |
|---|
| 250 | + * @return creationTimestamp |
|---|
| 251 | + */ |
|---|
| 252 | + public Date getCreationTimestamp() { return creationTimestamp; } |
|---|
| 169 | 253 | |
|---|
| 170 | | - public void setCreationTimestamp(Date creationTimestamp) { |
|---|
| 171 | | - this.creationTimestamp = creationTimestamp; |
|---|
| 172 | | - } |
|---|
| 254 | + /** |
|---|
| 255 | + * setCreationTimestamp<p> |
|---|
| 256 | + * Set creation timestamp. |
|---|
| 257 | + * |
|---|
| 258 | + * @param creationTimestamp |
|---|
| 259 | + */ |
|---|
| 260 | + public void setCreationTimestamp(Date creationTimestamp) { this.creationTimestamp = creationTimestamp; } |
|---|
| 173 | 261 | |
|---|
| 174 | | - @Override |
|---|
| 175 | | - public String toString() { |
|---|
| 176 | | - return "{User: " + username + " Name: " + firstName + " " + lastName + ", last login: " + lastLogin + "}"; |
|---|
| 177 | | - } |
|---|
| 262 | + /** |
|---|
| 263 | + * getLang<p> |
|---|
| 264 | + * Return preferred language. |
|---|
| 265 | + * |
|---|
| 266 | + * @return lang |
|---|
| 267 | + */ |
|---|
| 268 | + public String getLang() { return lang; } |
|---|
| 178 | 269 | |
|---|
| 179 | | - public String getLang() { |
|---|
| 180 | | - return lang; |
|---|
| 181 | | - } |
|---|
| 270 | + /** |
|---|
| 271 | + * setLang<p> |
|---|
| 272 | + * Set preferred language. |
|---|
| 273 | + * |
|---|
| 274 | + * @param lang |
|---|
| 275 | + */ |
|---|
| 276 | + public void setLang(String lang) { this.lang = lang; } |
|---|
| 182 | 277 | |
|---|
| 183 | | - public void setLang(String lang) { |
|---|
| 184 | | - this.lang = lang; |
|---|
| 185 | | - } |
|---|
| 278 | + /** |
|---|
| 279 | + * getEmail<p> |
|---|
| 280 | + * Return email address. |
|---|
| 281 | + * |
|---|
| 282 | + * @return email |
|---|
| 283 | + */ |
|---|
| 284 | + public String getEmail() { return email; } |
|---|
| 186 | 285 | |
|---|
| 187 | | - public Set<Organization> getOrganizations() { |
|---|
| 188 | | - return organizations; |
|---|
| 189 | | - } |
|---|
| 286 | + /** |
|---|
| 287 | + * setEmail<p> |
|---|
| 288 | + * Set email address. |
|---|
| 289 | + * |
|---|
| 290 | + * @param email |
|---|
| 291 | + */ |
|---|
| 292 | + public void setEmail(String email) { this.email = email; } |
|---|
| 190 | 293 | |
|---|
| 191 | | - public void setOrganizations(Set<Organization> organizations) { |
|---|
| 192 | | - this.organizations = organizations; |
|---|
| 193 | | - } |
|---|
| 294 | + /** |
|---|
| 295 | + * getOrganizations<p> |
|---|
| 296 | + * Return organizations (entity set). |
|---|
| 297 | + * |
|---|
| 298 | + * @return organizations |
|---|
| 299 | + */ |
|---|
| 300 | + public Set<Organization> getOrganizations() { return organizations; } |
|---|
| 194 | 301 | |
|---|
| 195 | | - public Set<Application> getApplications() { |
|---|
| 196 | | - return applications; |
|---|
| 197 | | - } |
|---|
| 302 | + /** |
|---|
| 303 | + * setOrganizations<p> |
|---|
| 304 | + * Set organizations (entity set). |
|---|
| 305 | + * |
|---|
| 306 | + * @param organizations |
|---|
| 307 | + */ |
|---|
| 308 | + public void setOrganizations(Set<Organization> organizations) { this.organizations = organizations; } |
|---|
| 198 | 309 | |
|---|
| 199 | | - public void setApplications(Set<Application> applications) { |
|---|
| 200 | | - this.applications = applications; |
|---|
| 201 | | - } |
|---|
| 310 | + /** |
|---|
| 311 | + * getApplications<p> |
|---|
| 312 | + * Return applications (entity set). |
|---|
| 313 | + * |
|---|
| 314 | + * @return applications |
|---|
| 315 | + */ |
|---|
| 316 | + public Set<Application> getApplications() { return applications; } |
|---|
| 202 | 317 | |
|---|
| 203 | | - @JsonProperty("organizations_ids") |
|---|
| 204 | | - public void setOrgsIds(List<Integer> orgsIds) { |
|---|
| 205 | | - organizations = new HashSet<>(); |
|---|
| 206 | | - for (Integer orgid : orgsIds) { |
|---|
| 207 | | - Organization o = new Organization(); |
|---|
| 208 | | - o.setId(orgid); |
|---|
| 209 | | - organizations.add(o); |
|---|
| 210 | | - } |
|---|
| 211 | | - } |
|---|
| 318 | + /** |
|---|
| 319 | + * setApplications<p> |
|---|
| 320 | + * Set applications (entity set). |
|---|
| 321 | + * |
|---|
| 322 | + * @param applications |
|---|
| 323 | + */ |
|---|
| 324 | + public void setApplications(Set<Application> applications) { this.applications = applications; } |
|---|
| 212 | 325 | |
|---|
| 213 | | - @JsonProperty("organizations_ids") |
|---|
| 214 | | - public Set<Integer> getOrgsIds() { |
|---|
| 215 | | - if (organizations == null) { |
|---|
| 216 | | - return null; |
|---|
| 217 | | - } |
|---|
| 218 | | - Set<Integer> ids = new HashSet<>(); |
|---|
| 219 | | - for (Organization org : organizations) { |
|---|
| 220 | | - ids.add(org.getId()); |
|---|
| 221 | | - } |
|---|
| 222 | | - return ids; |
|---|
| 223 | | - } |
|---|
| 326 | + // -------- JSON helpers for related IDs -------- |
|---|
| 224 | 327 | |
|---|
| 225 | | - @JsonProperty("applications_ids") |
|---|
| 226 | | - public void setAppsIds(Collection<Integer> appIds) { |
|---|
| 227 | | - applications = new HashSet<>(); |
|---|
| 228 | | - for (Integer appid : appIds) { |
|---|
| 229 | | - Application a = new Application(); |
|---|
| 230 | | - a.setId(appid); |
|---|
| 231 | | - applications.add(a); |
|---|
| 232 | | - } |
|---|
| 233 | | - } |
|---|
| 328 | + /** |
|---|
| 329 | + * setOrgsIds<p> |
|---|
| 330 | + * Replace organizations from a list of org IDs. |
|---|
| 331 | + * |
|---|
| 332 | + * @param orgsIds |
|---|
| 333 | + */ |
|---|
| 334 | + @JsonProperty("organizations_ids") |
|---|
| 335 | + public void setOrgsIds(List<Integer> orgsIds) { |
|---|
| 336 | + organizations = new HashSet<>(); |
|---|
| 337 | + for (Integer orgid : orgsIds) { |
|---|
| 338 | + Organization o = new Organization(); |
|---|
| 339 | + o.setId(orgid); |
|---|
| 340 | + organizations.add(o); |
|---|
| 341 | + } |
|---|
| 342 | + } |
|---|
| 234 | 343 | |
|---|
| 235 | | - @JsonProperty("applications_ids") |
|---|
| 236 | | - public Set<Integer> getAppsIds() { |
|---|
| 237 | | - if (applications == null) { |
|---|
| 238 | | - return null; |
|---|
| 239 | | - } |
|---|
| 240 | | - Set<Integer> ids = new HashSet<>(); |
|---|
| 241 | | - for (Application app : applications) { |
|---|
| 242 | | - ids.add(app.getId()); |
|---|
| 243 | | - } |
|---|
| 244 | | - return ids; |
|---|
| 245 | | - } |
|---|
| 344 | + /** |
|---|
| 345 | + * getOrgsIds<p> |
|---|
| 346 | + * Expose organization IDs. |
|---|
| 347 | + * |
|---|
| 348 | + * @return orgsIds |
|---|
| 349 | + */ |
|---|
| 350 | + @JsonProperty("organizations_ids") |
|---|
| 351 | + public Set<Integer> getOrgsIds() { |
|---|
| 352 | + if (organizations == null) return null; |
|---|
| 353 | + Set<Integer> ids = new HashSet<>(); |
|---|
| 354 | + for (Organization org : organizations) ids.add(org.getId()); |
|---|
| 355 | + return ids; |
|---|
| 356 | + } |
|---|
| 246 | 357 | |
|---|
| 247 | | - @JsonIgnore |
|---|
| 248 | | - public Set<Integer> getAllOrgsIds() { |
|---|
| 249 | | - if (organizations == null) { |
|---|
| 250 | | - return null; |
|---|
| 251 | | - } |
|---|
| 252 | | - Set<Integer> ids = new HashSet<>(); |
|---|
| 253 | | - includeAllOrgs(this.organizations, ids); |
|---|
| 254 | | - return ids; |
|---|
| 255 | | - } |
|---|
| 358 | + /** |
|---|
| 359 | + * setAppsIds<p> |
|---|
| 360 | + * Replace applications from a collection of app IDs. |
|---|
| 361 | + * |
|---|
| 362 | + * @param appIds |
|---|
| 363 | + */ |
|---|
| 364 | + @JsonProperty("applications_ids") |
|---|
| 365 | + public void setAppsIds(Collection<Integer> appIds) { |
|---|
| 366 | + applications = new HashSet<>(); |
|---|
| 367 | + for (Integer appid : appIds) { |
|---|
| 368 | + Application a = new Application(); |
|---|
| 369 | + a.setId(appid); |
|---|
| 370 | + applications.add(a); |
|---|
| 371 | + } |
|---|
| 372 | + } |
|---|
| 256 | 373 | |
|---|
| 257 | | - @JsonIgnore |
|---|
| 258 | | - public Set<Integer> getAllAppsIds() { |
|---|
| 259 | | - if (applications == null) { |
|---|
| 260 | | - return null; |
|---|
| 261 | | - } |
|---|
| 262 | | - Set<Integer> ids = this.applications.parallelStream().map(app -> app.getId()).collect(Collectors.toSet()); |
|---|
| 374 | + /** |
|---|
| 375 | + * getAppsIds<p> |
|---|
| 376 | + * Expose application IDs. |
|---|
| 377 | + * |
|---|
| 378 | + * @return appsIds |
|---|
| 379 | + */ |
|---|
| 380 | + @JsonProperty("applications_ids") |
|---|
| 381 | + public Set<Integer> getAppsIds() { |
|---|
| 382 | + if (applications == null) return null; |
|---|
| 383 | + Set<Integer> ids = new HashSet<>(); |
|---|
| 384 | + for (Application app : applications) ids.add(app.getId()); |
|---|
| 385 | + return ids; |
|---|
| 386 | + } |
|---|
| 263 | 387 | |
|---|
| 264 | | - return ids; |
|---|
| 265 | | - } |
|---|
| 388 | + // -------- Derived scopes -------- |
|---|
| 266 | 389 | |
|---|
| 267 | | - /** |
|---|
| 268 | | - * Walk into the organization hierarchy to include all descendants |
|---|
| 269 | | - * |
|---|
| 270 | | - * @param list |
|---|
| 271 | | - * @param orgIds |
|---|
| 272 | | - */ |
|---|
| 273 | | - private void includeAllOrgs(Set<Organization> list, Set<Integer> orgIds) { |
|---|
| 274 | | - for (Organization org : list) { |
|---|
| 275 | | - orgIds.add(org.getId()); |
|---|
| 276 | | - includeAllOrgs(org.getChildOrganizations(), orgIds); |
|---|
| 277 | | - } |
|---|
| 278 | | - } |
|---|
| 390 | + /** |
|---|
| 391 | + * getAllOrgsIds<p> |
|---|
| 392 | + * Compute full organization scope including descendants. |
|---|
| 393 | + * |
|---|
| 394 | + * @return set of org IDs (may be null when no organizations) |
|---|
| 395 | + */ |
|---|
| 396 | + @JsonIgnore |
|---|
| 397 | + public Set<Integer> getAllOrgsIds() { |
|---|
| 398 | + if (organizations == null) return null; |
|---|
| 399 | + Set<Integer> ids = new HashSet<>(); |
|---|
| 400 | + includeAllOrgs(this.organizations, ids); |
|---|
| 401 | + return ids; |
|---|
| 402 | + } |
|---|
| 279 | 403 | |
|---|
| 280 | | - public String getEmail() { |
|---|
| 281 | | - return email; |
|---|
| 282 | | - } |
|---|
| 404 | + /** |
|---|
| 405 | + * getAllAppsIds<p> |
|---|
| 406 | + * Compute application scope (direct associations only). |
|---|
| 407 | + * |
|---|
| 408 | + * @return set of application IDs (may be null when no applications) |
|---|
| 409 | + */ |
|---|
| 410 | + @JsonIgnore |
|---|
| 411 | + public Set<Integer> getAllAppsIds() { |
|---|
| 412 | + if (applications == null) return null; |
|---|
| 413 | + return this.applications.parallelStream().map(Application::getId).collect(Collectors.toSet()); |
|---|
| 414 | + } |
|---|
| 283 | 415 | |
|---|
| 284 | | - public void setEmail(String email) { |
|---|
| 285 | | - this.email = email; |
|---|
| 286 | | - } |
|---|
| 416 | + /** |
|---|
| 417 | + * includeAllOrgs<p> |
|---|
| 418 | + * Walk organization hierarchy to include all descendants. |
|---|
| 419 | + * |
|---|
| 420 | + * @param list current level orgs |
|---|
| 421 | + * @param orgIds accumulator of ids |
|---|
| 422 | + */ |
|---|
| 423 | + private void includeAllOrgs(Set<Organization> list, Set<Integer> orgIds) { |
|---|
| 424 | + for (Organization org : list) { |
|---|
| 425 | + orgIds.add(org.getId()); |
|---|
| 426 | + includeAllOrgs(org.getChildOrganizations(), orgIds); |
|---|
| 427 | + } |
|---|
| 428 | + } |
|---|
| 287 | 429 | |
|---|
| 288 | | - /** |
|---|
| 289 | | - * Numeric rol mask. Be aware to use different bit position for each role |
|---|
| 290 | | - * |
|---|
| 291 | | - * @author rob |
|---|
| 292 | | - */ |
|---|
| 293 | | - public static class Rol { |
|---|
| 294 | | - public static final int ADVANCE = 0x01; |
|---|
| 295 | | - public static final int ADMIN = 0x02; |
|---|
| 296 | | - public static final int BASIC = 0x04; |
|---|
| 297 | | - public static final int API_CLIENT = 0x80; |
|---|
| 298 | | - public static final int[] ALL = new int[] { ADVANCE, ADMIN, BASIC, API_CLIENT }; |
|---|
| 299 | | - } |
|---|
| 430 | + /** |
|---|
| 431 | + * toString<p> |
|---|
| 432 | + * Get the string describing the current object |
|---|
| 433 | + * |
|---|
| 434 | + * @return object string |
|---|
| 435 | + */ |
|---|
| 436 | + @Override |
|---|
| 437 | + public String toString() { |
|---|
| 438 | + return "{User: " + username + " Name: " + firstName + " " + lastName + ", last login: " + lastLogin + "}"; |
|---|
| 439 | + } |
|---|
| 300 | 440 | |
|---|
| 441 | + |
|---|
| 442 | + /** |
|---|
| 443 | + * Rol |
|---|
| 444 | + * <p> |
|---|
| 445 | + * Bitmask constants for user roles. Each constant must occupy a distinct bit. |
|---|
| 446 | + */ |
|---|
| 447 | + public static class Rol { |
|---|
| 448 | + public static final int ADVANCE = 0x01; |
|---|
| 449 | + public static final int ADMIN = 0x02; |
|---|
| 450 | + public static final int BASIC = 0x04; |
|---|
| 451 | + public static final int API_CLIENT= 0x80; |
|---|
| 452 | + public static final int[] ALL = new int[] { ADVANCE, ADMIN, BASIC, API_CLIENT }; |
|---|
| 453 | + } |
|---|
| 301 | 454 | } |
|---|
| 455 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db.common; |
|---|
| 2 | 5 | |
|---|
| 6 | +/** |
|---|
| 7 | +* CodedEnum |
|---|
| 8 | +* <p> |
|---|
| 9 | +* Small contract for enums persisted as short codes |
|---|
| 10 | +*/ |
|---|
| 3 | 11 | public interface CodedEnum { |
|---|
| 4 | 12 | |
|---|
| 5 | | - public String getCode(); |
|---|
| 13 | + /** |
|---|
| 14 | + * getCode<p> |
|---|
| 15 | + * Return the short string code for persistence/JSON. |
|---|
| 16 | + * |
|---|
| 17 | + * @return codeEnum |
|---|
| 18 | + */ |
|---|
| 19 | + String getCode(); |
|---|
| 6 | 20 | |
|---|
| 7 | 21 | } |
|---|
| 22 | + |
|---|
| 23 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db.common; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Date; |
|---|
| 4 | 7 | |
|---|
| 8 | +/** |
|---|
| 9 | +* CreationTimestampEntity |
|---|
| 10 | +* <p> |
|---|
| 11 | +* Contract for entities that expose a creation timestamp property. |
|---|
| 12 | +*/ |
|---|
| 5 | 13 | public interface CreationTimestampEntity { |
|---|
| 6 | 14 | |
|---|
| 7 | | - public Date getCreationTimestamp(); |
|---|
| 15 | + /** |
|---|
| 16 | + * getCreationTimestamp<p> |
|---|
| 17 | + * Return creation timestamp. |
|---|
| 18 | + * |
|---|
| 19 | + * @return creationTimeStamp |
|---|
| 20 | + */ |
|---|
| 21 | + Date getCreationTimestamp(); |
|---|
| 8 | 22 | |
|---|
| 9 | | - public void setCreationTimestamp(Date creationTimestamp); |
|---|
| 23 | + /** |
|---|
| 24 | + * setCreationTimestamp<p> |
|---|
| 25 | + * Set creation timestamp. |
|---|
| 26 | + * |
|---|
| 27 | + * @param creationTimestamp |
|---|
| 28 | + */ |
|---|
| 29 | + void setCreationTimestamp(Date creationTimestamp); |
|---|
| 10 | 30 | } |
|---|
| 31 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db.common; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import net.curisit.securis.db.LicenseStatus; |
|---|
| 4 | 7 | |
|---|
| 8 | +/** |
|---|
| 9 | +* LicenseStatusType |
|---|
| 10 | +* <p> |
|---|
| 11 | +* Hibernate user type that persists {@link LicenseStatus} using its code. |
|---|
| 12 | +* Delegates specifics to {@link PersistentEnumUserType}. |
|---|
| 13 | +* |
|---|
| 14 | +* @author JRA |
|---|
| 15 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 16 | +*/ |
|---|
| 5 | 17 | public class LicenseStatusType extends PersistentEnumUserType<LicenseStatus> { |
|---|
| 6 | 18 | |
|---|
| 19 | + /** |
|---|
| 20 | + * returnedClass<p> |
|---|
| 21 | + * Return the enum class handled by this type. |
|---|
| 22 | + * |
|---|
| 23 | + * @return licenseStatus |
|---|
| 24 | + */ |
|---|
| 7 | 25 | @Override |
|---|
| 8 | 26 | public Class<LicenseStatus> returnedClass() { |
|---|
| 9 | 27 | return LicenseStatus.class; |
|---|
| 10 | 28 | } |
|---|
| 11 | | - |
|---|
| 12 | 29 | } |
|---|
| 30 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db.common; |
|---|
| 2 | 5 | |
|---|
| 6 | +/** |
|---|
| 7 | +* Metadata |
|---|
| 8 | +* <p> |
|---|
| 9 | +* Contract for metadata entries (key/value/mandatory). |
|---|
| 10 | +* Implemented by ApplicationMetadata, LicenseTypeMetadata, PackMetadata, etc. |
|---|
| 11 | +* |
|---|
| 12 | +* @author JRA |
|---|
| 13 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 14 | +*/ |
|---|
| 3 | 15 | public interface Metadata { |
|---|
| 4 | | - public String getKey(); |
|---|
| 16 | + |
|---|
| 17 | + /** |
|---|
| 18 | + * getKey<p> |
|---|
| 19 | + * Return entry key. |
|---|
| 20 | + * |
|---|
| 21 | + * @return key |
|---|
| 22 | + */ |
|---|
| 23 | + String getKey(); |
|---|
| 24 | + |
|---|
| 25 | + /** |
|---|
| 26 | + * setKey<p> |
|---|
| 27 | + * Set entry key. |
|---|
| 28 | + * |
|---|
| 29 | + * @param key |
|---|
| 30 | + */ |
|---|
| 31 | + void setKey(String key); |
|---|
| 5 | 32 | |
|---|
| 6 | | - public void setKey(String key); |
|---|
| 33 | + /** |
|---|
| 34 | + * getValue<p> |
|---|
| 35 | + * Return entry value. |
|---|
| 36 | + * |
|---|
| 37 | + * @return value |
|---|
| 38 | + */ |
|---|
| 39 | + String getValue(); |
|---|
| 40 | + |
|---|
| 41 | + /** |
|---|
| 42 | + * setValue<p> |
|---|
| 43 | + * Set entry value. |
|---|
| 44 | + * |
|---|
| 45 | + * @param value |
|---|
| 46 | + */ |
|---|
| 47 | + void setValue(String value); |
|---|
| 7 | 48 | |
|---|
| 8 | | - public String getValue(); |
|---|
| 9 | | - |
|---|
| 10 | | - public void setValue(String value); |
|---|
| 11 | | - |
|---|
| 12 | | - public boolean isMandatory(); |
|---|
| 13 | | - |
|---|
| 14 | | - public void setMandatory(boolean mandatory); |
|---|
| 15 | | - |
|---|
| 49 | + /** |
|---|
| 50 | + * isMandatory<p> |
|---|
| 51 | + * Whether this metadata is required. |
|---|
| 52 | + * |
|---|
| 53 | + * @return isMandatory |
|---|
| 54 | + */ |
|---|
| 55 | + boolean isMandatory(); |
|---|
| 56 | + |
|---|
| 57 | + /** |
|---|
| 58 | + * setMandatory<p> |
|---|
| 59 | + * Set required flag. |
|---|
| 60 | + * |
|---|
| 61 | + * @param mandatory |
|---|
| 62 | + */ |
|---|
| 63 | + void setMandatory(boolean mandatory); |
|---|
| 16 | 64 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db.common; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Date; |
|---|
| 4 | 7 | |
|---|
| 8 | +/** |
|---|
| 9 | +* ModificationTimestampEntity |
|---|
| 10 | +* <p> |
|---|
| 11 | +* Contract for entities that track a <b>modification timestamp</b>. |
|---|
| 12 | +* Typical usage: attach {@code @EntityListeners(ModificationTimestampListener.class)} |
|---|
| 13 | +* so the value is updated automatically on persist/update. |
|---|
| 14 | +* |
|---|
| 15 | +* Methods: |
|---|
| 16 | +* - {@link #getModificationTimestamp()} exposes the timestamp. |
|---|
| 17 | +* - {@link #setModificationTimestamp(Date)} sets the timestamp. |
|---|
| 18 | +* |
|---|
| 19 | +* @author JRA |
|---|
| 20 | +* Last reviewed by JRA on Oct 7, 2025. |
|---|
| 21 | +*/ |
|---|
| 5 | 22 | public interface ModificationTimestampEntity { |
|---|
| 6 | 23 | |
|---|
| 7 | | - public Date getModificationTimestamp(); |
|---|
| 24 | + /** |
|---|
| 25 | + * getModificationTimestamp<p> |
|---|
| 26 | + * Return the last modification timestamp. |
|---|
| 27 | + * |
|---|
| 28 | + * @return modificationTimestamp |
|---|
| 29 | + */ |
|---|
| 30 | + Date getModificationTimestamp(); |
|---|
| 8 | 31 | |
|---|
| 9 | | - public void setModificationTimestamp(Date modificationTimestamp); |
|---|
| 32 | + /** |
|---|
| 33 | + * setModificationTimestamp<p> |
|---|
| 34 | + * Set/update the last modification timestamp. |
|---|
| 35 | + * |
|---|
| 36 | + * @param modificationTimestamp |
|---|
| 37 | + */ |
|---|
| 38 | + void setModificationTimestamp(Date modificationTimestamp); |
|---|
| 10 | 39 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db.common; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import net.curisit.securis.db.PackStatus; |
|---|
| 4 | 7 | |
|---|
| 8 | +/** |
|---|
| 9 | +* PackStatusType |
|---|
| 10 | +* <p> |
|---|
| 11 | +* Hibernate {@code UserType} for persisting {@link PackStatus} as its short code. |
|---|
| 12 | +* Delegates common enum-code mapping behavior to {@link PersistentEnumUserType}. |
|---|
| 13 | +* |
|---|
| 14 | +* @author JRA |
|---|
| 15 | +* Last reviewed by JRA on Oct 7, 2025. |
|---|
| 16 | +*/ |
|---|
| 5 | 17 | public class PackStatusType extends PersistentEnumUserType<PackStatus> { |
|---|
| 6 | 18 | |
|---|
| 19 | + /** |
|---|
| 20 | + * returnedClass<p> |
|---|
| 21 | + * Return enum class handled by this type. |
|---|
| 22 | + * |
|---|
| 23 | + * @return packStatus |
|---|
| 24 | + */ |
|---|
| 7 | 25 | @Override |
|---|
| 8 | 26 | public Class<PackStatus> returnedClass() { |
|---|
| 9 | 27 | return PackStatus.class; |
|---|
| 10 | 28 | } |
|---|
| 11 | | - |
|---|
| 12 | 29 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db.common; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.Serializable; |
|---|
| .. | .. |
|---|
| 10 | 13 | import org.hibernate.engine.spi.SharedSessionContractImplementor; |
|---|
| 11 | 14 | import org.hibernate.usertype.UserType; |
|---|
| 12 | 15 | |
|---|
| 16 | +/** |
|---|
| 17 | +* PersistentEnumUserType |
|---|
| 18 | +* <p> |
|---|
| 19 | +* Base Hibernate {@link UserType} for enums implementing {@link CodedEnum}. |
|---|
| 20 | +* Stores the enum's <b>short code</b> (VARCHAR) and reconstructs the enum |
|---|
| 21 | +* from that code on load. |
|---|
| 22 | +* |
|---|
| 23 | +* Notes: |
|---|
| 24 | +* - SQL type is {@code VARCHAR}. |
|---|
| 25 | +* - Immutable by default ({@link #isMutable()} returns false). |
|---|
| 26 | +* - {@link #equals(Object, Object)} compares by reference (adequate for enums). |
|---|
| 27 | +* |
|---|
| 28 | +* @param <T> enum type implementing {@link CodedEnum} |
|---|
| 29 | +* |
|---|
| 30 | +* @author JRA |
|---|
| 31 | +* Last reviewed by JRA on Oct 7, 2025. |
|---|
| 32 | +*/ |
|---|
| 13 | 33 | public abstract class PersistentEnumUserType<T extends CodedEnum> implements UserType { |
|---|
| 14 | 34 | |
|---|
| 35 | + /** |
|---|
| 36 | + * assemble<p> |
|---|
| 37 | + * Return cached value as-is (immutable semantics). |
|---|
| 38 | + * |
|---|
| 39 | + * @param cached |
|---|
| 40 | + * @param owner |
|---|
| 41 | + * @return assembleObject |
|---|
| 42 | + * @throws HibernateException |
|---|
| 43 | + */ |
|---|
| 15 | 44 | @Override |
|---|
| 16 | 45 | public Object assemble(Serializable cached, Object owner) throws HibernateException { |
|---|
| 17 | 46 | return cached; |
|---|
| 18 | 47 | } |
|---|
| 19 | 48 | |
|---|
| 49 | + /** |
|---|
| 50 | + * deepCopy<p> |
|---|
| 51 | + * Enums are immutable; return value as-is. |
|---|
| 52 | + * |
|---|
| 53 | + * @param value |
|---|
| 54 | + * @return deepCopy |
|---|
| 55 | + */ |
|---|
| 20 | 56 | @Override |
|---|
| 21 | 57 | public Object deepCopy(Object value) throws HibernateException { |
|---|
| 22 | 58 | return value; |
|---|
| 23 | 59 | } |
|---|
| 24 | 60 | |
|---|
| 61 | + /** |
|---|
| 62 | + * disassemble<p> |
|---|
| 63 | + * Return value for 2nd-level cache. |
|---|
| 64 | + * |
|---|
| 65 | + * @param value |
|---|
| 66 | + * @return disassembleObject |
|---|
| 67 | + * @throw HibernateException |
|---|
| 68 | + */ |
|---|
| 25 | 69 | @Override |
|---|
| 26 | 70 | public Serializable disassemble(Object value) throws HibernateException { |
|---|
| 27 | 71 | return (Serializable) value; |
|---|
| 28 | 72 | } |
|---|
| 29 | 73 | |
|---|
| 74 | + /** |
|---|
| 75 | + * equals<p> |
|---|
| 76 | + * Reference equality is fine for enums. |
|---|
| 77 | + * |
|---|
| 78 | + * @param x |
|---|
| 79 | + * @param y |
|---|
| 80 | + * @param isEqual |
|---|
| 81 | + * @throws HibernateException |
|---|
| 82 | + */ |
|---|
| 30 | 83 | @Override |
|---|
| 31 | 84 | public boolean equals(Object x, Object y) throws HibernateException { |
|---|
| 32 | 85 | return x == y; |
|---|
| 33 | 86 | } |
|---|
| 34 | 87 | |
|---|
| 88 | + /** |
|---|
| 89 | + * hashCode<p> |
|---|
| 90 | + * Delegate to value hashCode; 0 for null. |
|---|
| 91 | + * |
|---|
| 92 | + * @param object |
|---|
| 93 | + * @return hashCode |
|---|
| 94 | + * @throws HibernateException |
|---|
| 95 | + */ |
|---|
| 35 | 96 | @Override |
|---|
| 36 | 97 | public int hashCode(Object x) throws HibernateException { |
|---|
| 37 | 98 | return x == null ? 0 : x.hashCode(); |
|---|
| 38 | 99 | } |
|---|
| 39 | 100 | |
|---|
| 101 | + /** |
|---|
| 102 | + * isMutable<p> |
|---|
| 103 | + * Enums are immutable. |
|---|
| 104 | + * |
|---|
| 105 | + * @return isMutable |
|---|
| 106 | + */ |
|---|
| 40 | 107 | @Override |
|---|
| 41 | 108 | public boolean isMutable() { |
|---|
| 42 | 109 | return false; |
|---|
| 43 | 110 | } |
|---|
| 44 | 111 | |
|---|
| 112 | + /** |
|---|
| 113 | + * replace<p> |
|---|
| 114 | + * Immutable; return original. |
|---|
| 115 | + * |
|---|
| 116 | + * @param original |
|---|
| 117 | + * @param target |
|---|
| 118 | + * @param owner |
|---|
| 119 | + * @return object |
|---|
| 120 | + * @throws HibernateException |
|---|
| 121 | + */ |
|---|
| 45 | 122 | @Override |
|---|
| 46 | 123 | public Object replace(Object original, Object target, Object owner) throws HibernateException { |
|---|
| 47 | 124 | return original; |
|---|
| 48 | 125 | } |
|---|
| 49 | 126 | |
|---|
| 127 | + /** |
|---|
| 128 | + * returnedClass<p> |
|---|
| 129 | + * Concrete types must provide the enum class. |
|---|
| 130 | + */ |
|---|
| 50 | 131 | @Override |
|---|
| 51 | 132 | public abstract Class<T> returnedClass(); |
|---|
| 52 | 133 | |
|---|
| 134 | + /** |
|---|
| 135 | + * sqlTypes<p> |
|---|
| 136 | + * Persist as single VARCHAR column |
|---|
| 137 | + * |
|---|
| 138 | + * @return sqlTypes |
|---|
| 139 | + */ |
|---|
| 53 | 140 | @Override |
|---|
| 54 | 141 | public int[] sqlTypes() { |
|---|
| 55 | | - return new int[] { |
|---|
| 56 | | - Types.VARCHAR |
|---|
| 57 | | - }; |
|---|
| 142 | + return new int[] { Types.VARCHAR }; |
|---|
| 58 | 143 | } |
|---|
| 59 | 144 | |
|---|
| 145 | + /** |
|---|
| 146 | + * nullSafeGet<p> |
|---|
| 147 | + * Read code from result set and map to the corresponding enum constant. |
|---|
| 148 | + * |
|---|
| 149 | + * @param resultSet |
|---|
| 150 | + * @param names |
|---|
| 151 | + * @param session |
|---|
| 152 | + * @param owner |
|---|
| 153 | + * @return enum instance or null if DB value is null or code not matched |
|---|
| 154 | + */ |
|---|
| 60 | 155 | @Override |
|---|
| 61 | 156 | public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) |
|---|
| 62 | 157 | throws HibernateException, SQLException { |
|---|
| 63 | 158 | String code = rs.getString(names[0]); |
|---|
| 159 | + if (code == null) return null; |
|---|
| 64 | 160 | for (CodedEnum en : returnedClass().getEnumConstants()) { |
|---|
| 65 | 161 | if (en.getCode().equals(code)) { |
|---|
| 66 | 162 | return en; |
|---|
| .. | .. |
|---|
| 69 | 165 | return null; |
|---|
| 70 | 166 | } |
|---|
| 71 | 167 | |
|---|
| 168 | + /** |
|---|
| 169 | + * nullSafeSet<p> |
|---|
| 170 | + * Write enum code as VARCHAR or set NULL if value is null. |
|---|
| 171 | + * |
|---|
| 172 | + * @param statement |
|---|
| 173 | + * @param value |
|---|
| 174 | + * @param index |
|---|
| 175 | + * @param session |
|---|
| 176 | + */ |
|---|
| 72 | 177 | @Override |
|---|
| 73 | 178 | public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) |
|---|
| 74 | 179 | throws HibernateException, SQLException { |
|---|
| .. | .. |
|---|
| 78 | 183 | st.setString(index, ((CodedEnum) value).getCode()); |
|---|
| 79 | 184 | } |
|---|
| 80 | 185 | } |
|---|
| 81 | | - |
|---|
| 82 | 186 | } |
|---|
| 187 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db.common; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Date; |
|---|
| .. | .. |
|---|
| 13 | 16 | import org.apache.logging.log4j.LogManager; |
|---|
| 14 | 17 | import org.apache.logging.log4j.Logger; |
|---|
| 15 | 18 | |
|---|
| 19 | +/** |
|---|
| 20 | +* SystemParams |
|---|
| 21 | +* <p> |
|---|
| 22 | +* Simple façade to read/write application-wide parameters stored in the |
|---|
| 23 | +* {@code settings} table (key/value + timestamps). |
|---|
| 24 | +* |
|---|
| 25 | +* Features: |
|---|
| 26 | +* - Typed getters: {@code String}, {@code Integer}, {@code Boolean}, {@code Double}, {@code Date}. |
|---|
| 27 | +* - Default value overloads. |
|---|
| 28 | +* - Upsert semantics in {@link #setParam(String, String)} and typed variants. |
|---|
| 29 | +* - Removal with {@link #removeParam(String)}. |
|---|
| 30 | +* |
|---|
| 31 | +* Transaction note: |
|---|
| 32 | +* - Each write method starts and commits its own transaction. Rollback is invoked |
|---|
| 33 | +* only on exceptions. |
|---|
| 34 | +* |
|---|
| 35 | +* @author JRA |
|---|
| 36 | +* Last reviewed by JRA on Oct 7, 2025. |
|---|
| 37 | +*/ |
|---|
| 16 | 38 | @ApplicationScoped |
|---|
| 17 | 39 | public class SystemParams { |
|---|
| 18 | 40 | |
|---|
| 19 | 41 | @SuppressWarnings("unused") |
|---|
| 20 | 42 | private static final Logger LOG = LogManager.getLogger(SystemParams.class); |
|---|
| 21 | 43 | |
|---|
| 22 | | - @Inject |
|---|
| 23 | | - private EntityManagerProvider emProvider; |
|---|
| 44 | + @Inject private EntityManagerProvider emProvider; |
|---|
| 45 | + |
|---|
| 46 | + // -------------------- Read API -------------------- |
|---|
| 24 | 47 | |
|---|
| 25 | 48 | /** |
|---|
| 26 | | - * Returns the system parameter value for given key |
|---|
| 27 | | - * |
|---|
| 28 | | - * @param key |
|---|
| 29 | | - * @return the value of the param or null if it doesn't exist |
|---|
| 30 | | - */ |
|---|
| 49 | + * getParam<p> |
|---|
| 50 | + * Get raw string value or {@code null} when absent. |
|---|
| 51 | + * |
|---|
| 52 | + * @param key setting key |
|---|
| 53 | + * @return string value or null |
|---|
| 54 | + */ |
|---|
| 31 | 55 | public String getParam(String key) { |
|---|
| 32 | 56 | return getParam(key, null); |
|---|
| 33 | 57 | } |
|---|
| 34 | 58 | |
|---|
| 35 | 59 | /** |
|---|
| 36 | | - * Returns the system parameter as int value for given key |
|---|
| 37 | | - * |
|---|
| 38 | | - * @param key |
|---|
| 39 | | - * @return the value of the param or null if it doesn't exist |
|---|
| 40 | | - */ |
|---|
| 60 | + * getParamAsInt<p> |
|---|
| 61 | + * Get value as Integer or null when absent. |
|---|
| 62 | + * |
|---|
| 63 | + * @param key setting key |
|---|
| 64 | + * @return integer value or null |
|---|
| 65 | + */ |
|---|
| 41 | 66 | public Integer getParamAsInt(String key) { |
|---|
| 42 | 67 | String value = getParam(key, null); |
|---|
| 43 | 68 | return value == null ? null : Integer.parseInt(value); |
|---|
| 44 | 69 | } |
|---|
| 45 | 70 | |
|---|
| 46 | 71 | /** |
|---|
| 47 | | - * |
|---|
| 48 | | - * @param key |
|---|
| 49 | | - * @param defaulValue |
|---|
| 50 | | - * returned if key doesn't exist in params table |
|---|
| 51 | | - * @return |
|---|
| 52 | | - */ |
|---|
| 72 | + * getParamAsInt<p> |
|---|
| 73 | + * Get value as Integer with default. |
|---|
| 74 | + * |
|---|
| 75 | + * @param key setting key |
|---|
| 76 | + * @param defaulValue returned if key is missing |
|---|
| 77 | + * @return integer value or default |
|---|
| 78 | + */ |
|---|
| 53 | 79 | public Integer getParamAsInt(String key, Integer defaulValue) { |
|---|
| 54 | 80 | String value = getParam(key, null); |
|---|
| 55 | 81 | return value == null ? defaulValue : Integer.parseInt(value); |
|---|
| 56 | 82 | } |
|---|
| 57 | 83 | |
|---|
| 58 | 84 | /** |
|---|
| 59 | | - * Returns the system parameter as Date value for given key |
|---|
| 60 | | - * |
|---|
| 61 | | - * @param key |
|---|
| 62 | | - * @return the value of the param or null if it doesn't exist |
|---|
| 63 | | - */ |
|---|
| 85 | + * getParamAsDate<p> |
|---|
| 86 | + * Get value parsed from ISO-8601. |
|---|
| 87 | + * |
|---|
| 88 | + * @param key setting key |
|---|
| 89 | + * @return date value or null |
|---|
| 90 | + */ |
|---|
| 64 | 91 | public Date getParamAsDate(String key) { |
|---|
| 65 | 92 | String value = getParam(key, null); |
|---|
| 66 | 93 | return value == null ? null : Utils.toDateFromIso(value); |
|---|
| 67 | 94 | } |
|---|
| 68 | 95 | |
|---|
| 69 | 96 | /** |
|---|
| 70 | | - * Returns the system parameter as boolean value for given key |
|---|
| 71 | | - * |
|---|
| 72 | | - * @param key |
|---|
| 73 | | - * @return the value of the param or null if it doesn't exist |
|---|
| 74 | | - */ |
|---|
| 97 | + * getParamAsBool<p> |
|---|
| 98 | + * Get value parsed as boolean. |
|---|
| 99 | + * |
|---|
| 100 | + * @param key setting key |
|---|
| 101 | + * @return boolean value or null |
|---|
| 102 | + */ |
|---|
| 75 | 103 | public Boolean getParamAsBool(String key) { |
|---|
| 76 | 104 | String value = getParam(key, null); |
|---|
| 77 | 105 | return value == null ? null : Boolean.parseBoolean(value); |
|---|
| 78 | 106 | } |
|---|
| 79 | 107 | |
|---|
| 80 | 108 | /** |
|---|
| 81 | | - * |
|---|
| 82 | | - * @param key |
|---|
| 83 | | - * @param defaulValue |
|---|
| 84 | | - * returned if key doesn't exist in params table |
|---|
| 85 | | - * @return |
|---|
| 86 | | - */ |
|---|
| 109 | + * getParamAsBool<p> |
|---|
| 110 | + * Get value parsed as boolean with default. |
|---|
| 111 | + * |
|---|
| 112 | + * @param key setting key |
|---|
| 113 | + * @param defaulValue default when missing |
|---|
| 114 | + * @return boolean value or default |
|---|
| 115 | + */ |
|---|
| 87 | 116 | public Boolean getParamAsBool(String key, boolean defaulValue) { |
|---|
| 88 | 117 | String value = getParam(key, null); |
|---|
| 89 | 118 | return value == null ? defaulValue : Boolean.parseBoolean(value); |
|---|
| 90 | 119 | } |
|---|
| 91 | 120 | |
|---|
| 92 | 121 | /** |
|---|
| 93 | | - * Returns the system parameter as boolean value for given key |
|---|
| 94 | | - * |
|---|
| 95 | | - * @param key |
|---|
| 96 | | - * @return the value of the param or null if it doesn't exist |
|---|
| 97 | | - */ |
|---|
| 122 | + * getParamAsDouble<p> |
|---|
| 123 | + * Get value parsed as double. |
|---|
| 124 | + * |
|---|
| 125 | + * @param key setting key |
|---|
| 126 | + * @return double value or null |
|---|
| 127 | + */ |
|---|
| 98 | 128 | public Double getParamAsDouble(String key) { |
|---|
| 99 | 129 | String value = getParam(key, null); |
|---|
| 100 | 130 | return value == null ? null : Double.parseDouble(value); |
|---|
| 101 | 131 | } |
|---|
| 102 | 132 | |
|---|
| 103 | 133 | /** |
|---|
| 104 | | - * Returns the system parameter value for given key |
|---|
| 105 | | - * |
|---|
| 106 | | - * @param key |
|---|
| 107 | | - * @param defaultValue |
|---|
| 108 | | - * returned if key doesn't exist in params table |
|---|
| 109 | | - * @return |
|---|
| 110 | | - */ |
|---|
| 134 | + * getParam<p> |
|---|
| 135 | + * Get raw string value or a default when missing. |
|---|
| 136 | + * |
|---|
| 137 | + * @param key setting key |
|---|
| 138 | + * @param defaultValue default when missing |
|---|
| 139 | + * @return value or default |
|---|
| 140 | + */ |
|---|
| 111 | 141 | public String getParam(String key, String defaultValue) { |
|---|
| 112 | 142 | EntityManager em = emProvider.getEntityManager(); |
|---|
| 113 | 143 | Settings p = em.find(Settings.class, key); |
|---|
| 114 | 144 | return p == null ? defaultValue : p.getValue(); |
|---|
| 115 | 145 | } |
|---|
| 116 | 146 | |
|---|
| 147 | + // -------------------- Write API -------------------- |
|---|
| 148 | + |
|---|
| 117 | 149 | /** |
|---|
| 118 | | - * Returns the system parameter value passed as parameter to method |
|---|
| 119 | | - * |
|---|
| 120 | | - * @param key |
|---|
| 121 | | - * @param defaultValue |
|---|
| 122 | | - * @return |
|---|
| 123 | | - */ |
|---|
| 150 | + * setParam<p> |
|---|
| 151 | + * Upsert a parameter as string. |
|---|
| 152 | + * |
|---|
| 153 | + * @param key setting key |
|---|
| 154 | + * @param value string value |
|---|
| 155 | + */ |
|---|
| 124 | 156 | public void setParam(String key, String value) { |
|---|
| 125 | 157 | EntityManager em = emProvider.getEntityManager(); |
|---|
| 126 | 158 | em.getTransaction().begin(); |
|---|
| 127 | 159 | try { |
|---|
| 128 | 160 | Settings p = em.find(Settings.class, key); |
|---|
| 129 | | - |
|---|
| 130 | 161 | if (p == null) { |
|---|
| 131 | 162 | p = new Settings(); |
|---|
| 132 | 163 | p.setKey(key); |
|---|
| .. | .. |
|---|
| 136 | 167 | p.setValue(value); |
|---|
| 137 | 168 | em.merge(p); |
|---|
| 138 | 169 | } |
|---|
| 139 | | - em.flush(); |
|---|
| 140 | 170 | em.getTransaction().commit(); |
|---|
| 141 | | - } finally { |
|---|
| 171 | + } catch (Exception ex) { |
|---|
| 142 | 172 | em.getTransaction().rollback(); |
|---|
| 173 | + throw ex; |
|---|
| 143 | 174 | } |
|---|
| 144 | 175 | } |
|---|
| 145 | 176 | |
|---|
| 146 | | - /** |
|---|
| 147 | | - * Save a parameter as a Date |
|---|
| 177 | + /** |
|---|
| 178 | + * setParam<p> |
|---|
| 179 | + * Save parameter as ISO date string. |
|---|
| 148 | 180 | * |
|---|
| 149 | 181 | * @param key |
|---|
| 150 | 182 | * @param value |
|---|
| .. | .. |
|---|
| 153 | 185 | setParam(key, Utils.toIsoFormat(value)); |
|---|
| 154 | 186 | } |
|---|
| 155 | 187 | |
|---|
| 156 | | - /** |
|---|
| 157 | | - * Save a parameter as a integer |
|---|
| 188 | + /** |
|---|
| 189 | + * setParam<p> |
|---|
| 190 | + * Save parameter as integer. |
|---|
| 158 | 191 | * |
|---|
| 159 | 192 | * @param key |
|---|
| 160 | 193 | * @param value |
|---|
| .. | .. |
|---|
| 163 | 196 | setParam(key, String.valueOf(value)); |
|---|
| 164 | 197 | } |
|---|
| 165 | 198 | |
|---|
| 166 | | - /** |
|---|
| 167 | | - * Save a parameter as a boolean |
|---|
| 199 | + /** |
|---|
| 200 | + * setParam<p> |
|---|
| 201 | + * Save parameter as boolean. |
|---|
| 168 | 202 | * |
|---|
| 169 | 203 | * @param key |
|---|
| 170 | 204 | * @param value |
|---|
| .. | .. |
|---|
| 173 | 207 | setParam(key, String.valueOf(value)); |
|---|
| 174 | 208 | } |
|---|
| 175 | 209 | |
|---|
| 176 | | - /** |
|---|
| 177 | | - * Save a parameter as a double |
|---|
| 210 | + /** |
|---|
| 211 | + * setParam<p> |
|---|
| 212 | + * Save parameter as double. |
|---|
| 178 | 213 | * |
|---|
| 179 | 214 | * @param key |
|---|
| 180 | 215 | * @param value |
|---|
| .. | .. |
|---|
| 184 | 219 | } |
|---|
| 185 | 220 | |
|---|
| 186 | 221 | /** |
|---|
| 187 | | - * Remove a parameter from params table |
|---|
| 188 | | - * |
|---|
| 189 | | - * @param key |
|---|
| 190 | | - * @return |
|---|
| 191 | | - */ |
|---|
| 222 | + * removeParam<p> |
|---|
| 223 | + * Delete a parameter by key (no-op if missing). |
|---|
| 224 | + * |
|---|
| 225 | + * @param key setting key |
|---|
| 226 | + */ |
|---|
| 192 | 227 | public void removeParam(String key) { |
|---|
| 193 | 228 | EntityManager em = emProvider.getEntityManager(); |
|---|
| 194 | 229 | em.getTransaction().begin(); |
|---|
| .. | .. |
|---|
| 198 | 233 | em.remove(p); |
|---|
| 199 | 234 | } |
|---|
| 200 | 235 | em.getTransaction().commit(); |
|---|
| 201 | | - } finally { |
|---|
| 236 | + } catch (Exception ex) { |
|---|
| 202 | 237 | em.getTransaction().rollback(); |
|---|
| 238 | + throw ex; |
|---|
| 203 | 239 | } |
|---|
| 204 | 240 | } |
|---|
| 205 | 241 | |
|---|
| 242 | + /** |
|---|
| 243 | + * Keys |
|---|
| 244 | + * <p> |
|---|
| 245 | + * Centralized constants for parameter keys (client/common/server). |
|---|
| 246 | + */ |
|---|
| 206 | 247 | public static class Keys { |
|---|
| 207 | | - // Keys used in basic app |
|---|
| 248 | + // Client app keys |
|---|
| 208 | 249 | public static final String CONFIG_CLIENT_HOST = "config.client.host"; |
|---|
| 209 | 250 | public static final String CONFIG_CLIENT_PORT = "config.client.port"; |
|---|
| 210 | 251 | public static final String CONFIG_CLIENT_LAST_UPDATE = "config.client.last_update"; |
|---|
| .. | .. |
|---|
| 217 | 258 | public static final String CONFIG_CLIENT_GS_HOST = "config.client.gs_host"; |
|---|
| 218 | 259 | public static final String CONFIG_CLIENT_GS_PORT = "config.client.gs_port"; |
|---|
| 219 | 260 | |
|---|
| 220 | | - // Keys used in both app |
|---|
| 221 | | - public static final String CONFIG_COMMON_CUSTOMER_CODE = "config.common.customer_code"; // BP |
|---|
| 222 | | - public static final String CONFIG_COMMON_CS_CODE = "config.common.cs_code"; // 0000 |
|---|
| 261 | + // Shared keys |
|---|
| 262 | + public static final String CONFIG_COMMON_CUSTOMER_CODE = "config.common.customer_code"; |
|---|
| 263 | + public static final String CONFIG_COMMON_CS_CODE = "config.common.cs_code"; |
|---|
| 223 | 264 | public static final String CONFIG_COMMON_USERS_VERSION = "config.common.user_version"; |
|---|
| 224 | 265 | public static final String CONFIG_COMMON_SETTINGS_VERSION = "config.common.settings_version"; |
|---|
| 225 | 266 | public static final String CONFIG_COMMON_DATASET_VERSION = "config.common.dataset_version"; |
|---|
| .. | .. |
|---|
| 227 | 268 | public static final String CONFIG_COMMON_TIMEOUT_SESSION_BA = "config.common.timeout_session_ba"; |
|---|
| 228 | 269 | public static final String CONFIG_COMMON_TIMEOUT_SESSION_CS = "config.common.timeout_session_cs"; |
|---|
| 229 | 270 | |
|---|
| 230 | | - // Keys used in server app |
|---|
| 271 | + // Server app keys |
|---|
| 231 | 272 | public static final String CONFIG_SERVER_LICENSE_EXPIRATION = "config.server.license.expiation"; |
|---|
| 232 | 273 | public static final String CONFIG_SERVER_MAX_INSTANCES = "config.server.max_instances"; |
|---|
| 233 | 274 | public static final String CONFIG_SERVER_MAX_USERS = "config.server.max_users"; |
|---|
| .. | .. |
|---|
| 240 | 281 | public static final String CONFIG_SERVER_CREATE_DATASET = "config.server.create_dataset_in_next_startup"; |
|---|
| 241 | 282 | public static final String CONFIG_SERVER_PORT = "config.server.port"; |
|---|
| 242 | 283 | } |
|---|
| 243 | | - |
|---|
| 244 | 284 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db.listeners; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Date; |
|---|
| .. | .. |
|---|
| 9 | 12 | import org.apache.logging.log4j.LogManager; |
|---|
| 10 | 13 | import org.apache.logging.log4j.Logger; |
|---|
| 11 | 14 | |
|---|
| 15 | +/** |
|---|
| 16 | +* CreationTimestampListener |
|---|
| 17 | +* <p> |
|---|
| 18 | +* JPA entity listener that sets the <b>creation timestamp</b> right before persisting. |
|---|
| 19 | +* Entities must implement {@link CreationTimestampEntity} to be compatible. |
|---|
| 20 | +* |
|---|
| 21 | +* Usage: |
|---|
| 22 | +* {@code @EntityListeners(CreationTimestampListener.class)} |
|---|
| 23 | +* |
|---|
| 24 | +* @author JRA |
|---|
| 25 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 26 | +*/ |
|---|
| 12 | 27 | public class CreationTimestampListener { |
|---|
| 13 | 28 | |
|---|
| 14 | 29 | private static final Logger log = LogManager.getLogger(CreationTimestampListener.class); |
|---|
| 15 | 30 | |
|---|
| 31 | + /** |
|---|
| 32 | + * updateTimestamp<p> |
|---|
| 33 | + * Set creation timestamp during @PrePersist. |
|---|
| 34 | + * |
|---|
| 35 | + * @param creationTimeStampEntity |
|---|
| 36 | + */ |
|---|
| 16 | 37 | @PrePersist |
|---|
| 17 | 38 | public void updateTimestamp(CreationTimestampEntity p) { |
|---|
| 18 | | - log.info("Settings creation timestmap date"); |
|---|
| 39 | + log.info("Settings creation timestamp date"); |
|---|
| 19 | 40 | p.setCreationTimestamp(new Date()); |
|---|
| 20 | 41 | } |
|---|
| 21 | | - |
|---|
| 22 | 42 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.db.listeners; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Date; |
|---|
| .. | .. |
|---|
| 10 | 13 | import org.apache.logging.log4j.LogManager; |
|---|
| 11 | 14 | import org.apache.logging.log4j.Logger; |
|---|
| 12 | 15 | |
|---|
| 16 | +/** |
|---|
| 17 | +* ModificationTimestampListener |
|---|
| 18 | +* <p> |
|---|
| 19 | +* JPA entity listener that updates the <b>modification timestamp</b> on both |
|---|
| 20 | +* persist and update events. Entities must implement |
|---|
| 21 | +* {@link ModificationTimestampEntity}. |
|---|
| 22 | +* |
|---|
| 23 | +* @author JRA |
|---|
| 24 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 25 | +*/ |
|---|
| 13 | 26 | public class ModificationTimestampListener { |
|---|
| 14 | 27 | |
|---|
| 15 | 28 | private static final Logger log = LogManager.getLogger(ModificationTimestampListener.class); |
|---|
| 16 | 29 | |
|---|
| 30 | + /** |
|---|
| 31 | + * updateTimestamp<p> |
|---|
| 32 | + * Set modification timestamp during @PrePersist/@PreUpdate. |
|---|
| 33 | + * |
|---|
| 34 | + * @param modificationTimestampEntity |
|---|
| 35 | + */ |
|---|
| 17 | 36 | @PreUpdate |
|---|
| 18 | 37 | @PrePersist |
|---|
| 19 | 38 | public void updateTimestamp(ModificationTimestampEntity p) { |
|---|
| 20 | | - log.info("Settings modification timestmap date"); |
|---|
| 39 | + log.info("Settings modification timestamp date"); |
|---|
| 21 | 40 | p.setModificationTimestamp(new Date()); |
|---|
| 22 | 41 | } |
|---|
| 23 | | - |
|---|
| 24 | 42 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.ioc; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.lang.annotation.ElementType; |
|---|
| .. | .. |
|---|
| 7 | 10 | |
|---|
| 8 | 11 | import jakarta.interceptor.InterceptorBinding; |
|---|
| 9 | 12 | |
|---|
| 10 | | -@Target({ |
|---|
| 11 | | - ElementType.METHOD, ElementType.TYPE |
|---|
| 12 | | -}) |
|---|
| 13 | +/** |
|---|
| 14 | +* EnsureTransaction |
|---|
| 15 | +* <p> |
|---|
| 16 | +* CDI interceptor binding to mark resource methods that require a |
|---|
| 17 | +* transaction boundary. Interceptors (e.g., in a request filter / writer |
|---|
| 18 | +* interceptor) can check this annotation to begin/commit/rollback. |
|---|
| 19 | +* |
|---|
| 20 | +* Usage: |
|---|
| 21 | +* {@code @EnsureTransaction} on JAX-RS methods. |
|---|
| 22 | +* |
|---|
| 23 | +* @author JRA |
|---|
| 24 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 25 | +*/ |
|---|
| 26 | +@Target({ ElementType.METHOD, ElementType.TYPE }) |
|---|
| 13 | 27 | @Retention(RetentionPolicy.RUNTIME) |
|---|
| 14 | 28 | @InterceptorBinding |
|---|
| 15 | | -public @interface EnsureTransaction { |
|---|
| 16 | | - |
|---|
| 17 | | -} |
|---|
| 29 | +public @interface EnsureTransaction { } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.ioc; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import jakarta.enterprise.context.ApplicationScoped; |
|---|
| .. | .. |
|---|
| 8 | 11 | import org.apache.logging.log4j.LogManager; |
|---|
| 9 | 12 | import org.apache.logging.log4j.Logger; |
|---|
| 10 | 13 | |
|---|
| 14 | +/** |
|---|
| 15 | +* EntityManagerProvider |
|---|
| 16 | +* <p> |
|---|
| 17 | +* Simple provider for JPA {@link EntityManager} instances using the |
|---|
| 18 | +* persistence unit "localdb". Creates an {@link EntityManagerFactory} |
|---|
| 19 | +* once per application and returns a fresh {@link EntityManager} per call. |
|---|
| 20 | +* |
|---|
| 21 | +* Note: |
|---|
| 22 | +* - Callers are responsible for closing the obtained EntityManager. |
|---|
| 23 | +* |
|---|
| 24 | +* @author JRA |
|---|
| 25 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 26 | +*/ |
|---|
| 11 | 27 | @ApplicationScoped |
|---|
| 12 | 28 | public class EntityManagerProvider { |
|---|
| 13 | 29 | |
|---|
| 14 | 30 | @SuppressWarnings("unused") |
|---|
| 15 | | - private static final Logger log = LogManager.getLogger(EntityManagerProvider.class); |
|---|
| 31 | + private static final Logger log = LogManager.getLogger(EntityManagerProvider.class); |
|---|
| 16 | 32 | |
|---|
| 33 | + /** |
|---|
| 34 | + * entityManagerFactory<p> |
|---|
| 35 | + * Application-wide EMF built from persistence.xml PU "localdb". |
|---|
| 36 | + */ |
|---|
| 17 | 37 | private final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("localdb"); |
|---|
| 18 | 38 | |
|---|
| 39 | + /** |
|---|
| 40 | + * getEntityManager<p> |
|---|
| 41 | + * Create a new {@link EntityManager}. |
|---|
| 42 | + * |
|---|
| 43 | + * @return a new EntityManager; caller must close it |
|---|
| 44 | + */ |
|---|
| 19 | 45 | public EntityManager getEntityManager() { |
|---|
| 20 | 46 | return entityManagerFactory.createEntityManager(); |
|---|
| 21 | 47 | } |
|---|
| 22 | | - |
|---|
| 23 | 48 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.ioc; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.IOException; |
|---|
| .. | .. |
|---|
| 31 | 34 | import net.curisit.securis.utils.CacheTTL; |
|---|
| 32 | 35 | import net.curisit.securis.utils.TokenHelper; |
|---|
| 33 | 36 | |
|---|
| 37 | +/** |
|---|
| 38 | +* RequestsInterceptor |
|---|
| 39 | +* <p> |
|---|
| 40 | +* Authentication/authorization interceptor that: |
|---|
| 41 | +* <ul> |
|---|
| 42 | +* <li>Loads and stores the {@link EntityManager} in the request context.</li> |
|---|
| 43 | +* <li>Validates tokens for methods annotated with {@link Securable}.</li> |
|---|
| 44 | +* <li>Builds a {@link BasicSecurityContext} with roles and scoped organization/application IDs.</li> |
|---|
| 45 | +* <li>Manages transactions when {@code @EnsureTransaction} is present.</li> |
|---|
| 46 | +* </ul> |
|---|
| 47 | +* |
|---|
| 48 | +* <p><b>Cache usage:</b> Uses {@link CacheTTL} to cache roles and scope sets. |
|---|
| 49 | +* The new {@link CacheTTL#getSet(String, Class)} helper removes unchecked |
|---|
| 50 | +* conversion warnings when retrieving {@code Set<Integer>} from the cache. |
|---|
| 51 | +* |
|---|
| 52 | +* @author JRA |
|---|
| 53 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 54 | +*/ |
|---|
| 34 | 55 | @Provider |
|---|
| 35 | 56 | @Priority(Priorities.AUTHENTICATION) |
|---|
| 36 | 57 | public class RequestsInterceptor implements ContainerRequestFilter, WriterInterceptor { |
|---|
| 37 | 58 | |
|---|
| 38 | | - private static final Logger LOG = LogManager.getLogger(RequestsInterceptor.class); |
|---|
| 59 | + private static final Logger LOG = LogManager.getLogger(RequestsInterceptor.class); |
|---|
| 39 | 60 | |
|---|
| 40 | | - @Context |
|---|
| 41 | | - private HttpServletResponse servletResponse; |
|---|
| 61 | + @Inject private CacheTTL cache; |
|---|
| 62 | + @Inject private TokenHelper tokenHelper; |
|---|
| 63 | + @Inject private EntityManagerProvider emProvider; |
|---|
| 42 | 64 | |
|---|
| 43 | | - @Context |
|---|
| 44 | | - private HttpServletRequest servletRequest; |
|---|
| 65 | + @Context private HttpServletResponse servletResponse; |
|---|
| 66 | + @Context private HttpServletRequest servletRequest; |
|---|
| 67 | + @Context private ResourceInfo resourceInfo; |
|---|
| 45 | 68 | |
|---|
| 46 | | - @Context |
|---|
| 47 | | - private ResourceInfo resourceInfo; |
|---|
| 69 | + private static final String EM_CONTEXT_PROPERTY = "curisit.entitymanager"; |
|---|
| 48 | 70 | |
|---|
| 49 | | - @Inject |
|---|
| 50 | | - private CacheTTL cache; |
|---|
| 71 | + // ------------------------------------------------------------- |
|---|
| 72 | + // Request filter (authN/authZ + EM handling) |
|---|
| 73 | + // ------------------------------------------------------------- |
|---|
| 51 | 74 | |
|---|
| 52 | | - @Inject |
|---|
| 53 | | - private TokenHelper tokenHelper; |
|---|
| 75 | + /** |
|---|
| 76 | + * filter<p> |
|---|
| 77 | + * Entry point before resource method invocation. |
|---|
| 78 | + * |
|---|
| 79 | + * @param requestContext |
|---|
| 80 | + * @throws IOException |
|---|
| 81 | + */ |
|---|
| 82 | + @Override |
|---|
| 83 | + public void filter(ContainerRequestContext requestContext) throws IOException { |
|---|
| 84 | + EntityManager em = emProvider.getEntityManager(); |
|---|
| 85 | + LOG.debug("GETTING EM: {}", em); |
|---|
| 54 | 86 | |
|---|
| 55 | | - @Inject |
|---|
| 56 | | - private EntityManagerProvider emProvider; |
|---|
| 87 | + // Store EntityManager for later retrieval (writer interceptor) |
|---|
| 88 | + requestContext.setProperty(EM_CONTEXT_PROPERTY, em); |
|---|
| 57 | 89 | |
|---|
| 58 | | - private static final String EM_CONTEXT_PROPERTY = "curisit.entitymanager"; |
|---|
| 90 | + Method method = resourceInfo.getResourceMethod(); |
|---|
| 59 | 91 | |
|---|
| 60 | | - @Override |
|---|
| 61 | | - public void filter(ContainerRequestContext requestContext) throws IOException { |
|---|
| 62 | | - EntityManager em = emProvider.getEntityManager(); |
|---|
| 63 | | - LOG.debug("GETTING EM: {}", em); |
|---|
| 92 | + if (checkSecurableMethods(requestContext, method)) { |
|---|
| 93 | + if (method.isAnnotationPresent(EnsureTransaction.class)) { |
|---|
| 94 | + LOG.debug("Beginning transaction"); |
|---|
| 95 | + em.getTransaction().begin(); |
|---|
| 96 | + } |
|---|
| 97 | + } |
|---|
| 98 | + } |
|---|
| 64 | 99 | |
|---|
| 65 | | - // Guardamos el EntityManager en el contexto para recuperación posterior |
|---|
| 66 | | - requestContext.setProperty(EM_CONTEXT_PROPERTY, em); |
|---|
| 100 | + /** |
|---|
| 101 | + * checkSecurableMethods<p> |
|---|
| 102 | + * Enforce security checks for methods annotated with {@link Securable}. |
|---|
| 103 | + * Builds {@link BasicSecurityContext} when authorized. |
|---|
| 104 | + * |
|---|
| 105 | + * @param ctx |
|---|
| 106 | + * @param method |
|---|
| 107 | + * @return true if request can proceed; false when aborted |
|---|
| 108 | + */ |
|---|
| 109 | + private boolean checkSecurableMethods(ContainerRequestContext ctx, Method method) { |
|---|
| 110 | + if (!method.isAnnotationPresent(Securable.class)) return true; |
|---|
| 67 | 111 | |
|---|
| 68 | | - Method method = resourceInfo.getResourceMethod(); |
|---|
| 112 | + String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM); |
|---|
| 113 | + if (token == null || !tokenHelper.isTokenValid(token)) { |
|---|
| 114 | + LOG.warn("Access denied, invalid token"); |
|---|
| 115 | + ctx.abortWith(Response.status(Status.UNAUTHORIZED).build()); |
|---|
| 116 | + return false; |
|---|
| 117 | + } |
|---|
| 69 | 118 | |
|---|
| 70 | | - if (checkSecurableMethods(requestContext, method)) { |
|---|
| 71 | | - if (method.isAnnotationPresent(EnsureTransaction.class)) { |
|---|
| 72 | | - LOG.debug("Beginning transaction"); |
|---|
| 73 | | - em.getTransaction().begin(); |
|---|
| 74 | | - } |
|---|
| 75 | | - } |
|---|
| 76 | | - } |
|---|
| 119 | + String username = tokenHelper.extractUserFromToken(token); |
|---|
| 120 | + int roles = getUserRoles(username); |
|---|
| 121 | + Securable securable = method.getAnnotation(Securable.class); |
|---|
| 77 | 122 | |
|---|
| 78 | | - private boolean checkSecurableMethods(ContainerRequestContext ctx, Method method) { |
|---|
| 79 | | - if (!method.isAnnotationPresent(Securable.class)) return true; |
|---|
| 123 | + if (securable.roles() != 0 && (securable.roles() & roles) == 0) { |
|---|
| 124 | + LOG.warn("User {} lacks required roles for method {}", username, method.getName()); |
|---|
| 125 | + ctx.abortWith(Response.status(Status.UNAUTHORIZED).build()); |
|---|
| 126 | + return false; |
|---|
| 127 | + } |
|---|
| 80 | 128 | |
|---|
| 81 | | - String token = servletRequest.getHeader(TokenHelper.TOKEN_HEADER_PÀRAM); |
|---|
| 82 | | - if (token == null || !tokenHelper.isTokenValid(token)) { |
|---|
| 83 | | - LOG.warn("Access denied, invalid token"); |
|---|
| 84 | | - ctx.abortWith(Response.status(Status.UNAUTHORIZED).build()); |
|---|
| 85 | | - return false; |
|---|
| 86 | | - } |
|---|
| 129 | + BasicSecurityContext sc = new BasicSecurityContext(username, roles, servletRequest.isSecure()); |
|---|
| 130 | + sc.setOrganizationsIds(getUserOrganizations(username)); |
|---|
| 131 | + sc.setApplicationsIds(getUserApplications(username)); |
|---|
| 132 | + ctx.setSecurityContext(sc); |
|---|
| 133 | + return true; |
|---|
| 134 | + } |
|---|
| 87 | 135 | |
|---|
| 88 | | - String username = tokenHelper.extractUserFromToken(token); |
|---|
| 89 | | - int roles = getUserRoles(username); |
|---|
| 90 | | - Securable securable = method.getAnnotation(Securable.class); |
|---|
| 136 | + // ------------------------------------------------------------- |
|---|
| 137 | + // Cached lookups (roles/orgs/apps) |
|---|
| 138 | + // ------------------------------------------------------------- |
|---|
| 91 | 139 | |
|---|
| 92 | | - if (securable.roles() != 0 && (securable.roles() & roles) == 0) { |
|---|
| 93 | | - LOG.warn("User {} lacks required roles for method {}", username, method.getName()); |
|---|
| 94 | | - ctx.abortWith(Response.status(Status.UNAUTHORIZED).build()); |
|---|
| 95 | | - return false; |
|---|
| 96 | | - } |
|---|
| 140 | + /** |
|---|
| 141 | + * getUserRoles<p> |
|---|
| 142 | + * Retrieve roles bitmask for the given user (cached). |
|---|
| 143 | + * |
|---|
| 144 | + * @param username |
|---|
| 145 | + * @return userRoles |
|---|
| 146 | + */ |
|---|
| 147 | + 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; |
|---|
| 97 | 151 | |
|---|
| 98 | | - BasicSecurityContext sc = new BasicSecurityContext(username, roles, servletRequest.isSecure()); |
|---|
| 99 | | - sc.setOrganizationsIds(getUserOrganizations(username)); |
|---|
| 100 | | - sc.setApplicationsIds(getUserApplications(username)); |
|---|
| 101 | | - ctx.setSecurityContext(sc); |
|---|
| 102 | | - return true; |
|---|
| 103 | | - } |
|---|
| 152 | + EntityManager em = emProvider.getEntityManager(); |
|---|
| 153 | + User user = em.find(User.class, username); |
|---|
| 154 | + int roles = 0; |
|---|
| 155 | + if (user != null) { |
|---|
| 156 | + List<Integer> r = user.getRoles(); |
|---|
| 157 | + if (r != null) for (Integer role : r) roles += role; |
|---|
| 158 | + cache.set("roles_" + username, roles, 3600); |
|---|
| 159 | + // also warm some caches |
|---|
| 160 | + cache.set("orgs_" + username, user.getOrgsIds(), 3600); |
|---|
| 161 | + } |
|---|
| 162 | + return roles; |
|---|
| 163 | + } |
|---|
| 104 | 164 | |
|---|
| 105 | | - private int getUserRoles(String username) { |
|---|
| 106 | | - if (username == null) return 0; |
|---|
| 107 | | - Integer cached = cache.get("roles_" + username, Integer.class); |
|---|
| 108 | | - if (cached != null) return cached; |
|---|
| 165 | + /** |
|---|
| 166 | + * getUserOrganizations<p> |
|---|
| 167 | + * Retrieve organization scope for the user as a typed {@code Set<Integer>} |
|---|
| 168 | + * using the cache helper that validates element types. |
|---|
| 169 | + * |
|---|
| 170 | + * @param username |
|---|
| 171 | + * @return userOrganizations |
|---|
| 172 | + */ |
|---|
| 173 | + private Set<Integer> getUserOrganizations(String username) { |
|---|
| 174 | + Set<Integer> cached = cache.getSet("orgs_" + username, Integer.class); |
|---|
| 175 | + if (cached != null) return cached; |
|---|
| 109 | 176 | |
|---|
| 110 | | - EntityManager em = emProvider.getEntityManager(); |
|---|
| 111 | | - User user = em.find(User.class, username); |
|---|
| 112 | | - int roles = 0; |
|---|
| 113 | | - if (user != null) { |
|---|
| 114 | | - List<Integer> r = user.getRoles(); |
|---|
| 115 | | - if (r != null) for (Integer role : r) roles += role; |
|---|
| 116 | | - cache.set("roles_" + username, roles, 3600); |
|---|
| 117 | | - cache.set("orgs_" + username, user.getOrgsIds(), 3600); |
|---|
| 118 | | - } |
|---|
| 119 | | - return roles; |
|---|
| 120 | | - } |
|---|
| 177 | + User user = emProvider.getEntityManager().find(User.class, username); |
|---|
| 178 | + if (user != null) { |
|---|
| 179 | + Set<Integer> result = user.getAllOrgsIds(); |
|---|
| 180 | + cache.set("orgs_" + username, result, 3600); |
|---|
| 181 | + return result; |
|---|
| 182 | + } |
|---|
| 183 | + return Set.of(); |
|---|
| 184 | + } |
|---|
| 121 | 185 | |
|---|
| 122 | | - private Set<Integer> getUserOrganizations(String username) { |
|---|
| 123 | | - Set<Integer> cached = cache.get("orgs_" + username, Set.class); |
|---|
| 124 | | - if (cached != null) return cached; |
|---|
| 125 | | - User user = emProvider.getEntityManager().find(User.class, username); |
|---|
| 126 | | - if (user != null) { |
|---|
| 127 | | - Set<Integer> result = user.getAllOrgsIds(); |
|---|
| 128 | | - cache.set("orgs_" + username, result, 3600); |
|---|
| 129 | | - return result; |
|---|
| 130 | | - } |
|---|
| 131 | | - return Set.of(); |
|---|
| 132 | | - } |
|---|
| 186 | + /** |
|---|
| 187 | + * getUserApplications<p> |
|---|
| 188 | + * Retrieve application scope for the user as a typed {@code Set<Integer>} |
|---|
| 189 | + * using the cache helper that validates element types. |
|---|
| 190 | + * |
|---|
| 191 | + * @param username |
|---|
| 192 | + * @return userApplications |
|---|
| 193 | + */ |
|---|
| 194 | + private Set<Integer> getUserApplications(String username) { |
|---|
| 195 | + Set<Integer> cached = cache.getSet("apps_" + username, Integer.class); |
|---|
| 196 | + if (cached != null) return cached; |
|---|
| 133 | 197 | |
|---|
| 134 | | - private Set<Integer> getUserApplications(String username) { |
|---|
| 135 | | - Set<Integer> cached = cache.get("apps_" + username, Set.class); |
|---|
| 136 | | - if (cached != null) return cached; |
|---|
| 137 | | - User user = emProvider.getEntityManager().find(User.class, username); |
|---|
| 138 | | - if (user != null) { |
|---|
| 139 | | - Set<Integer> result = user.getAllAppsIds(); |
|---|
| 140 | | - cache.set("apps_" + username, result, 3600); |
|---|
| 141 | | - return result; |
|---|
| 142 | | - } |
|---|
| 143 | | - return Set.of(); |
|---|
| 144 | | - } |
|---|
| 198 | + User user = emProvider.getEntityManager().find(User.class, username); |
|---|
| 199 | + if (user != null) { |
|---|
| 200 | + Set<Integer> result = user.getAllAppsIds(); |
|---|
| 201 | + cache.set("apps_" + username, result, 3600); |
|---|
| 202 | + return result; |
|---|
| 203 | + } |
|---|
| 204 | + return Set.of(); |
|---|
| 205 | + } |
|---|
| 145 | 206 | |
|---|
| 146 | | - @Override |
|---|
| 147 | | - public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { |
|---|
| 148 | | - context.proceed(); |
|---|
| 207 | + // ------------------------------------------------------------- |
|---|
| 208 | + // Writer interceptor (transaction finalize) |
|---|
| 209 | + // ------------------------------------------------------------- |
|---|
| 149 | 210 | |
|---|
| 150 | | - EntityManager em = (EntityManager) context.getProperty(EM_CONTEXT_PROPERTY); |
|---|
| 151 | | - if (em == null) return; |
|---|
| 211 | + /** |
|---|
| 212 | + * aroundWriteTo<p> |
|---|
| 213 | + * Commit/rollback and close EM after response writing. |
|---|
| 214 | + * |
|---|
| 215 | + * @param context |
|---|
| 216 | + * @throws IOException |
|---|
| 217 | + * @throws WebApplicationException |
|---|
| 218 | + */ |
|---|
| 219 | + @Override |
|---|
| 220 | + public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { |
|---|
| 221 | + context.proceed(); |
|---|
| 152 | 222 | |
|---|
| 153 | | - try { |
|---|
| 154 | | - if (em.getTransaction().isActive()) { |
|---|
| 155 | | - if (servletResponse.getStatus() == Status.OK.getStatusCode()) { |
|---|
| 156 | | - em.getTransaction().commit(); |
|---|
| 157 | | - LOG.debug("Transaction committed"); |
|---|
| 158 | | - } else { |
|---|
| 159 | | - em.getTransaction().rollback(); |
|---|
| 160 | | - LOG.debug("Transaction rolled back"); |
|---|
| 161 | | - } |
|---|
| 162 | | - } |
|---|
| 163 | | - } finally { |
|---|
| 164 | | - if (em.isOpen()) { |
|---|
| 165 | | - try { |
|---|
| 166 | | - em.close(); |
|---|
| 167 | | - } catch (Exception e) { |
|---|
| 168 | | - LOG.error("Error closing EntityManager", e); |
|---|
| 169 | | - } |
|---|
| 170 | | - } |
|---|
| 171 | | - } |
|---|
| 172 | | - } |
|---|
| 223 | + EntityManager em = (EntityManager) context.getProperty(EM_CONTEXT_PROPERTY); |
|---|
| 224 | + if (em == null) return; |
|---|
| 225 | + |
|---|
| 226 | + 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 | + } |
|---|
| 236 | + } finally { |
|---|
| 237 | + if (em.isOpen()) { |
|---|
| 238 | + try { |
|---|
| 239 | + em.close(); |
|---|
| 240 | + } catch (Exception e) { |
|---|
| 241 | + LOG.error("Error closing EntityManager", e); |
|---|
| 242 | + } |
|---|
| 243 | + } |
|---|
| 244 | + } |
|---|
| 245 | + } |
|---|
| 173 | 246 | } |
|---|
| 247 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.ioc; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import net.curisit.securis.services.ApiResource; |
|---|
| .. | .. |
|---|
| 11 | 14 | |
|---|
| 12 | 15 | import com.google.inject.AbstractModule; |
|---|
| 13 | 16 | |
|---|
| 17 | +/** |
|---|
| 18 | +* RequestsModule |
|---|
| 19 | +* <p> |
|---|
| 20 | +* Guice module that binds JAX-RS resource classes so they can be |
|---|
| 21 | +* injected and discovered by the DI container. |
|---|
| 22 | +* <p> |
|---|
| 23 | +* Notes: |
|---|
| 24 | +* - Currently binds resources explicitly. TODO indicates a future |
|---|
| 25 | +* improvement to bind dynamically via reflection / classpath scanning. |
|---|
| 26 | +* |
|---|
| 27 | +* @author JRA |
|---|
| 28 | +* Last reviewed by JRA on Oct 7, 2025. |
|---|
| 29 | +*/ |
|---|
| 14 | 30 | public class RequestsModule extends AbstractModule { |
|---|
| 15 | 31 | |
|---|
| 32 | + /** |
|---|
| 33 | + * configure<p> |
|---|
| 34 | + * Register resource types in the injector. |
|---|
| 35 | + */ |
|---|
| 16 | 36 | @Override |
|---|
| 17 | 37 | protected void configure() { |
|---|
| 18 | 38 | // TODO Securis: Make the bind using reflection dynamically |
|---|
| 19 | | - |
|---|
| 20 | 39 | bind(BasicServices.class); |
|---|
| 21 | 40 | bind(UserResource.class); |
|---|
| 22 | | - |
|---|
| 23 | 41 | bind(ApplicationResource.class); |
|---|
| 24 | 42 | bind(LicenseTypeResource.class); |
|---|
| 25 | 43 | bind(OrganizationResource.class); |
|---|
| 26 | 44 | bind(ApiResource.class); |
|---|
| 27 | 45 | bind(LicenseResource.class); |
|---|
| 28 | 46 | bind(PackResource.class); |
|---|
| 29 | | - |
|---|
| 30 | 47 | } |
|---|
| 31 | 48 | |
|---|
| 49 | + // Example provider (kept commented for reference) |
|---|
| 32 | 50 | // @Provides |
|---|
| 33 | 51 | // @RequestScoped |
|---|
| 34 | 52 | // public User provideUser() { |
|---|
| 35 | | - // return ResteasyProviderFactory.getContextData(User.class); |
|---|
| 53 | + // return ResteasyProviderFactory.getContextData(User.class); |
|---|
| 36 | 54 | // } |
|---|
| 37 | | - |
|---|
| 38 | 55 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.ioc; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.File; |
|---|
| .. | .. |
|---|
| 18 | 21 | import com.google.inject.AbstractModule; |
|---|
| 19 | 22 | import com.google.inject.Provides; |
|---|
| 20 | 23 | |
|---|
| 24 | +/** |
|---|
| 25 | +* SecurisModule |
|---|
| 26 | +* <p> |
|---|
| 27 | +* Guice module that provides application-level infrastructural dependencies |
|---|
| 28 | +* (base URI, app directories, DB files list, support email/hash, etc.). |
|---|
| 29 | +* <p> |
|---|
| 30 | +* Configuration: |
|---|
| 31 | +* - Reads server port from /securis-server.properties (key: "port"). |
|---|
| 32 | +* - Defaults to port 9997 when not present or on read errors. |
|---|
| 33 | +* - Constructs base URI as http://0.0.0.0:{port}/ with UriBuilder. |
|---|
| 34 | +* - Creates working directories under ${user.home}/.SeCuris on demand. |
|---|
| 35 | +* |
|---|
| 36 | +* Security note: |
|---|
| 37 | +* - getPassword/getFilePassword are simple helpers; secrets should be |
|---|
| 38 | +* managed via a secure vault/env vars in production. |
|---|
| 39 | +* |
|---|
| 40 | +* @author JRA |
|---|
| 41 | +* Last reviewed by JRA on Oct 7, 2025. |
|---|
| 42 | +*/ |
|---|
| 21 | 43 | public class SecurisModule extends AbstractModule { |
|---|
| 22 | 44 | |
|---|
| 23 | 45 | private static final int DEFAULT_PORT = 9997; |
|---|
| .. | .. |
|---|
| 25 | 47 | |
|---|
| 26 | 48 | private static final Logger LOG = LogManager.getLogger(SecurisModule.class); |
|---|
| 27 | 49 | |
|---|
| 50 | + /** configure<p>Currently no explicit bindings; providers below supply instances. */ |
|---|
| 28 | 51 | @Override |
|---|
| 29 | | - protected void configure() { |
|---|
| 52 | + protected void configure() { } |
|---|
| 30 | 53 | |
|---|
| 31 | | - } |
|---|
| 32 | | - |
|---|
| 54 | + /** |
|---|
| 55 | + * getPassword<p> |
|---|
| 56 | + * Composite password (example use with encrypted H2 URL). |
|---|
| 57 | + * |
|---|
| 58 | + * @return concatenated password string |
|---|
| 59 | + */ |
|---|
| 33 | 60 | public String getPassword() { |
|---|
| 34 | 61 | return getFilePassword() + " " + "53curi5"; |
|---|
| 35 | 62 | } |
|---|
| 36 | 63 | |
|---|
| 64 | + /** |
|---|
| 65 | + * getFilePassword<p> |
|---|
| 66 | + * Standalone file password (for H2 CIPHER). |
|---|
| 67 | + * |
|---|
| 68 | + * @return file password string |
|---|
| 69 | + */ |
|---|
| 37 | 70 | public String getFilePassword() { |
|---|
| 38 | 71 | return "cur151T"; |
|---|
| 39 | 72 | } |
|---|
| 40 | 73 | |
|---|
| 74 | + /** |
|---|
| 75 | + * getUrl<p> |
|---|
| 76 | + * H2 JDBC URL with AES cipher pointing to {appDir}/db/securis. |
|---|
| 77 | + * |
|---|
| 78 | + * @param appDir application working directory |
|---|
| 79 | + * @return JDBC URL (H2) |
|---|
| 80 | + */ |
|---|
| 41 | 81 | public String getUrl(File appDir) { |
|---|
| 42 | 82 | return String.format("jdbc:h2:%s/db/securis;CIPHER=AES", appDir.getAbsolutePath()); |
|---|
| 43 | 83 | } |
|---|
| 44 | 84 | |
|---|
| 85 | + /** |
|---|
| 86 | + * getBaseURI<p> |
|---|
| 87 | + * Provide the base URI for the HTTP server using configured or default port. |
|---|
| 88 | + * |
|---|
| 89 | + * @return base URI (http://0.0.0.0:{port}/) |
|---|
| 90 | + */ |
|---|
| 45 | 91 | @Named("base-uri") |
|---|
| 46 | 92 | @Provides |
|---|
| 47 | 93 | @ApplicationScoped |
|---|
| 48 | 94 | public URI getBaseURI() { |
|---|
| 49 | | - // Read from configuration, where? |
|---|
| 50 | 95 | try { |
|---|
| 51 | 96 | String url = MessageFormat.format("http://{0}/", "0.0.0.0"); |
|---|
| 52 | 97 | LOG.debug("Server url{}", url); |
|---|
| .. | .. |
|---|
| 56 | 101 | } |
|---|
| 57 | 102 | } |
|---|
| 58 | 103 | |
|---|
| 104 | + /** |
|---|
| 105 | + * getPort<p> |
|---|
| 106 | + * Read port from properties file or return default. |
|---|
| 107 | + * |
|---|
| 108 | + * @return HTTP port |
|---|
| 109 | + */ |
|---|
| 59 | 110 | private int getPort() { |
|---|
| 60 | 111 | Integer port; |
|---|
| 61 | 112 | Properties prop = new Properties(); |
|---|
| 62 | 113 | try { |
|---|
| 63 | 114 | prop.load(getClass().getResourceAsStream(PROPERTIES_FILE_NAME)); |
|---|
| 64 | 115 | port = Integer.valueOf(prop.getProperty("port")); |
|---|
| 65 | | - if (port == null) { |
|---|
| 66 | | - return DEFAULT_PORT; |
|---|
| 67 | | - } else { |
|---|
| 68 | | - return port; |
|---|
| 69 | | - } |
|---|
| 116 | + return (port == null ? DEFAULT_PORT : port); |
|---|
| 70 | 117 | } catch (Exception ex) { |
|---|
| 71 | 118 | return DEFAULT_PORT; |
|---|
| 72 | 119 | } |
|---|
| 73 | 120 | } |
|---|
| 74 | 121 | |
|---|
| 122 | + /** |
|---|
| 123 | + * getAppDbFiles<p> |
|---|
| 124 | + * List of SQL files to initialize the application DB. |
|---|
| 125 | + * |
|---|
| 126 | + * @return list of classpath resource paths |
|---|
| 127 | + */ |
|---|
| 75 | 128 | protected List<String> getAppDbFiles() { |
|---|
| 76 | | - |
|---|
| 77 | 129 | return Arrays.asList("/db/schema.sql"); |
|---|
| 78 | 130 | } |
|---|
| 79 | 131 | |
|---|
| 132 | + /** |
|---|
| 133 | + * getTemporaryDir<p> |
|---|
| 134 | + * Provide a temp directory inside the app working dir (.TEMP). |
|---|
| 135 | + * Creates it if missing and marks for deletion on exit. |
|---|
| 136 | + * |
|---|
| 137 | + * @return temp directory or null if creation failed |
|---|
| 138 | + */ |
|---|
| 80 | 139 | @Named("temporary-dir") |
|---|
| 81 | 140 | @Provides |
|---|
| 82 | 141 | @ApplicationScoped |
|---|
| 83 | 142 | public File getTemporaryDir() { |
|---|
| 84 | | - String tmp = getAppDir().getAbsolutePath(); |
|---|
| 85 | | - tmp += File.separator + ".TEMP"; |
|---|
| 143 | + String tmp = getAppDir().getAbsolutePath() + File.separator + ".TEMP"; |
|---|
| 86 | 144 | File ftmp = new File(tmp); |
|---|
| 87 | 145 | if (!ftmp.exists()) { |
|---|
| 88 | 146 | if (!ftmp.mkdirs()) { |
|---|
| .. | .. |
|---|
| 94 | 152 | return ftmp; |
|---|
| 95 | 153 | } |
|---|
| 96 | 154 | |
|---|
| 155 | + /** |
|---|
| 156 | + * getAppDir<p> |
|---|
| 157 | + * Provide the app working directory under ${user.home}/.SeCuris (creates if missing). |
|---|
| 158 | + * |
|---|
| 159 | + * @return working directory or null if creation failed |
|---|
| 160 | + */ |
|---|
| 97 | 161 | @Named("app-dir") |
|---|
| 98 | 162 | @Provides |
|---|
| 99 | 163 | @ApplicationScoped |
|---|
| 100 | 164 | public File getAppDir() { |
|---|
| 101 | 165 | String appDir = System.getProperty("user.home", System.getProperty("user.dir")); |
|---|
| 102 | | - if (appDir == null) { |
|---|
| 103 | | - appDir = "."; |
|---|
| 104 | | - } |
|---|
| 166 | + if (appDir == null) appDir = "."; |
|---|
| 105 | 167 | appDir += File.separator + ".SeCuris"; |
|---|
| 106 | 168 | File fAppDir = new File(appDir); |
|---|
| 107 | 169 | if (!fAppDir.exists()) { |
|---|
| .. | .. |
|---|
| 113 | 175 | return fAppDir; |
|---|
| 114 | 176 | } |
|---|
| 115 | 177 | |
|---|
| 178 | + /** |
|---|
| 179 | + * getSupportEmail<p> |
|---|
| 180 | + * Provide support email address. |
|---|
| 181 | + * |
|---|
| 182 | + * @return email |
|---|
| 183 | + */ |
|---|
| 116 | 184 | @Named("support-email") |
|---|
| 117 | 185 | @Provides |
|---|
| 118 | 186 | @ApplicationScoped |
|---|
| .. | .. |
|---|
| 120 | 188 | return "support@curisit.net"; |
|---|
| 121 | 189 | } |
|---|
| 122 | 190 | |
|---|
| 191 | + /** |
|---|
| 192 | + * getHashLogo<p> |
|---|
| 193 | + * Provide a static content hash for the logo (cache-busting or integrity). |
|---|
| 194 | + * |
|---|
| 195 | + * @return hex SHA-256 |
|---|
| 196 | + */ |
|---|
| 123 | 197 | @Named("hash-logo") |
|---|
| 124 | 198 | @Provides |
|---|
| 125 | 199 | @ApplicationScoped |
|---|
| .. | .. |
|---|
| 127 | 201 | return "1b42616809d4cd8ccf109e3c30d0ab25067f160b30b7354a08ddd563de0096ba"; |
|---|
| 128 | 202 | } |
|---|
| 129 | 203 | |
|---|
| 204 | + /** |
|---|
| 205 | + * getDbFiles<p> |
|---|
| 206 | + * Provide DB initialization files list (delegates to {@link #getAppDbFiles()}). |
|---|
| 207 | + * |
|---|
| 208 | + * @return list of SQL resource paths |
|---|
| 209 | + */ |
|---|
| 130 | 210 | @Named("db-files") |
|---|
| 131 | 211 | @Provides |
|---|
| 132 | 212 | @ApplicationScoped |
|---|
| 133 | 213 | public List<String> getDbFiles() { |
|---|
| 134 | 214 | return getAppDbFiles(); |
|---|
| 135 | 215 | } |
|---|
| 136 | | - |
|---|
| 137 | 216 | } |
|---|
| 217 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.security; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.security.Principal; |
|---|
| .. | .. |
|---|
| 9 | 12 | import net.curisit.integrity.commons.Utils; |
|---|
| 10 | 13 | import net.curisit.securis.db.User; |
|---|
| 11 | 14 | |
|---|
| 15 | +/** |
|---|
| 16 | +* BasicSecurityContext |
|---|
| 17 | +* <p> |
|---|
| 18 | +* Lightweight implementation of JAX-RS {@link SecurityContext} based on: |
|---|
| 19 | +* - A {@link Principal} holding the username. |
|---|
| 20 | +* - An integer bitmask of roles (see {@link User.Rol}). |
|---|
| 21 | +* - Optional scope restrictions (organization/application IDs). |
|---|
| 22 | +* |
|---|
| 23 | +* Role checks: |
|---|
| 24 | +* - {@link #isUserInRole(String)} maps string names to bit constants via {@link #ROLES}. |
|---|
| 25 | +* |
|---|
| 26 | +* Scope helpers: |
|---|
| 27 | +* - {@link #isOrgAccesible(Integer)} and {@link #isAppAccesible(Integer)}. |
|---|
| 28 | +* |
|---|
| 29 | +* @author JRA |
|---|
| 30 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 31 | +*/ |
|---|
| 12 | 32 | public class BasicSecurityContext implements SecurityContext { |
|---|
| 13 | 33 | |
|---|
| 14 | | - final public static String ROL_ADVANCE = "advance"; |
|---|
| 15 | | - final public static String ROL_ADMIN = "admin"; |
|---|
| 16 | | - final public static String ROL_BASIC = "basic"; |
|---|
| 34 | + /** String role names mapped to bit flags. */ |
|---|
| 35 | + public static final String ROL_ADVANCE = "advance"; |
|---|
| 36 | + public static final String ROL_ADMIN = "admin"; |
|---|
| 37 | + public static final String ROL_BASIC = "basic"; |
|---|
| 17 | 38 | |
|---|
| 18 | | - final static Map<String, Integer> ROLES = Utils.<String, Integer> createMap(ROL_BASIC, User.Rol.BASIC, ROL_ADVANCE, User.Rol.ADVANCE, ROL_ADMIN, User.Rol.ADMIN); |
|---|
| 39 | + /** Mapping from role name to bit flag. */ |
|---|
| 40 | + static final Map<String, Integer> ROLES = |
|---|
| 41 | + Utils.<String, Integer>createMap(ROL_BASIC, User.Rol.BASIC, |
|---|
| 42 | + ROL_ADVANCE, User.Rol.ADVANCE, |
|---|
| 43 | + ROL_ADMIN, User.Rol.ADMIN); |
|---|
| 19 | 44 | |
|---|
| 20 | | - Principal user = null; |
|---|
| 21 | | - int roles = 0; |
|---|
| 22 | | - boolean secure = false; |
|---|
| 23 | | - Set<Integer> organizationsIds = null; |
|---|
| 24 | | - Set<Integer> applicationsIds = null; |
|---|
| 25 | | - double ran = 0; |
|---|
| 45 | + Principal user = null; |
|---|
| 46 | + int roles = 0; |
|---|
| 47 | + boolean secure = false; |
|---|
| 48 | + Set<Integer> organizationsIds = null; |
|---|
| 49 | + Set<Integer> applicationsIds = null; |
|---|
| 50 | + double ran = 0; // small unique marker for debugging instances |
|---|
| 26 | 51 | |
|---|
| 27 | | - public BasicSecurityContext(String username, int roles, boolean secure) { |
|---|
| 28 | | - user = new UserPrincipal(username); |
|---|
| 29 | | - this.roles = roles; |
|---|
| 30 | | - this.secure = secure; |
|---|
| 31 | | - ran = Math.random(); |
|---|
| 32 | | - } |
|---|
| 52 | + /** |
|---|
| 53 | + * BasicSecurityContext<p> |
|---|
| 54 | + * Construct a context for given user, roles and transport security flag. |
|---|
| 55 | + * |
|---|
| 56 | + * @param username principal name |
|---|
| 57 | + * @param roles bitmask of roles |
|---|
| 58 | + * @param secure whether the request is HTTPS |
|---|
| 59 | + */ |
|---|
| 60 | + public BasicSecurityContext(String username, int roles, boolean secure) { |
|---|
| 61 | + user = new UserPrincipal(username); |
|---|
| 62 | + this.roles = roles; |
|---|
| 63 | + this.secure = secure; |
|---|
| 64 | + ran = Math.random(); |
|---|
| 65 | + } |
|---|
| 33 | 66 | |
|---|
| 34 | | - @Override |
|---|
| 35 | | - public Principal getUserPrincipal() { |
|---|
| 36 | | - return user; |
|---|
| 37 | | - } |
|---|
| 67 | + /** |
|---|
| 68 | + * getUserPrincipal<p> |
|---|
| 69 | + * Return the user principal. |
|---|
| 70 | + * |
|---|
| 71 | + * @return mainUser |
|---|
| 72 | + */ |
|---|
| 73 | + @Override |
|---|
| 74 | + public Principal getUserPrincipal() { return user; } |
|---|
| 38 | 75 | |
|---|
| 39 | | - @Override |
|---|
| 40 | | - public boolean isUserInRole(String role) { |
|---|
| 41 | | - Integer introle = ROLES.get(role); |
|---|
| 42 | | - return introle != null && (introle & roles) != 0; |
|---|
| 43 | | - } |
|---|
| 76 | + /** |
|---|
| 77 | + * isUserInRole<p> |
|---|
| 78 | + * Check role membership by name (mapped to bitmask). |
|---|
| 79 | + * |
|---|
| 80 | + * @param role |
|---|
| 81 | + * @return isUserInRole |
|---|
| 82 | + */ |
|---|
| 83 | + @Override |
|---|
| 84 | + public boolean isUserInRole(String role) { |
|---|
| 85 | + Integer introle = ROLES.get(role); |
|---|
| 86 | + return introle != null && (introle & roles) != 0; |
|---|
| 87 | + } |
|---|
| 44 | 88 | |
|---|
| 45 | | - @Override |
|---|
| 46 | | - public boolean isSecure() { |
|---|
| 47 | | - return secure; |
|---|
| 48 | | - } |
|---|
| 89 | + /** |
|---|
| 90 | + * isSecure<p> |
|---|
| 91 | + * Return whether transport is secure (HTTPS). |
|---|
| 92 | + * |
|---|
| 93 | + * @return isSecure |
|---|
| 94 | + */ |
|---|
| 95 | + @Override |
|---|
| 96 | + public boolean isSecure() { return secure; } |
|---|
| 49 | 97 | |
|---|
| 50 | | - @Override |
|---|
| 51 | | - public String getAuthenticationScheme() { |
|---|
| 52 | | - return null; |
|---|
| 53 | | - } |
|---|
| 98 | + /** |
|---|
| 99 | + * getAuthenticationScheme<p> |
|---|
| 100 | + * Not used; returns null. |
|---|
| 101 | + * |
|---|
| 102 | + * @return authenticationsScheme |
|---|
| 103 | + */ |
|---|
| 104 | + @Override |
|---|
| 105 | + public String getAuthenticationScheme() { return null; } |
|---|
| 54 | 106 | |
|---|
| 55 | | - @Override |
|---|
| 56 | | - public String toString() { |
|---|
| 107 | + /** |
|---|
| 108 | + * toString<p> |
|---|
| 109 | + * Get the string describing the current object |
|---|
| 110 | + * |
|---|
| 111 | + * @return object string |
|---|
| 112 | + */ |
|---|
| 113 | + @Override |
|---|
| 114 | + public String toString() { return String.format("SecurityContextWrapper(%f) %s", ran, user); } |
|---|
| 57 | 115 | |
|---|
| 58 | | - return String.format("SecurityContextWrapper(%f) %s", ran, user); |
|---|
| 59 | | - } |
|---|
| 116 | + /** |
|---|
| 117 | + * setOrganizationsIds<p> |
|---|
| 118 | + * Set org scope (IDs allowed). |
|---|
| 119 | + * |
|---|
| 120 | + * @param organizationsIds |
|---|
| 121 | + */ |
|---|
| 122 | + public void setOrganizationsIds(Set<Integer> orgs) { this.organizationsIds = orgs; } |
|---|
| 60 | 123 | |
|---|
| 61 | | - public void setOrganizationsIds(Set<Integer> orgs) { |
|---|
| 62 | | - this.organizationsIds = orgs; |
|---|
| 63 | | - } |
|---|
| 124 | + /** |
|---|
| 125 | + * getOrganizationsIds<p> |
|---|
| 126 | + * Return org scope. |
|---|
| 127 | + * |
|---|
| 128 | + * @return organizationsIds |
|---|
| 129 | + */ |
|---|
| 130 | + public Set<Integer> getOrganizationsIds() { return this.organizationsIds; } |
|---|
| 64 | 131 | |
|---|
| 65 | | - public Set<Integer> getOrganizationsIds() { |
|---|
| 66 | | - return this.organizationsIds; |
|---|
| 67 | | - } |
|---|
| 132 | + /** |
|---|
| 133 | + * getApplicationsIds<p> |
|---|
| 134 | + * Return app scope. |
|---|
| 135 | + * |
|---|
| 136 | + * @return applicationIds |
|---|
| 137 | + */ |
|---|
| 138 | + public Set<Integer> getApplicationsIds() { return applicationsIds; } |
|---|
| 68 | 139 | |
|---|
| 69 | | - public Set<Integer> getApplicationsIds() { |
|---|
| 70 | | - return applicationsIds; |
|---|
| 71 | | - } |
|---|
| 140 | + /** |
|---|
| 141 | + * setApplicationsIds<p> |
|---|
| 142 | + * Set app scope. |
|---|
| 143 | + * |
|---|
| 144 | + * @param applicationIds |
|---|
| 145 | + */ |
|---|
| 146 | + public void setApplicationsIds(Set<Integer> applicationsIds) { this.applicationsIds = applicationsIds; } |
|---|
| 72 | 147 | |
|---|
| 73 | | - public void setApplicationsIds(Set<Integer> applicationsIds) { |
|---|
| 74 | | - this.applicationsIds = applicationsIds; |
|---|
| 75 | | - } |
|---|
| 148 | + /** |
|---|
| 149 | + * UserPrincipal<p> |
|---|
| 150 | + * Inner Principal holding only the username. |
|---|
| 151 | + */ |
|---|
| 152 | + private class UserPrincipal implements Principal { |
|---|
| 153 | + final String name; |
|---|
| 154 | + |
|---|
| 155 | + /** |
|---|
| 156 | + * UserPrincipal<p> |
|---|
| 157 | + * Main user |
|---|
| 158 | + * |
|---|
| 159 | + * @param username |
|---|
| 160 | + */ |
|---|
| 161 | + public UserPrincipal(String name) { this.name = name; } |
|---|
| 162 | + |
|---|
| 163 | + /** |
|---|
| 164 | + * getName<p> |
|---|
| 165 | + * Get the username |
|---|
| 166 | + * |
|---|
| 167 | + * @return userName |
|---|
| 168 | + */ |
|---|
| 169 | + @Override public String getName() { return this.name; } |
|---|
| 170 | + |
|---|
| 171 | + /** |
|---|
| 172 | + * toString<p> |
|---|
| 173 | + * Get the string describing the current object |
|---|
| 174 | + * |
|---|
| 175 | + * @return object string |
|---|
| 176 | + */ |
|---|
| 177 | + @Override public String toString() { return String.format("[%s]", name); } |
|---|
| 178 | + } |
|---|
| 76 | 179 | |
|---|
| 77 | | - private class UserPrincipal implements Principal { |
|---|
| 180 | + /** |
|---|
| 181 | + * isOrgAccesible<p> |
|---|
| 182 | + * Check if org id is within scope. |
|---|
| 183 | + * |
|---|
| 184 | + * @param orgId |
|---|
| 185 | + * @return isOrgAccesible |
|---|
| 186 | + */ |
|---|
| 187 | + public boolean isOrgAccesible(Integer orgid) { |
|---|
| 188 | + if (organizationsIds == null || orgid == null) return false; |
|---|
| 189 | + return organizationsIds.contains(orgid); |
|---|
| 190 | + } |
|---|
| 78 | 191 | |
|---|
| 79 | | - final String name; |
|---|
| 80 | | - |
|---|
| 81 | | - public UserPrincipal(String name) { |
|---|
| 82 | | - this.name = name; |
|---|
| 83 | | - } |
|---|
| 84 | | - |
|---|
| 85 | | - @Override |
|---|
| 86 | | - public String getName() { |
|---|
| 87 | | - return this.name; |
|---|
| 88 | | - } |
|---|
| 89 | | - |
|---|
| 90 | | - @Override |
|---|
| 91 | | - public String toString() { |
|---|
| 92 | | - return String.format("[%s]", name); |
|---|
| 93 | | - } |
|---|
| 94 | | - |
|---|
| 95 | | - } |
|---|
| 96 | | - |
|---|
| 97 | | - public boolean isOrgAccesible(Integer orgid) { |
|---|
| 98 | | - if (organizationsIds == null || orgid == null) { |
|---|
| 99 | | - return false; |
|---|
| 100 | | - } |
|---|
| 101 | | - return organizationsIds.contains(orgid); |
|---|
| 102 | | - } |
|---|
| 103 | | - |
|---|
| 104 | | - public boolean isAppAccesible(Integer appid) { |
|---|
| 105 | | - if (applicationsIds == null || appid == null) { |
|---|
| 106 | | - return false; |
|---|
| 107 | | - } |
|---|
| 108 | | - return applicationsIds.contains(appid); |
|---|
| 109 | | - } |
|---|
| 110 | | - |
|---|
| 192 | + /** |
|---|
| 193 | + * isAppAccesible<p> |
|---|
| 194 | + * Check if app id is within scope. |
|---|
| 195 | + * |
|---|
| 196 | + * @param appId |
|---|
| 197 | + * @return isAppAccesible |
|---|
| 198 | + */ |
|---|
| 199 | + public boolean isAppAccesible(Integer appid) { |
|---|
| 200 | + if (applicationsIds == null || appid == null) return false; |
|---|
| 201 | + return applicationsIds.contains(appid); |
|---|
| 202 | + } |
|---|
| 111 | 203 | } |
|---|
| 204 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.security; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.lang.annotation.ElementType; |
|---|
| .. | .. |
|---|
| 7 | 10 | |
|---|
| 8 | 11 | import net.curisit.securis.utils.TokenHelper; |
|---|
| 9 | 12 | |
|---|
| 13 | +/** |
|---|
| 14 | +* Securable |
|---|
| 15 | +* <p> |
|---|
| 16 | +* Method-level annotation to declare security requirements: |
|---|
| 17 | +* - {@link #header()} name containing the auth token (defaults to {@link TokenHelper#TOKEN_HEADER_PÀRAM}). |
|---|
| 18 | +* - {@link #roles()} required role bitmask; {@code 0} means no role restriction. |
|---|
| 19 | +* |
|---|
| 20 | +* Intended to be enforced by request filters/interceptors (e.g., RequestsInterceptor). |
|---|
| 21 | +* |
|---|
| 22 | +* @author JRA |
|---|
| 23 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 24 | +*/ |
|---|
| 10 | 25 | @Retention(RetentionPolicy.RUNTIME) |
|---|
| 11 | 26 | @Target(ElementType.METHOD) |
|---|
| 12 | 27 | public @interface Securable { |
|---|
| 13 | | - /** |
|---|
| 14 | | - * Name of header parameter with the auth token to validate |
|---|
| 15 | | - */ |
|---|
| 28 | + |
|---|
| 29 | + /** Header name carrying the token to validate. */ |
|---|
| 16 | 30 | String header() default TokenHelper.TOKEN_HEADER_PÀRAM; |
|---|
| 17 | 31 | |
|---|
| 18 | | - /** |
|---|
| 19 | | - * Bit mask with the rol or roles necessary to access the method |
|---|
| 20 | | - */ |
|---|
| 32 | + /** Bitmask of required roles; set 0 for public endpoints (token still may be required). */ |
|---|
| 21 | 33 | int roles() default 0; |
|---|
| 22 | 34 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.services; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.IOException; |
|---|
| .. | .. |
|---|
| 49 | 52 | import net.curisit.securis.utils.TokenHelper; |
|---|
| 50 | 53 | |
|---|
| 51 | 54 | /** |
|---|
| 52 | | - * External API to be accessed by third parties |
|---|
| 53 | | - * |
|---|
| 54 | | - * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 55 | | - */ |
|---|
| 55 | +* ApiResource |
|---|
| 56 | +* <p> |
|---|
| 57 | +* External API for license operations, intended for third-party clients. |
|---|
| 58 | +* |
|---|
| 59 | +* Endpoints: |
|---|
| 60 | +* - GET /api/ -> Plain-text status with date (health check). |
|---|
| 61 | +* - GET /api/ping -> JSON status (message + date). |
|---|
| 62 | +* - POST /api/request -> Create license from RequestBean (JSON). |
|---|
| 63 | +* - POST /api/request -> Create license from request file (multipart). |
|---|
| 64 | +* - POST /api/renew -> Renew from previous LicenseBean (JSON). |
|---|
| 65 | +* - POST /api/renew -> Renew from previous license file (multipart). |
|---|
| 66 | +* - POST /api/validate -> Server-side validation of a license. |
|---|
| 67 | +* |
|---|
| 68 | +* Security: |
|---|
| 69 | +* - Methods that mutate/inspect licenses require {@link Securable} with role {@link Rol#API_CLIENT}. |
|---|
| 70 | +* - {@link EnsureTransaction} ensures transaction handling at the filter/interceptor layer. |
|---|
| 71 | +* |
|---|
| 72 | +* Errors: |
|---|
| 73 | +* - Business errors are mapped to {@link SeCurisServiceException} with {@link ErrorCodes}. |
|---|
| 74 | +* |
|---|
| 75 | +* @author JRA |
|---|
| 76 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 77 | +*/ |
|---|
| 56 | 78 | @Path("/api") |
|---|
| 57 | 79 | public class ApiResource { |
|---|
| 58 | 80 | |
|---|
| 59 | | - private static final Logger LOG = LogManager.getLogger(ApiResource.class); |
|---|
| 81 | + private static final Logger LOG = LogManager.getLogger(ApiResource.class); |
|---|
| 60 | 82 | |
|---|
| 61 | | - @Inject |
|---|
| 62 | | - TokenHelper tokenHelper; |
|---|
| 83 | + @Inject TokenHelper tokenHelper; |
|---|
| 84 | + @Inject private LicenseHelper licenseHelper; |
|---|
| 85 | + @Context EntityManager em; |
|---|
| 86 | + @Inject LicenseGenerator licenseGenerator; |
|---|
| 63 | 87 | |
|---|
| 64 | | - @Inject |
|---|
| 65 | | - private LicenseHelper licenseHelper; |
|---|
| 88 | + /** Fixed username representing API client actor for audit trails. */ |
|---|
| 89 | + public static final String API_CLIENT_USERNAME = "_client"; |
|---|
| 66 | 90 | |
|---|
| 67 | | - @Context |
|---|
| 68 | | - EntityManager em; |
|---|
| 91 | + /** Default constructor (required by JAX-RS). */ |
|---|
| 92 | + public ApiResource() { } |
|---|
| 69 | 93 | |
|---|
| 70 | | - @Inject |
|---|
| 71 | | - LicenseGenerator licenseGenerator; |
|---|
| 94 | + // -------------------- Health checks -------------------- |
|---|
| 72 | 95 | |
|---|
| 73 | | - public static final String API_CLIENT_USERNAME = "_client"; |
|---|
| 96 | + /** |
|---|
| 97 | + * index<p> |
|---|
| 98 | + * Plain text endpoint to verify API is reachable. |
|---|
| 99 | + * |
|---|
| 100 | + * @return 200 OK with simple message |
|---|
| 101 | + */ |
|---|
| 102 | + @GET |
|---|
| 103 | + @Path("/") |
|---|
| 104 | + @Produces({ MediaType.TEXT_PLAIN }) |
|---|
| 105 | + public Response index() { |
|---|
| 106 | + return Response.ok("SeCuris API. Date: " + new Date()).build(); |
|---|
| 107 | + } |
|---|
| 74 | 108 | |
|---|
| 75 | | - public ApiResource() { |
|---|
| 76 | | - } |
|---|
| 109 | + /** |
|---|
| 110 | + * ping<p> |
|---|
| 111 | + * JSON endpoint for health checks. |
|---|
| 112 | + * |
|---|
| 113 | + * @return 200 OK with {@link StatusBean} |
|---|
| 114 | + */ |
|---|
| 115 | + @GET |
|---|
| 116 | + @Path("/ping") |
|---|
| 117 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 118 | + public Response ping() { |
|---|
| 119 | + StatusBean status = new StatusBean(); |
|---|
| 120 | + status.setDate(new Date()); |
|---|
| 121 | + status.setMessage(LicenseManager.PING_MESSAGE); |
|---|
| 122 | + return Response.ok(status).build(); |
|---|
| 123 | + } |
|---|
| 77 | 124 | |
|---|
| 78 | | - /** |
|---|
| 79 | | - * |
|---|
| 80 | | - * @return Simple text message to check API status |
|---|
| 81 | | - */ |
|---|
| 82 | | - @GET |
|---|
| 83 | | - @Path("/") |
|---|
| 84 | | - @Produces({ MediaType.TEXT_PLAIN }) |
|---|
| 85 | | - public Response index() { |
|---|
| 86 | | - return Response.ok("SeCuris API. Date: " + new Date()).build(); |
|---|
| 87 | | - } |
|---|
| 125 | + // -------------------- License creation -------------------- |
|---|
| 88 | 126 | |
|---|
| 89 | | - /** |
|---|
| 90 | | - * |
|---|
| 91 | | - * @return Simple text message to check API status |
|---|
| 92 | | - */ |
|---|
| 93 | | - @GET |
|---|
| 94 | | - @Path("/ping") |
|---|
| 95 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 96 | | - public Response ping() { |
|---|
| 97 | | - StatusBean status = new StatusBean(); |
|---|
| 98 | | - status.setDate(new Date()); |
|---|
| 99 | | - status.setMessage(LicenseManager.PING_MESSAGE); |
|---|
| 100 | | - return Response.ok(status).build(); |
|---|
| 101 | | - } |
|---|
| 127 | + /** |
|---|
| 128 | + * createFromRequest<p> |
|---|
| 129 | + * Create a new license from JSON request data. |
|---|
| 130 | + * |
|---|
| 131 | + * @param request RequestBean payload |
|---|
| 132 | + * @param nameOrReference Holder name or external reference (header) |
|---|
| 133 | + * @param userEmail Email (header) |
|---|
| 134 | + * @return {@link SignedLicenseBean} JSON |
|---|
| 135 | + */ |
|---|
| 136 | + @POST |
|---|
| 137 | + @Path("/request") |
|---|
| 138 | + @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 139 | + @Securable(roles = Rol.API_CLIENT) |
|---|
| 140 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 141 | + @EnsureTransaction |
|---|
| 142 | + public Response createFromRequest(RequestBean request, |
|---|
| 143 | + @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference, |
|---|
| 144 | + @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail) |
|---|
| 145 | + throws IOException, SeCurisServiceException, SeCurisException { |
|---|
| 146 | + LOG.info("Request to get license: {}", request); |
|---|
| 147 | + SignedLicenseBean lic = createLicense(request, em, nameOrReference, userEmail); |
|---|
| 148 | + return Response.ok(lic).build(); |
|---|
| 149 | + } |
|---|
| 102 | 150 | |
|---|
| 103 | | - /** |
|---|
| 104 | | - * Request a new license file based in a RequestBean object sent as |
|---|
| 105 | | - * parameter |
|---|
| 106 | | - * |
|---|
| 107 | | - * @param mpfdi |
|---|
| 108 | | - * @param bsc |
|---|
| 109 | | - * @return |
|---|
| 110 | | - * @throws IOException |
|---|
| 111 | | - * @throws SeCurisServiceException |
|---|
| 112 | | - */ |
|---|
| 113 | | - @POST |
|---|
| 114 | | - @Path("/request") |
|---|
| 115 | | - @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 116 | | - @Securable(roles = Rol.API_CLIENT) |
|---|
| 117 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 118 | | - @EnsureTransaction |
|---|
| 119 | | - public Response createFromRequest(RequestBean request, @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference, |
|---|
| 120 | | - @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail) throws IOException, SeCurisServiceException, SeCurisException { |
|---|
| 121 | | - LOG.info("Request to get license: {}", request); |
|---|
| 122 | | - SignedLicenseBean lic = createLicense(request, em, nameOrReference, userEmail); |
|---|
| 151 | + /** |
|---|
| 152 | + * createFromRequestFile<p> |
|---|
| 153 | + * Create a new license from a multipart form (uploaded request fields). |
|---|
| 154 | + * |
|---|
| 155 | + * @param mpfdi multipart input |
|---|
| 156 | + * @param nameOrReference holder name/reference (header) |
|---|
| 157 | + * @param userEmail email (header) |
|---|
| 158 | + * @return {@link SignedLicenseBean} JSON |
|---|
| 159 | + */ |
|---|
| 160 | + @POST |
|---|
| 161 | + @Path("/request") |
|---|
| 162 | + @Consumes(MediaType.MULTIPART_FORM_DATA) |
|---|
| 163 | + @Securable(roles = Rol.API_CLIENT) |
|---|
| 164 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 165 | + @EnsureTransaction |
|---|
| 166 | + @SuppressWarnings("unchecked") |
|---|
| 167 | + public Response createFromRequestFile(MultipartFormDataInput mpfdi, |
|---|
| 168 | + @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference, |
|---|
| 169 | + @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail) |
|---|
| 170 | + throws IOException, SeCurisServiceException, SeCurisException { |
|---|
| 171 | + RequestBean req = new RequestBean(); |
|---|
| 172 | + req.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null)); |
|---|
| 173 | + req.setActivationCode(mpfdi.getFormDataPart("activationCode", String.class, null)); |
|---|
| 174 | + req.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null)); |
|---|
| 175 | + req.setLicenseTypeCode(mpfdi.getFormDataPart("licenseTypeCode", String.class, null)); |
|---|
| 176 | + req.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null)); |
|---|
| 177 | + req.setArch(mpfdi.getFormDataPart("arch", String.class, null)); |
|---|
| 178 | + req.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null)); |
|---|
| 179 | + req.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null)); |
|---|
| 180 | + req.setOsName(mpfdi.getFormDataPart("osName", String.class, null)); |
|---|
| 123 | 181 | |
|---|
| 124 | | - return Response.ok(lic).build(); |
|---|
| 125 | | - } |
|---|
| 182 | + return createFromRequest(req, nameOrReference, userEmail); |
|---|
| 183 | + } |
|---|
| 126 | 184 | |
|---|
| 127 | | - /** |
|---|
| 128 | | - * Returns a License file in JSON format from an uploaded Request file |
|---|
| 129 | | - * |
|---|
| 130 | | - * @param mpfdi |
|---|
| 131 | | - * @param bsc |
|---|
| 132 | | - * @return |
|---|
| 133 | | - * @throws IOException |
|---|
| 134 | | - * @throws SeCurisServiceException |
|---|
| 135 | | - * @throws SeCurisException |
|---|
| 136 | | - */ |
|---|
| 137 | | - @POST |
|---|
| 138 | | - @Path("/request") |
|---|
| 139 | | - @Consumes(MediaType.MULTIPART_FORM_DATA) |
|---|
| 140 | | - @Securable(roles = Rol.API_CLIENT) |
|---|
| 141 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 142 | | - @EnsureTransaction |
|---|
| 143 | | - @SuppressWarnings("unchecked") |
|---|
| 144 | | - public Response createFromRequestFile(MultipartFormDataInput mpfdi, @HeaderParam(LicenseManager.HEADER_LICENSE_NAME_OR_REFERENCE) String nameOrReference, |
|---|
| 145 | | - @HeaderParam(LicenseManager.HEADER_LICENSE_EMAIL) String userEmail) throws IOException, SeCurisServiceException, SeCurisException { |
|---|
| 146 | | - RequestBean req = new RequestBean(); |
|---|
| 147 | | - req.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null)); |
|---|
| 148 | | - req.setActivationCode(mpfdi.getFormDataPart("activationCode", String.class, null)); |
|---|
| 149 | | - req.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null)); |
|---|
| 150 | | - req.setLicenseTypeCode(mpfdi.getFormDataPart("licenseTypeCode", String.class, null)); |
|---|
| 151 | | - req.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null)); |
|---|
| 152 | | - req.setArch(mpfdi.getFormDataPart("arch", String.class, null)); |
|---|
| 153 | | - req.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null)); |
|---|
| 154 | | - req.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null)); |
|---|
| 155 | | - req.setOsName(mpfdi.getFormDataPart("osName", String.class, null)); |
|---|
| 185 | + // -------------------- License renew -------------------- |
|---|
| 156 | 186 | |
|---|
| 157 | | - return createFromRequest(req, nameOrReference, userEmail); |
|---|
| 158 | | - } |
|---|
| 187 | + /** |
|---|
| 188 | + * renewFromPreviousLicense<p> |
|---|
| 189 | + * Renew a license from an existing {@link LicenseBean} JSON payload. |
|---|
| 190 | + * Only <b>Active</b> licenses within one month of expiration are eligible. |
|---|
| 191 | + * |
|---|
| 192 | + * @param previousLic current license bean |
|---|
| 193 | + * @param bsc security context |
|---|
| 194 | + * @return new {@link SignedLicenseBean} |
|---|
| 195 | + */ |
|---|
| 196 | + @POST |
|---|
| 197 | + @Path("/renew") |
|---|
| 198 | + @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 199 | + @Securable(roles = Rol.API_CLIENT) |
|---|
| 200 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 201 | + @EnsureTransaction |
|---|
| 202 | + public Response renewFromPreviousLicense(LicenseBean previousLic, @Context BasicSecurityContext bsc) |
|---|
| 203 | + throws IOException, SeCurisServiceException, SeCurisException { |
|---|
| 204 | + LOG.info("Renew license: {}", previousLic); |
|---|
| 159 | 205 | |
|---|
| 160 | | - /** |
|---|
| 161 | | - * Create a new License file based in a previous one |
|---|
| 162 | | - * |
|---|
| 163 | | - * @param request |
|---|
| 164 | | - * @param bsc |
|---|
| 165 | | - * @return |
|---|
| 166 | | - * @throws IOException |
|---|
| 167 | | - * @throws SeCurisServiceException |
|---|
| 168 | | - * @throws SeCurisException |
|---|
| 169 | | - */ |
|---|
| 170 | | - @POST |
|---|
| 171 | | - @Path("/renew") |
|---|
| 172 | | - @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 173 | | - @Securable(roles = Rol.API_CLIENT) |
|---|
| 174 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 175 | | - @EnsureTransaction |
|---|
| 176 | | - public Response renewFromPreviousLicense(LicenseBean previousLic, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException { |
|---|
| 177 | | - LOG.info("Renew license: {}", previousLic); |
|---|
| 206 | + if (previousLic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) { |
|---|
| 207 | + throw new SeCurisServiceException(ErrorCodes.UNNECESSARY_RENEW, "The license is still valid, not ready for renew"); |
|---|
| 208 | + } |
|---|
| 178 | 209 | |
|---|
| 179 | | - if (previousLic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) { |
|---|
| 180 | | - throw new SeCurisServiceException(ErrorCodes.UNNECESSARY_RENEW, "The license is still valid, not ready for renew"); |
|---|
| 181 | | - } |
|---|
| 210 | + License lic = License.findLicenseByCode(previousLic.getLicenseCode(), em); |
|---|
| 211 | + if (lic == null) { |
|---|
| 212 | + throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license is missing in DB"); |
|---|
| 213 | + } |
|---|
| 214 | + if (lic.getStatus() != LicenseStatus.ACTIVE) { |
|---|
| 215 | + throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "Only licenses with status 'Active' can be renew"); |
|---|
| 216 | + } |
|---|
| 182 | 217 | |
|---|
| 183 | | - // EntityManager em = emProvider.get(); |
|---|
| 184 | | - License lic = License.findLicenseByCode(previousLic.getLicenseCode(), em); |
|---|
| 185 | | - if (lic == null) { |
|---|
| 186 | | - throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license is missing in DB"); |
|---|
| 187 | | - } |
|---|
| 218 | + SignedLicenseBean signedLic = renewLicense(previousLic, em); |
|---|
| 219 | + LOG.info("Renewed license code: {}, until: {}", signedLic.getLicenseCode(), signedLic.getExpirationDate()); |
|---|
| 188 | 220 | |
|---|
| 189 | | - if (lic.getStatus() != LicenseStatus.ACTIVE) { |
|---|
| 190 | | - throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "Only licenses with status 'Active' can be renew"); |
|---|
| 191 | | - } |
|---|
| 221 | + return Response.ok(signedLic).build(); |
|---|
| 222 | + } |
|---|
| 192 | 223 | |
|---|
| 193 | | - SignedLicenseBean signedLic = renewLicense(previousLic, em); |
|---|
| 194 | | - LOG.info("Renewed license code: {}, until: {}", signedLic.getLicenseCode(), signedLic.getExpirationDate()); |
|---|
| 224 | + /** |
|---|
| 225 | + * renewFromLicenseFile<p> |
|---|
| 226 | + * Renew a license from multipart (uploaded prior license fields). |
|---|
| 227 | + * |
|---|
| 228 | + * @param mpfdi multipart input |
|---|
| 229 | + * @param bsc security context |
|---|
| 230 | + * @return new {@link SignedLicenseBean} |
|---|
| 231 | + */ |
|---|
| 232 | + @POST |
|---|
| 233 | + @Path("/renew") |
|---|
| 234 | + @Consumes(MediaType.MULTIPART_FORM_DATA) |
|---|
| 235 | + @Securable(roles = Rol.API_CLIENT) |
|---|
| 236 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 237 | + @EnsureTransaction |
|---|
| 238 | + @SuppressWarnings("unchecked") |
|---|
| 239 | + public Response renewFromLicenseFile(MultipartFormDataInput mpfdi, @Context BasicSecurityContext bsc) |
|---|
| 240 | + throws IOException, SeCurisServiceException, SeCurisException { |
|---|
| 241 | + LicenseBean lic = new LicenseBean(); |
|---|
| 195 | 242 | |
|---|
| 196 | | - return Response.ok(signedLic).build(); |
|---|
| 197 | | - } |
|---|
| 243 | + lic.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null)); |
|---|
| 244 | + lic.setActivationCode(mpfdi.getFormDataPart("activationName", String.class, null)); |
|---|
| 245 | + lic.setAppName(mpfdi.getFormDataPart("appName", String.class, null)); |
|---|
| 246 | + lic.setArch(mpfdi.getFormDataPart("arch", String.class, null)); |
|---|
| 247 | + lic.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null)); |
|---|
| 248 | + lic.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null)); |
|---|
| 249 | + lic.setLicenseTypeCode(mpfdi.getFormDataPart("licenseCode", String.class, null)); |
|---|
| 250 | + lic.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null)); |
|---|
| 251 | + lic.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null)); |
|---|
| 252 | + lic.setOsName(mpfdi.getFormDataPart("osName", String.class, null)); |
|---|
| 253 | + lic.setExpirationDate(mpfdi.getFormDataPart("expirationDate", Date.class, null)); |
|---|
| 198 | 254 | |
|---|
| 199 | | - /** |
|---|
| 200 | | - * License validation on server side, in this case we validate that the |
|---|
| 201 | | - * current licenses has not been cancelled and they are still in valid |
|---|
| 202 | | - * period. If the pack has reached the end valid period, the license is no |
|---|
| 203 | | - * longer valid. |
|---|
| 204 | | - * |
|---|
| 205 | | - * @param currentLic |
|---|
| 206 | | - * @param bsc |
|---|
| 207 | | - * @return |
|---|
| 208 | | - * @throws IOException |
|---|
| 209 | | - * @throws SeCurisServiceException |
|---|
| 210 | | - * @throws SeCurisException |
|---|
| 211 | | - */ |
|---|
| 212 | | - @POST |
|---|
| 213 | | - @Path("/validate") |
|---|
| 214 | | - @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 215 | | - @Securable(roles = Rol.API_CLIENT) |
|---|
| 216 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 217 | | - @EnsureTransaction |
|---|
| 218 | | - public Response validate(LicenseBean currentLic, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException { |
|---|
| 219 | | - LOG.info("Validate license: {}", currentLic); |
|---|
| 255 | + LOG.info("Lic expires at: {}", lic.getExpirationDate()); |
|---|
| 256 | + if (lic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) { |
|---|
| 257 | + throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "The license is still valid, not ready for renew"); |
|---|
| 258 | + } |
|---|
| 220 | 259 | |
|---|
| 221 | | - if (currentLic.getExpirationDate().before(new Date())) { |
|---|
| 222 | | - throw new SeCurisServiceException(ErrorCodes.LICENSE_IS_EXPIRED, "The license is expired"); |
|---|
| 223 | | - } |
|---|
| 260 | + return renewFromPreviousLicense(lic, bsc); |
|---|
| 261 | + } |
|---|
| 224 | 262 | |
|---|
| 225 | | - License existingLic = licenseHelper.getActiveLicenseFromDB(currentLic, em); |
|---|
| 263 | + // -------------------- Validation -------------------- |
|---|
| 226 | 264 | |
|---|
| 227 | | - Pack pack = existingLic.getPack(); |
|---|
| 228 | | - if (pack.getEndValidDate().before(new Date())) { |
|---|
| 229 | | - throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack end valid date has been reached"); |
|---|
| 230 | | - } |
|---|
| 231 | | - if (pack.getStatus() != PackStatus.ACTIVE) { |
|---|
| 232 | | - LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus()); |
|---|
| 233 | | - throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack status is not Active"); |
|---|
| 234 | | - } |
|---|
| 265 | + /** |
|---|
| 266 | + * validate<p> |
|---|
| 267 | + * Server-side validation of a license: |
|---|
| 268 | + * - Not expired |
|---|
| 269 | + * - Pack still valid and active |
|---|
| 270 | + * - Signature valid |
|---|
| 271 | + * |
|---|
| 272 | + * @param currentLic license to validate |
|---|
| 273 | + * @param bsc security context |
|---|
| 274 | + * @return same license if valid |
|---|
| 275 | + */ |
|---|
| 276 | + @POST |
|---|
| 277 | + @Path("/validate") |
|---|
| 278 | + @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 279 | + @Securable(roles = Rol.API_CLIENT) |
|---|
| 280 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 281 | + @EnsureTransaction |
|---|
| 282 | + public Response validate(LicenseBean currentLic, @Context BasicSecurityContext bsc) |
|---|
| 283 | + throws IOException, SeCurisServiceException, SeCurisException { |
|---|
| 284 | + LOG.info("Validate license: {}", currentLic); |
|---|
| 235 | 285 | |
|---|
| 236 | | - try { |
|---|
| 237 | | - SignatureHelper.getInstance().validateSignature(currentLic); |
|---|
| 238 | | - } catch (SeCurisException ex) { |
|---|
| 239 | | - throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The license signature is not valid"); |
|---|
| 240 | | - } |
|---|
| 286 | + if (currentLic.getExpirationDate().before(new Date())) { |
|---|
| 287 | + throw new SeCurisServiceException(ErrorCodes.LICENSE_IS_EXPIRED, "The license is expired"); |
|---|
| 288 | + } |
|---|
| 241 | 289 | |
|---|
| 242 | | - return Response.ok(currentLic).build(); |
|---|
| 243 | | - } |
|---|
| 290 | + License existingLic = licenseHelper.getActiveLicenseFromDB(currentLic, em); |
|---|
| 244 | 291 | |
|---|
| 245 | | - /** |
|---|
| 246 | | - * Returns a new License file in JSON format based in a previous license |
|---|
| 247 | | - * There is 2 /renew services with json input and with upload file |
|---|
| 248 | | - * |
|---|
| 249 | | - * @param mpfdi |
|---|
| 250 | | - * @param bsc |
|---|
| 251 | | - * @return |
|---|
| 252 | | - * @throws IOException |
|---|
| 253 | | - * @throws SeCurisServiceException |
|---|
| 254 | | - * @throws SeCurisException |
|---|
| 255 | | - */ |
|---|
| 256 | | - @POST |
|---|
| 257 | | - @Path("/renew") |
|---|
| 258 | | - @Consumes(MediaType.MULTIPART_FORM_DATA) |
|---|
| 259 | | - @Securable(roles = Rol.API_CLIENT) |
|---|
| 260 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 261 | | - @EnsureTransaction |
|---|
| 262 | | - @SuppressWarnings("unchecked") |
|---|
| 263 | | - public Response renewFromLicenseFile(MultipartFormDataInput mpfdi, @Context BasicSecurityContext bsc) throws IOException, SeCurisServiceException, SeCurisException { |
|---|
| 264 | | - LicenseBean lic = new LicenseBean(); |
|---|
| 292 | + Pack pack = existingLic.getPack(); |
|---|
| 293 | + if (pack.getEndValidDate().before(new Date())) { |
|---|
| 294 | + throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack end valid date has been reached"); |
|---|
| 295 | + } |
|---|
| 296 | + if (pack.getStatus() != PackStatus.ACTIVE) { |
|---|
| 297 | + LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus()); |
|---|
| 298 | + throw new SeCurisServiceException(ErrorCodes.LICENSE_PACK_IS_NOT_VALID, "The pack status is not Active"); |
|---|
| 299 | + } |
|---|
| 265 | 300 | |
|---|
| 266 | | - lic.setAppCode(mpfdi.getFormDataPart("appCode", String.class, null)); |
|---|
| 267 | | - lic.setActivationCode(mpfdi.getFormDataPart("activationName", String.class, null)); |
|---|
| 268 | | - lic.setAppName(mpfdi.getFormDataPart("appName", String.class, null)); |
|---|
| 269 | | - lic.setArch(mpfdi.getFormDataPart("arch", String.class, null)); |
|---|
| 270 | | - lic.setCrcLogo(mpfdi.getFormDataPart("crcLogo", String.class, null)); |
|---|
| 271 | | - lic.setPackCode(mpfdi.getFormDataPart("packCode", String.class, null)); |
|---|
| 272 | | - lic.setLicenseTypeCode(mpfdi.getFormDataPart("licenseCode", String.class, null)); |
|---|
| 273 | | - lic.setCustomerCode(mpfdi.getFormDataPart("customerCode", String.class, null)); |
|---|
| 274 | | - lic.setMacAddresses(mpfdi.getFormDataPart("macAddresses", List.class, null)); |
|---|
| 275 | | - lic.setOsName(mpfdi.getFormDataPart("osName", String.class, null)); |
|---|
| 276 | | - lic.setExpirationDate(mpfdi.getFormDataPart("expirationDate", Date.class, null)); |
|---|
| 277 | | - LOG.info("Lic expires at: {}", lic.getExpirationDate()); |
|---|
| 278 | | - if (lic.getExpirationDate().after(DateUtils.addMonths(new Date(), 1))) { |
|---|
| 279 | | - throw new SeCurisServiceException(ErrorCodes.LICENSE_NOT_READY_FOR_RENEW, "The license is still valid, not ready for renew"); |
|---|
| 280 | | - } |
|---|
| 301 | + try { |
|---|
| 302 | + SignatureHelper.getInstance().validateSignature(currentLic); |
|---|
| 303 | + } catch (SeCurisException ex) { |
|---|
| 304 | + throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The license signature is not valid"); |
|---|
| 305 | + } |
|---|
| 281 | 306 | |
|---|
| 282 | | - return renewFromPreviousLicense(lic, bsc); |
|---|
| 283 | | - } |
|---|
| 307 | + return Response.ok(currentLic).build(); |
|---|
| 308 | + } |
|---|
| 284 | 309 | |
|---|
| 285 | | - /** |
|---|
| 286 | | - * Creates a new signed license from request data or from previous license |
|---|
| 287 | | - * if It's a renew |
|---|
| 288 | | - * |
|---|
| 289 | | - * @param req |
|---|
| 290 | | - * @param em |
|---|
| 291 | | - * @param renew |
|---|
| 292 | | - * @return |
|---|
| 293 | | - * @throws SeCurisServiceException |
|---|
| 294 | | - */ |
|---|
| 295 | | - private SignedLicenseBean createLicense(RequestBean req, EntityManager em, String nameOrReference, String email) throws SeCurisServiceException { |
|---|
| 296 | | - License lic = null; |
|---|
| 310 | + // -------------------- Internal helpers -------------------- |
|---|
| 297 | 311 | |
|---|
| 298 | | - if (req.getActivationCode() != null) { |
|---|
| 299 | | - lic = License.findLicenseByActivationCode(req.getActivationCode(), em); |
|---|
| 300 | | - if (lic == null) { |
|---|
| 301 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The given activation code is invalid: " + req.getActivationCode()); |
|---|
| 302 | | - } |
|---|
| 303 | | - if (lic.getStatus() == LicenseStatus.ACTIVE) { |
|---|
| 304 | | - RequestBean initialRequest; |
|---|
| 305 | | - try { |
|---|
| 306 | | - initialRequest = JsonUtils.json2object(lic.getRequestData(), RequestBean.class); |
|---|
| 307 | | - if (!req.match(initialRequest)) { |
|---|
| 308 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "There is already an active license for given activation code: " + req.getActivationCode()); |
|---|
| 309 | | - } else { |
|---|
| 310 | | - return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class); |
|---|
| 311 | | - } |
|---|
| 312 | | - } catch (SeCurisException e) { |
|---|
| 313 | | - LOG.error("Error getting existing license", e); |
|---|
| 314 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Original request is wrong"); |
|---|
| 315 | | - } |
|---|
| 316 | | - } else { |
|---|
| 317 | | - if (req.getAppCode() != null && !req.getAppCode().equals(lic.getPack().getLicenseType().getApplication().getCode())) { |
|---|
| 318 | | - LOG.error("Activation code {} belongs to app: {} but was sent by: {}", req.getActivationCode(), lic.getPack().getLicenseType().getApplication().getCode(), |
|---|
| 319 | | - req.getAppCode()); |
|---|
| 320 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The given activation code belongs to a different application: " + req.getActivationCode()); |
|---|
| 321 | | - } |
|---|
| 322 | | - } |
|---|
| 323 | | - // We validate if the HW is the same, otherwise an error is |
|---|
| 324 | | - // thrown |
|---|
| 325 | | - } else { |
|---|
| 326 | | - try { |
|---|
| 327 | | - lic = License.findValidLicenseByRequestData(JsonUtils.toJSON(req), em); |
|---|
| 328 | | - } catch (SeCurisException e1) { |
|---|
| 329 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Request sent is not valid"); |
|---|
| 330 | | - } |
|---|
| 331 | | - if (lic != null) { |
|---|
| 332 | | - try { |
|---|
| 333 | | - if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) { |
|---|
| 334 | | - return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class); |
|---|
| 335 | | - } |
|---|
| 336 | | - } catch (SeCurisException e) { |
|---|
| 337 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error trying to get the license bean from license code: " + lic.getCode()); |
|---|
| 338 | | - } |
|---|
| 339 | | - } else { |
|---|
| 340 | | - lic = new License(); |
|---|
| 341 | | - } |
|---|
| 342 | | - } |
|---|
| 312 | + /** |
|---|
| 313 | + * createLicense<p> |
|---|
| 314 | + * Creates a new signed license from request data or reuses an existing |
|---|
| 315 | + * pre-active/active one when allowed by business rules. |
|---|
| 316 | + * |
|---|
| 317 | + * @param req request bean |
|---|
| 318 | + * @param em entity manager |
|---|
| 319 | + * @param nameOrReference license holder name/reference (header) |
|---|
| 320 | + * @param email email (header) |
|---|
| 321 | + * @return signed license bean |
|---|
| 322 | + */ |
|---|
| 323 | + private SignedLicenseBean createLicense(RequestBean req, EntityManager em, String nameOrReference, String email) |
|---|
| 324 | + throws SeCurisServiceException { |
|---|
| 343 | 325 | |
|---|
| 344 | | - Pack pack; |
|---|
| 345 | | - if (lic.getActivationCode() == null) { |
|---|
| 346 | | - try { |
|---|
| 347 | | - pack = em.createNamedQuery("pack-by-code", Pack.class).setParameter("code", req.getPackCode()).getSingleResult(); |
|---|
| 348 | | - } catch (NoResultException e) { |
|---|
| 349 | | - throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "No pack found for code: " + req.getPackCode()); |
|---|
| 350 | | - } |
|---|
| 326 | + License lic = null; |
|---|
| 351 | 327 | |
|---|
| 352 | | - if (pack.getNumAvailables() <= 0) { |
|---|
| 353 | | - throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "The current pack has no licenses availables"); |
|---|
| 354 | | - } |
|---|
| 355 | | - if (lic.getStatus() == LicenseStatus.REQUESTED && !pack.isLicensePreactivation()) { |
|---|
| 356 | | - throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation"); |
|---|
| 357 | | - } |
|---|
| 328 | + // (1) Activation-code flow |
|---|
| 329 | + if (req.getActivationCode() != null) { |
|---|
| 330 | + lic = License.findLicenseByActivationCode(req.getActivationCode(), em); |
|---|
| 331 | + if (lic == null) { |
|---|
| 332 | + throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The given activation code is invalid: " + req.getActivationCode()); |
|---|
| 333 | + } |
|---|
| 334 | + if (lic.getStatus() == LicenseStatus.ACTIVE) { |
|---|
| 335 | + try { |
|---|
| 336 | + RequestBean initialRequest = JsonUtils.json2object(lic.getRequestData(), RequestBean.class); |
|---|
| 337 | + if (!req.match(initialRequest)) { |
|---|
| 338 | + throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, |
|---|
| 339 | + "There is already an active license for given activation code: " + req.getActivationCode()); |
|---|
| 340 | + } else { |
|---|
| 341 | + return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class); |
|---|
| 342 | + } |
|---|
| 343 | + } catch (SeCurisException e) { |
|---|
| 344 | + LOG.error("Error getting existing license", e); |
|---|
| 345 | + throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Original request is wrong"); |
|---|
| 346 | + } |
|---|
| 347 | + } else { |
|---|
| 348 | + if (req.getAppCode() != null && |
|---|
| 349 | + !req.getAppCode().equals(lic.getPack().getLicenseType().getApplication().getCode())) { |
|---|
| 350 | + LOG.error("Activation code {} belongs to app: {} but was sent by: {}", |
|---|
| 351 | + req.getActivationCode(), lic.getPack().getLicenseType().getApplication().getCode(), req.getAppCode()); |
|---|
| 352 | + throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, |
|---|
| 353 | + "The given activation code belongs to a different application: " + req.getActivationCode()); |
|---|
| 354 | + } |
|---|
| 355 | + } |
|---|
| 356 | + } else { |
|---|
| 357 | + // (2) Request-data flow (idempotent check) |
|---|
| 358 | + try { |
|---|
| 359 | + lic = License.findValidLicenseByRequestData(JsonUtils.toJSON(req), em); |
|---|
| 360 | + } catch (SeCurisException e1) { |
|---|
| 361 | + throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Request sent is not valid"); |
|---|
| 362 | + } |
|---|
| 363 | + if (lic != null) { |
|---|
| 364 | + try { |
|---|
| 365 | + if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE) { |
|---|
| 366 | + return JsonUtils.json2object(lic.getLicenseData(), SignedLicenseBean.class); |
|---|
| 367 | + } |
|---|
| 368 | + } catch (SeCurisException e) { |
|---|
| 369 | + throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, |
|---|
| 370 | + "Error trying to get the license bean from license code: " + lic.getCode()); |
|---|
| 371 | + } |
|---|
| 372 | + } else { |
|---|
| 373 | + lic = new License(); |
|---|
| 374 | + } |
|---|
| 375 | + } |
|---|
| 358 | 376 | |
|---|
| 359 | | - if (!req.getCustomerCode().equals(pack.getOrganization().getCode())) { |
|---|
| 360 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Customer code is not valid: " + req.getCustomerCode()); |
|---|
| 361 | | - } |
|---|
| 377 | + // (3) Pack validation & constraints |
|---|
| 378 | + Pack pack; |
|---|
| 379 | + if (lic.getActivationCode() == null) { |
|---|
| 380 | + try { |
|---|
| 381 | + pack = em.createNamedQuery("pack-by-code", Pack.class) |
|---|
| 382 | + .setParameter("code", req.getPackCode()) |
|---|
| 383 | + .getSingleResult(); |
|---|
| 384 | + } catch (NoResultException e) { |
|---|
| 385 | + throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "No pack found for code: " + req.getPackCode()); |
|---|
| 386 | + } |
|---|
| 362 | 387 | |
|---|
| 363 | | - if (!req.getLicenseTypeCode().equals(pack.getLicenseTypeCode())) { |
|---|
| 364 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "License type code is not valid: " + req.getLicenseTypeCode()); |
|---|
| 365 | | - } |
|---|
| 366 | | - } else { |
|---|
| 367 | | - pack = lic.getPack(); |
|---|
| 368 | | - } |
|---|
| 369 | | - if (pack.getStatus() != PackStatus.ACTIVE) { |
|---|
| 370 | | - LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus()); |
|---|
| 371 | | - throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The pack status is not Active"); |
|---|
| 372 | | - } |
|---|
| 373 | | - SignedLicenseBean signedLicense; |
|---|
| 374 | | - try { |
|---|
| 375 | | - String licCode; |
|---|
| 376 | | - if (lic.getCode() == null) { |
|---|
| 377 | | - licCode = LicUtils.getLicenseCode(pack.getCode(), licenseHelper.getNextCodeSuffix(pack.getId(), em)); |
|---|
| 378 | | - } else { |
|---|
| 379 | | - licCode = lic.getCode(); |
|---|
| 380 | | - } |
|---|
| 381 | | - Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, lic.getActivationCode() == null); |
|---|
| 388 | + if (pack.getNumAvailables() <= 0) { |
|---|
| 389 | + throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "The current pack has no licenses availables"); |
|---|
| 390 | + } |
|---|
| 391 | + if (lic.getStatus() == LicenseStatus.REQUESTED && !pack.isLicensePreactivation()) { |
|---|
| 392 | + throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation"); |
|---|
| 393 | + } |
|---|
| 394 | + if (!req.getCustomerCode().equals(pack.getOrganization().getCode())) { |
|---|
| 395 | + throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Customer code is not valid: " + req.getCustomerCode()); |
|---|
| 396 | + } |
|---|
| 397 | + if (!req.getLicenseTypeCode().equals(pack.getLicenseTypeCode())) { |
|---|
| 398 | + throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "License type code is not valid: " + req.getLicenseTypeCode()); |
|---|
| 399 | + } |
|---|
| 400 | + } else { |
|---|
| 401 | + pack = lic.getPack(); |
|---|
| 402 | + } |
|---|
| 382 | 403 | |
|---|
| 383 | | - LicenseBean lb = licenseGenerator.generateLicense(req, licenseHelper.extractPackMetadata(pack.getMetadata()), expirationDate, licCode, pack.getAppName()); |
|---|
| 384 | | - signedLicense = new SignedLicenseBean(lb); |
|---|
| 385 | | - } catch (SeCurisException e) { |
|---|
| 386 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString()); |
|---|
| 387 | | - } |
|---|
| 388 | | - try { |
|---|
| 389 | | - lic.setRequestData(JsonUtils.toJSON(req)); |
|---|
| 390 | | - if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) { |
|---|
| 391 | | - throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated"); |
|---|
| 392 | | - } |
|---|
| 393 | | - lic.setLicenseData(JsonUtils.toJSON(signedLicense)); |
|---|
| 394 | | - } catch (SeCurisException e) { |
|---|
| 395 | | - LOG.error("Error generating license JSON", e); |
|---|
| 396 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON"); |
|---|
| 397 | | - } |
|---|
| 404 | + if (pack.getStatus() != PackStatus.ACTIVE) { |
|---|
| 405 | + LOG.error("The Pack {} status is not active, is: {}", pack.getCode(), pack.getStatus()); |
|---|
| 406 | + throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "The pack status is not Active"); |
|---|
| 407 | + } |
|---|
| 398 | 408 | |
|---|
| 399 | | - lic.setModificationTimestamp(new Date()); |
|---|
| 400 | | - lic.setExpirationDate(signedLicense.getExpirationDate()); |
|---|
| 401 | | - User user = em.find(User.class, API_CLIENT_USERNAME); |
|---|
| 402 | | - if (lic.getStatus() != LicenseStatus.REQUESTED) { |
|---|
| 403 | | - lic.setPack(pack); |
|---|
| 404 | | - lic.setCreatedBy(user); |
|---|
| 405 | | - lic.setCreationTimestamp(new Date()); |
|---|
| 406 | | - if (lic.getActivationCode() != null) { |
|---|
| 407 | | - lic.setStatus(LicenseStatus.ACTIVE); |
|---|
| 408 | | - } else { |
|---|
| 409 | | - lic.setStatus(pack.isLicensePreactivation() ? LicenseStatus.PRE_ACTIVE : LicenseStatus.REQUESTED); |
|---|
| 410 | | - } |
|---|
| 411 | | - lic.setCode(signedLicense.getLicenseCode()); |
|---|
| 412 | | - lic.setCodeSuffix(LicUtils.getLicenseCodeSuffix(signedLicense.getLicenseCode())); |
|---|
| 413 | | - if (lic.getEmail() == null || "".equals(lic.getEmail())) { |
|---|
| 414 | | - lic.setEmail(email); |
|---|
| 415 | | - } |
|---|
| 416 | | - if (lic.getFullName() == null || "".equals(lic.getFullName())) { |
|---|
| 417 | | - lic.setFullName(nameOrReference); |
|---|
| 418 | | - } |
|---|
| 419 | | - if (lic.getId() != null) { |
|---|
| 420 | | - em.merge(lic); |
|---|
| 421 | | - } else { |
|---|
| 422 | | - em.persist(lic); |
|---|
| 423 | | - } |
|---|
| 424 | | - em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.CREATE)); |
|---|
| 425 | | - if (lic.getActivationCode() != null) { |
|---|
| 426 | | - em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.ACTIVATE, "Activated by code on creation")); |
|---|
| 427 | | - } else { |
|---|
| 428 | | - if (pack.isLicensePreactivation()) { |
|---|
| 429 | | - em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated on creation")); |
|---|
| 430 | | - } else { |
|---|
| 431 | | - LOG.warn("License ({}) created, but the pack doesn't allow preactivation", lic.getCode()); |
|---|
| 432 | | - throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation"); |
|---|
| 433 | | - } |
|---|
| 434 | | - } |
|---|
| 435 | | - } else { |
|---|
| 436 | | - lic.setStatus(LicenseStatus.PRE_ACTIVE); |
|---|
| 437 | | - em.merge(lic); |
|---|
| 438 | | - em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated after request")); |
|---|
| 439 | | - } |
|---|
| 409 | + // (4) License generation |
|---|
| 410 | + SignedLicenseBean signedLicense; |
|---|
| 411 | + try { |
|---|
| 412 | + String licCode = (lic.getCode() == null) |
|---|
| 413 | + ? LicUtils.getLicenseCode(pack.getCode(), licenseHelper.getNextCodeSuffix(pack.getId(), em)) |
|---|
| 414 | + : lic.getCode(); |
|---|
| 440 | 415 | |
|---|
| 441 | | - return signedLicense; |
|---|
| 442 | | - } |
|---|
| 416 | + Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, lic.getActivationCode() == null); |
|---|
| 417 | + LicenseBean lb = licenseGenerator.generateLicense( |
|---|
| 418 | + req, |
|---|
| 419 | + licenseHelper.extractPackMetadata(pack.getMetadata()), |
|---|
| 420 | + expirationDate, |
|---|
| 421 | + licCode, |
|---|
| 422 | + pack.getAppName() |
|---|
| 423 | + ); |
|---|
| 424 | + signedLicense = new SignedLicenseBean(lb); |
|---|
| 425 | + } catch (SeCurisException e) { |
|---|
| 426 | + throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString()); |
|---|
| 427 | + } |
|---|
| 443 | 428 | |
|---|
| 444 | | - /** |
|---|
| 445 | | - * Creates a new signed license from request data or from previous license |
|---|
| 446 | | - * if It's a renew |
|---|
| 447 | | - * |
|---|
| 448 | | - * @param req |
|---|
| 449 | | - * @param em |
|---|
| 450 | | - * @param renew |
|---|
| 451 | | - * @return |
|---|
| 452 | | - * @throws SeCurisServiceException |
|---|
| 453 | | - */ |
|---|
| 454 | | - private SignedLicenseBean renewLicense(LicenseBean previousLicenseBean, EntityManager em) throws SeCurisServiceException { |
|---|
| 429 | + // (5) Persist/merge license + history |
|---|
| 430 | + try { |
|---|
| 431 | + lic.setRequestData(JsonUtils.toJSON(req)); |
|---|
| 432 | + if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) { |
|---|
| 433 | + throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated"); |
|---|
| 434 | + } |
|---|
| 435 | + lic.setLicenseData(JsonUtils.toJSON(signedLicense)); |
|---|
| 436 | + } catch (SeCurisException e) { |
|---|
| 437 | + LOG.error("Error generating license JSON", e); |
|---|
| 438 | + throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON"); |
|---|
| 439 | + } |
|---|
| 455 | 440 | |
|---|
| 456 | | - License lic = License.findLicenseByCode(previousLicenseBean.getLicenseCode(), em); |
|---|
| 457 | | - if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) { |
|---|
| 458 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The current license has been cancelled"); |
|---|
| 459 | | - } |
|---|
| 441 | + lic.setModificationTimestamp(new Date()); |
|---|
| 442 | + lic.setExpirationDate(signedLicense.getExpirationDate()); |
|---|
| 443 | + User user = em.find(User.class, API_CLIENT_USERNAME); |
|---|
| 460 | 444 | |
|---|
| 461 | | - Pack pack = lic.getPack(); |
|---|
| 462 | | - SignedLicenseBean signedLicense; |
|---|
| 463 | | - try { |
|---|
| 464 | | - String licCode = lic.getCode(); |
|---|
| 465 | | - Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, false); |
|---|
| 445 | + if (lic.getStatus() != LicenseStatus.REQUESTED) { |
|---|
| 446 | + lic.setPack(pack); |
|---|
| 447 | + lic.setCreatedBy(user); |
|---|
| 448 | + lic.setCreationTimestamp(new Date()); |
|---|
| 449 | + if (lic.getActivationCode() != null) { |
|---|
| 450 | + lic.setStatus(LicenseStatus.ACTIVE); |
|---|
| 451 | + } else { |
|---|
| 452 | + lic.setStatus(pack.isLicensePreactivation() ? LicenseStatus.PRE_ACTIVE : LicenseStatus.REQUESTED); |
|---|
| 453 | + } |
|---|
| 454 | + lic.setCode(signedLicense.getLicenseCode()); |
|---|
| 455 | + lic.setCodeSuffix(LicUtils.getLicenseCodeSuffix(signedLicense.getLicenseCode())); |
|---|
| 456 | + if (lic.getEmail() == null || "".equals(lic.getEmail())) { |
|---|
| 457 | + lic.setEmail(email); |
|---|
| 458 | + } |
|---|
| 459 | + if (lic.getFullName() == null || "".equals(lic.getFullName())) { |
|---|
| 460 | + lic.setFullName(nameOrReference); |
|---|
| 461 | + } |
|---|
| 462 | + if (lic.getId() != null) { |
|---|
| 463 | + em.merge(lic); |
|---|
| 464 | + } else { |
|---|
| 465 | + em.persist(lic); |
|---|
| 466 | + } |
|---|
| 467 | + em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.CREATE)); |
|---|
| 468 | + if (lic.getActivationCode() != null) { |
|---|
| 469 | + em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.ACTIVATE, "Activated by code on creation")); |
|---|
| 470 | + } else { |
|---|
| 471 | + if (pack.isLicensePreactivation()) { |
|---|
| 472 | + em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated on creation")); |
|---|
| 473 | + } else { |
|---|
| 474 | + LOG.warn("License ({}) created, but the pack doesn't allow preactivation", lic.getCode()); |
|---|
| 475 | + throw new SeCurisServiceException(ErrorCodes.NO_AVAILABLE_LICENSES, "Current pack doesn't allow license preactivation"); |
|---|
| 476 | + } |
|---|
| 477 | + } |
|---|
| 478 | + } else { |
|---|
| 479 | + lic.setStatus(LicenseStatus.PRE_ACTIVE); |
|---|
| 480 | + em.merge(lic); |
|---|
| 481 | + em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.PRE_ACTIVATE, "Pre-activated after request")); |
|---|
| 482 | + } |
|---|
| 466 | 483 | |
|---|
| 467 | | - LicenseBean lb = licenseGenerator.generateLicense(previousLicenseBean, licenseHelper.extractPackMetadata(pack.getMetadata()), expirationDate, licCode, |
|---|
| 468 | | - pack.getAppName()); |
|---|
| 469 | | - signedLicense = new SignedLicenseBean(lb); |
|---|
| 470 | | - } catch (SeCurisException e) { |
|---|
| 471 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString()); |
|---|
| 472 | | - } |
|---|
| 473 | | - try { |
|---|
| 474 | | - lic.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class)); |
|---|
| 475 | | - if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) { |
|---|
| 476 | | - throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated"); |
|---|
| 477 | | - } |
|---|
| 478 | | - lic.setLicenseData(JsonUtils.toJSON(signedLicense)); |
|---|
| 479 | | - } catch (SeCurisException e) { |
|---|
| 480 | | - LOG.error("Error generating license JSON", e); |
|---|
| 481 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON"); |
|---|
| 482 | | - } |
|---|
| 484 | + return signedLicense; |
|---|
| 485 | + } |
|---|
| 483 | 486 | |
|---|
| 484 | | - lic.setModificationTimestamp(new Date()); |
|---|
| 485 | | - lic.setExpirationDate(signedLicense.getExpirationDate()); |
|---|
| 486 | | - User user = em.find(User.class, API_CLIENT_USERNAME); |
|---|
| 487 | + /** |
|---|
| 488 | + * renewLicense<p> |
|---|
| 489 | + * Internal renew logic used by JSON and multipart variants. |
|---|
| 490 | + * |
|---|
| 491 | + * @param previousLicenseBean previous license data |
|---|
| 492 | + * @param em entity manager |
|---|
| 493 | + * @return new signed license bean |
|---|
| 494 | + */ |
|---|
| 495 | + private SignedLicenseBean renewLicense(LicenseBean previousLicenseBean, EntityManager em) |
|---|
| 496 | + throws SeCurisServiceException { |
|---|
| 487 | 497 | |
|---|
| 488 | | - lic.setStatus(LicenseStatus.ACTIVE); |
|---|
| 489 | | - em.merge(lic); |
|---|
| 490 | | - em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.RENEW)); |
|---|
| 498 | + License lic = License.findLicenseByCode(previousLicenseBean.getLicenseCode(), em); |
|---|
| 499 | + if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) { |
|---|
| 500 | + throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The current license has been cancelled"); |
|---|
| 501 | + } |
|---|
| 491 | 502 | |
|---|
| 492 | | - return signedLicense; |
|---|
| 493 | | - } |
|---|
| 503 | + Pack pack = lic.getPack(); |
|---|
| 504 | + SignedLicenseBean signedLicense; |
|---|
| 505 | + try { |
|---|
| 506 | + String licCode = lic.getCode(); |
|---|
| 507 | + Date expirationDate = licenseHelper.getExpirationDateFromPack(pack, false); |
|---|
| 508 | + |
|---|
| 509 | + LicenseBean lb = licenseGenerator.generateLicense( |
|---|
| 510 | + previousLicenseBean, |
|---|
| 511 | + licenseHelper.extractPackMetadata(pack.getMetadata()), |
|---|
| 512 | + expirationDate, |
|---|
| 513 | + licCode, |
|---|
| 514 | + pack.getAppName() |
|---|
| 515 | + ); |
|---|
| 516 | + signedLicense = new SignedLicenseBean(lb); |
|---|
| 517 | + } catch (SeCurisException e) { |
|---|
| 518 | + throw new SeCurisServiceException(ErrorCodes.INVALID_LICENSE_REQUEST_DATA, "Error generating license: " + e.toString()); |
|---|
| 519 | + } |
|---|
| 520 | + try { |
|---|
| 521 | + lic.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class)); |
|---|
| 522 | + if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) { |
|---|
| 523 | + throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be activated"); |
|---|
| 524 | + } |
|---|
| 525 | + lic.setLicenseData(JsonUtils.toJSON(signedLicense)); |
|---|
| 526 | + } catch (SeCurisException e) { |
|---|
| 527 | + LOG.error("Error generating license JSON", e); |
|---|
| 528 | + throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generating license JSON"); |
|---|
| 529 | + } |
|---|
| 530 | + |
|---|
| 531 | + lic.setModificationTimestamp(new Date()); |
|---|
| 532 | + lic.setExpirationDate(signedLicense.getExpirationDate()); |
|---|
| 533 | + User user = em.find(User.class, API_CLIENT_USERNAME); |
|---|
| 534 | + |
|---|
| 535 | + lic.setStatus(LicenseStatus.ACTIVE); |
|---|
| 536 | + em.merge(lic); |
|---|
| 537 | + em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.RENEW)); |
|---|
| 538 | + |
|---|
| 539 | + return signedLicense; |
|---|
| 540 | + } |
|---|
| 494 | 541 | } |
|---|
| 542 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Date; |
|---|
| .. | .. |
|---|
| 42 | 45 | import net.curisit.securis.utils.TokenHelper; |
|---|
| 43 | 46 | |
|---|
| 44 | 47 | /** |
|---|
| 45 | | - * Application resource, this service will provide methods to create, modify and |
|---|
| 46 | | - * delete applications |
|---|
| 47 | | - * |
|---|
| 48 | | - * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 48 | + * ApplicationResource |
|---|
| 49 | + * <p> |
|---|
| 50 | + * REST endpoints to list, fetch, create, update and delete {@link Application}s. |
|---|
| 51 | + * Security: |
|---|
| 52 | + * <ul> |
|---|
| 53 | + * <li>Listing filters by user's accessible application IDs unless ADMIN.</li> |
|---|
| 54 | + * <li>Create/Modify/Delete restricted to ADMIN.</li> |
|---|
| 55 | + * </ul> |
|---|
| 56 | + * Side-effects: |
|---|
| 57 | + * <ul> |
|---|
| 58 | + * <li>Manages {@link ApplicationMetadata} lifecycle on create/update.</li> |
|---|
| 59 | + * <li>Propagates metadata changes via {@link MetadataHelper}.</li> |
|---|
| 60 | + * </ul> |
|---|
| 61 | + * |
|---|
| 62 | + * Author: roberto <roberto.sanchez@curisit.net><br> |
|---|
| 63 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 49 | 64 | */ |
|---|
| 50 | 65 | @Path("/application") |
|---|
| 51 | 66 | public class ApplicationResource { |
|---|
| 52 | 67 | |
|---|
| 53 | | - @Inject |
|---|
| 54 | | - TokenHelper tokenHelper; |
|---|
| 68 | + @Inject TokenHelper tokenHelper; |
|---|
| 69 | + @Inject MetadataHelper metadataHelper; |
|---|
| 55 | 70 | |
|---|
| 56 | | - @Inject |
|---|
| 57 | | - MetadataHelper metadataHelper; |
|---|
| 71 | + @Context EntityManager em; |
|---|
| 58 | 72 | |
|---|
| 59 | | - @Context |
|---|
| 60 | | - EntityManager em; |
|---|
| 73 | + private static final Logger LOG = LogManager.getLogger(ApplicationResource.class); |
|---|
| 61 | 74 | |
|---|
| 62 | | - private static final Logger LOG = LogManager.getLogger(ApplicationResource.class); |
|---|
| 75 | + /** |
|---|
| 76 | + * ApplicationResource<p> |
|---|
| 77 | + * Constructor |
|---|
| 78 | + */ |
|---|
| 79 | + public ApplicationResource() {} |
|---|
| 63 | 80 | |
|---|
| 64 | | - public ApplicationResource() { |
|---|
| 65 | | - } |
|---|
| 81 | + /** |
|---|
| 82 | + * index<p> |
|---|
| 83 | + * List applications visible to the current user. |
|---|
| 84 | + * |
|---|
| 85 | + * @param bsc security context |
|---|
| 86 | + * @return 200 with list (possibly empty) or 200 empty if user has no app scope |
|---|
| 87 | + */ |
|---|
| 88 | + @GET |
|---|
| 89 | + @Path("/") |
|---|
| 90 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 91 | + @Securable |
|---|
| 92 | + public Response index(@Context BasicSecurityContext bsc) { |
|---|
| 93 | + LOG.info("Getting applications list "); |
|---|
| 94 | + em.clear(); |
|---|
| 66 | 95 | |
|---|
| 67 | | - /** |
|---|
| 68 | | - * |
|---|
| 69 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 70 | | - */ |
|---|
| 71 | | - @GET |
|---|
| 72 | | - @Path("/") |
|---|
| 73 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 74 | | - @Securable |
|---|
| 75 | | - public Response index(@Context BasicSecurityContext bsc) { |
|---|
| 76 | | - LOG.info("Getting applications list "); |
|---|
| 96 | + TypedQuery<Application> q; |
|---|
| 97 | + if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) { |
|---|
| 98 | + q = em.createNamedQuery("list-applications", Application.class); |
|---|
| 99 | + } else { |
|---|
| 100 | + if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) { |
|---|
| 101 | + return Response.ok().build(); |
|---|
| 102 | + } |
|---|
| 103 | + q = em.createNamedQuery("list-applications-by_ids", Application.class); |
|---|
| 104 | + q.setParameter("list_ids", bsc.getApplicationsIds()); |
|---|
| 105 | + } |
|---|
| 106 | + List<Application> list = q.getResultList(); |
|---|
| 107 | + return Response.ok(list).build(); |
|---|
| 108 | + } |
|---|
| 77 | 109 | |
|---|
| 78 | | - // EntityManager em = emProvider.get(); |
|---|
| 79 | | - em.clear(); |
|---|
| 110 | + /** |
|---|
| 111 | + * get<p> |
|---|
| 112 | + * Fetch a single application by ID. |
|---|
| 113 | + * |
|---|
| 114 | + * @param appid string ID |
|---|
| 115 | + * @return 200 + entity or 404 if not found |
|---|
| 116 | + * @throws SeCurisServiceException when ID is invalid or not found |
|---|
| 117 | + */ |
|---|
| 118 | + @GET |
|---|
| 119 | + @Path("/{appid}") |
|---|
| 120 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 121 | + @Securable |
|---|
| 122 | + public Response get(@PathParam("appid") String appid) throws SeCurisServiceException { |
|---|
| 123 | + LOG.info("Getting application data for id: {}: ", appid); |
|---|
| 124 | + if (appid == null || "".equals(appid)) { |
|---|
| 125 | + LOG.error("Application ID is mandatory"); |
|---|
| 126 | + return Response.status(Status.NOT_FOUND).build(); |
|---|
| 127 | + } |
|---|
| 80 | 128 | |
|---|
| 81 | | - TypedQuery<Application> q; |
|---|
| 82 | | - if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) { |
|---|
| 83 | | - q = em.createNamedQuery("list-applications", Application.class); |
|---|
| 84 | | - } else { |
|---|
| 85 | | - if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) { |
|---|
| 86 | | - return Response.ok().build(); |
|---|
| 87 | | - } |
|---|
| 88 | | - q = em.createNamedQuery("list-applications-by_ids", Application.class); |
|---|
| 129 | + em.clear(); |
|---|
| 130 | + Application app = null; |
|---|
| 131 | + try { |
|---|
| 132 | + LOG.info("READY to GET app: {}", appid); |
|---|
| 133 | + app = em.find(Application.class, Integer.parseInt(appid)); |
|---|
| 134 | + } catch (Exception e) { |
|---|
| 135 | + LOG.info("ERROR GETTING app: {}", e); |
|---|
| 136 | + } |
|---|
| 137 | + if (app == null) { |
|---|
| 138 | + LOG.error("Application with id {} not found in DB", appid); |
|---|
| 139 | + throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "Application not found with ID: " + appid); |
|---|
| 140 | + } |
|---|
| 141 | + return Response.ok(app).build(); |
|---|
| 142 | + } |
|---|
| 89 | 143 | |
|---|
| 90 | | - q.setParameter("list_ids", bsc.getApplicationsIds()); |
|---|
| 91 | | - } |
|---|
| 92 | | - List<Application> list = q.getResultList(); |
|---|
| 144 | + /** |
|---|
| 145 | + * create<p> |
|---|
| 146 | + * Create a new application with optional metadata entries. |
|---|
| 147 | + * |
|---|
| 148 | + * @param app application payload |
|---|
| 149 | + * @param token auth token (audited externally) |
|---|
| 150 | + * @return 200 + persisted entity |
|---|
| 151 | + */ |
|---|
| 152 | + @POST |
|---|
| 153 | + @Path("/") |
|---|
| 154 | + @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 155 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 156 | + @EnsureTransaction |
|---|
| 157 | + @Securable(roles = Rol.ADMIN) |
|---|
| 158 | + @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 159 | + public Response create(Application app, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 160 | + LOG.info("Creating new application"); |
|---|
| 161 | + app.setCreationTimestamp(new Date()); |
|---|
| 162 | + em.persist(app); |
|---|
| 93 | 163 | |
|---|
| 94 | | - return Response.ok(list).build(); |
|---|
| 95 | | - } |
|---|
| 164 | + if (app.getApplicationMetadata() != null) { |
|---|
| 165 | + for (ApplicationMetadata md : app.getApplicationMetadata()) { |
|---|
| 166 | + md.setApplication(app); |
|---|
| 167 | + md.setCreationTimestamp(new Date()); |
|---|
| 168 | + em.persist(md); |
|---|
| 169 | + } |
|---|
| 170 | + } |
|---|
| 171 | + LOG.info("Creating application ({}) with date: {}", app.getId(), app.getCreationTimestamp()); |
|---|
| 172 | + return Response.ok(app).build(); |
|---|
| 173 | + } |
|---|
| 96 | 174 | |
|---|
| 97 | | - /** |
|---|
| 98 | | - * |
|---|
| 99 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 100 | | - * @throws SeCurisServiceException |
|---|
| 101 | | - */ |
|---|
| 102 | | - @GET |
|---|
| 103 | | - @Path("/{appid}") |
|---|
| 104 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 105 | | - @Securable |
|---|
| 106 | | - public Response get(@PathParam("appid") String appid) throws SeCurisServiceException { |
|---|
| 107 | | - LOG.info("Getting application data for id: {}: ", appid); |
|---|
| 108 | | - if (appid == null || "".equals(appid)) { |
|---|
| 109 | | - LOG.error("Application ID is mandatory"); |
|---|
| 110 | | - return Response.status(Status.NOT_FOUND).build(); |
|---|
| 111 | | - } |
|---|
| 175 | + /** |
|---|
| 176 | + * modify<p> |
|---|
| 177 | + * Update core fields and reconcile metadata set: |
|---|
| 178 | + * <ul> |
|---|
| 179 | + * <li>Removes missing keys, merges existing, persists new.</li> |
|---|
| 180 | + * <li>Propagates metadata if there were changes.</li> |
|---|
| 181 | + * </ul> |
|---|
| 182 | + * |
|---|
| 183 | + * @param appid path ID |
|---|
| 184 | + * @param app new state |
|---|
| 185 | + */ |
|---|
| 186 | + @PUT |
|---|
| 187 | + @POST |
|---|
| 188 | + @Path("/{appid}") |
|---|
| 189 | + @EnsureTransaction |
|---|
| 190 | + @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 191 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 192 | + @Securable(roles = Rol.ADMIN) |
|---|
| 193 | + @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 194 | + public Response modify(Application app, @PathParam("appid") String appid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 195 | + LOG.info("Modifying application with id: {}", appid); |
|---|
| 196 | + Application currentapp = em.find(Application.class, Integer.parseInt(appid)); |
|---|
| 197 | + if (currentapp == null) { |
|---|
| 198 | + LOG.error("Application with id {} not found in DB", appid); |
|---|
| 199 | + return Response.status(Status.NOT_FOUND) |
|---|
| 200 | + .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid) |
|---|
| 201 | + .build(); |
|---|
| 202 | + } |
|---|
| 112 | 203 | |
|---|
| 113 | | - em.clear(); |
|---|
| 204 | + currentapp.setCode(app.getCode()); |
|---|
| 205 | + currentapp.setName(app.getName()); |
|---|
| 206 | + currentapp.setLicenseFilename(app.getLicenseFilename()); |
|---|
| 207 | + currentapp.setDescription(app.getDescription()); |
|---|
| 114 | 208 | |
|---|
| 115 | | - Application app = null; |
|---|
| 116 | | - try { |
|---|
| 117 | | - LOG.info("READY to GET app: {}", appid); |
|---|
| 118 | | - app = em.find(Application.class, Integer.parseInt(appid)); |
|---|
| 119 | | - } catch (Exception e) { |
|---|
| 120 | | - LOG.info("ERROR GETTING app: {}", e); |
|---|
| 121 | | - } |
|---|
| 122 | | - if (app == null) { |
|---|
| 123 | | - LOG.error("Application with id {} not found in DB", appid); |
|---|
| 124 | | - throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "Application not found with ID: " + appid); |
|---|
| 125 | | - } |
|---|
| 209 | + Set<ApplicationMetadata> newMD = app.getApplicationMetadata(); |
|---|
| 210 | + Set<ApplicationMetadata> oldMD = currentapp.getApplicationMetadata(); |
|---|
| 211 | + boolean metadataChanges = !metadataHelper.match(newMD, oldMD); |
|---|
| 212 | + if (metadataChanges) { |
|---|
| 213 | + Map<String, ApplicationMetadata> directOldMD = getMapMD(oldMD); |
|---|
| 214 | + Map<String, ApplicationMetadata> directNewMD = getMapMD(newMD); |
|---|
| 126 | 215 | |
|---|
| 127 | | - return Response.ok(app).build(); |
|---|
| 128 | | - } |
|---|
| 216 | + // Remove deleted MD |
|---|
| 217 | + for (ApplicationMetadata currentMd : oldMD) { |
|---|
| 218 | + if (newMD == null || !directNewMD.containsKey(currentMd.getKey())) { |
|---|
| 219 | + em.remove(currentMd); |
|---|
| 220 | + } |
|---|
| 221 | + } |
|---|
| 222 | + // Merge or persist |
|---|
| 223 | + if (newMD != null) { |
|---|
| 224 | + for (ApplicationMetadata md : newMD) { |
|---|
| 225 | + if (directOldMD.containsKey(md.getKey())) { |
|---|
| 226 | + em.merge(md); |
|---|
| 227 | + } else { |
|---|
| 228 | + md.setApplication(currentapp); |
|---|
| 229 | + if (md.getCreationTimestamp() == null) { |
|---|
| 230 | + md.setCreationTimestamp(app.getCreationTimestamp()); |
|---|
| 231 | + } |
|---|
| 232 | + em.persist(md); |
|---|
| 233 | + } |
|---|
| 234 | + } |
|---|
| 235 | + } |
|---|
| 236 | + currentapp.setApplicationMetadata(app.getApplicationMetadata()); |
|---|
| 237 | + } |
|---|
| 129 | 238 | |
|---|
| 130 | | - @POST |
|---|
| 131 | | - @Path("/") |
|---|
| 132 | | - @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 133 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 134 | | - @EnsureTransaction |
|---|
| 135 | | - @Securable(roles = Rol.ADMIN) |
|---|
| 136 | | - @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 137 | | - public Response create(Application app, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 138 | | - LOG.info("Creating new application"); |
|---|
| 139 | | - // EntityManager em = emProvider.get(); |
|---|
| 140 | | - app.setCreationTimestamp(new Date()); |
|---|
| 141 | | - em.persist(app); |
|---|
| 239 | + em.merge(currentapp); |
|---|
| 240 | + if (metadataChanges) { |
|---|
| 241 | + metadataHelper.propagateMetadata(em, currentapp); |
|---|
| 242 | + } |
|---|
| 243 | + return Response.ok(currentapp).build(); |
|---|
| 244 | + } |
|---|
| 142 | 245 | |
|---|
| 143 | | - if (app.getApplicationMetadata() != null) { |
|---|
| 144 | | - for (ApplicationMetadata md : app.getApplicationMetadata()) { |
|---|
| 145 | | - md.setApplication(app); |
|---|
| 146 | | - md.setCreationTimestamp(new Date()); |
|---|
| 147 | | - em.persist(md); |
|---|
| 148 | | - } |
|---|
| 149 | | - } |
|---|
| 150 | | - LOG.info("Creating application ({}) with date: {}", app.getId(), app.getCreationTimestamp()); |
|---|
| 246 | + /** |
|---|
| 247 | + * getMapMD<p> |
|---|
| 248 | + * Build a map from metadata key → entity for fast reconciliation. |
|---|
| 249 | + * |
|---|
| 250 | + * @param applicationMetadata |
|---|
| 251 | + * @return mapMD |
|---|
| 252 | + */ |
|---|
| 253 | + private Map<String, ApplicationMetadata> getMapMD(Set<ApplicationMetadata> amd) { |
|---|
| 254 | + Map<String, ApplicationMetadata> map = new HashMap<>(); |
|---|
| 255 | + if (amd != null) { |
|---|
| 256 | + for (ApplicationMetadata m : amd) { |
|---|
| 257 | + map.put(m.getKey(), m); |
|---|
| 258 | + } |
|---|
| 259 | + } |
|---|
| 260 | + return map; |
|---|
| 261 | + } |
|---|
| 151 | 262 | |
|---|
| 152 | | - return Response.ok(app).build(); |
|---|
| 153 | | - } |
|---|
| 154 | | - |
|---|
| 155 | | - @PUT |
|---|
| 156 | | - @POST |
|---|
| 157 | | - @Path("/{appid}") |
|---|
| 158 | | - @EnsureTransaction |
|---|
| 159 | | - @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 160 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 161 | | - @Securable(roles = Rol.ADMIN) |
|---|
| 162 | | - @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 163 | | - public Response modify(Application app, @PathParam("appid") String appid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 164 | | - LOG.info("Modifying application with id: {}", appid); |
|---|
| 165 | | - // EntityManager em = emProvider.get(); |
|---|
| 166 | | - Application currentapp = em.find(Application.class, Integer.parseInt(appid)); |
|---|
| 167 | | - if (currentapp == null) { |
|---|
| 168 | | - LOG.error("Application with id {} not found in DB", appid); |
|---|
| 169 | | - return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid).build(); |
|---|
| 170 | | - } |
|---|
| 171 | | - currentapp.setCode(app.getCode()); |
|---|
| 172 | | - currentapp.setName(app.getName()); |
|---|
| 173 | | - currentapp.setLicenseFilename(app.getLicenseFilename()); |
|---|
| 174 | | - currentapp.setDescription(app.getDescription()); |
|---|
| 175 | | - |
|---|
| 176 | | - Set<ApplicationMetadata> newMD = app.getApplicationMetadata(); |
|---|
| 177 | | - Set<ApplicationMetadata> oldMD = currentapp.getApplicationMetadata(); |
|---|
| 178 | | - boolean metadataChanges = !metadataHelper.match(newMD, oldMD); |
|---|
| 179 | | - if (metadataChanges) { |
|---|
| 180 | | - Map<String, ApplicationMetadata> directOldMD = getMapMD(oldMD); |
|---|
| 181 | | - Map<String, ApplicationMetadata> directNewMD = getMapMD(newMD); |
|---|
| 182 | | - for (ApplicationMetadata currentMd : oldMD) { |
|---|
| 183 | | - if (newMD == null || !directNewMD.containsKey(currentMd.getKey())) { |
|---|
| 184 | | - em.remove(currentMd); |
|---|
| 185 | | - } |
|---|
| 186 | | - } |
|---|
| 187 | | - |
|---|
| 188 | | - if (newMD != null) { |
|---|
| 189 | | - for (ApplicationMetadata md : newMD) { |
|---|
| 190 | | - if (directOldMD.containsKey(md.getKey())) { |
|---|
| 191 | | - em.merge(md); |
|---|
| 192 | | - } else { |
|---|
| 193 | | - md.setApplication(currentapp); |
|---|
| 194 | | - if (md.getCreationTimestamp() == null) { |
|---|
| 195 | | - md.setCreationTimestamp(app.getCreationTimestamp()); |
|---|
| 196 | | - } |
|---|
| 197 | | - em.persist(md); |
|---|
| 198 | | - } |
|---|
| 199 | | - } |
|---|
| 200 | | - } |
|---|
| 201 | | - currentapp.setApplicationMetadata(app.getApplicationMetadata()); |
|---|
| 202 | | - } |
|---|
| 203 | | - em.merge(currentapp); |
|---|
| 204 | | - if (metadataChanges) { |
|---|
| 205 | | - metadataHelper.propagateMetadata(em, currentapp); |
|---|
| 206 | | - } |
|---|
| 207 | | - return Response.ok(currentapp).build(); |
|---|
| 208 | | - } |
|---|
| 209 | | - |
|---|
| 210 | | - private Map<String, ApplicationMetadata> getMapMD(Set<ApplicationMetadata> amd) { |
|---|
| 211 | | - Map<String, ApplicationMetadata> map = new HashMap<String, ApplicationMetadata>(); |
|---|
| 212 | | - if (amd != null) { |
|---|
| 213 | | - for (ApplicationMetadata applicationMetadata : amd) { |
|---|
| 214 | | - map.put(applicationMetadata.getKey(), applicationMetadata); |
|---|
| 215 | | - } |
|---|
| 216 | | - } |
|---|
| 217 | | - return map; |
|---|
| 218 | | - } |
|---|
| 219 | | - |
|---|
| 220 | | - @DELETE |
|---|
| 221 | | - @Path("/{appid}") |
|---|
| 222 | | - @EnsureTransaction |
|---|
| 223 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 224 | | - @Securable(roles = Rol.ADMIN) |
|---|
| 225 | | - @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 226 | | - public Response delete(@PathParam("appid") String appid, @Context HttpServletRequest request) { |
|---|
| 227 | | - LOG.info("Deleting app with id: {}", appid); |
|---|
| 228 | | - // EntityManager em = emProvider.get(); |
|---|
| 229 | | - Application app = em.find(Application.class, Integer.parseInt(appid)); |
|---|
| 230 | | - if (app == null) { |
|---|
| 231 | | - LOG.error("Application with id {} can not be deleted, It was not found in DB", appid); |
|---|
| 232 | | - return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid).build(); |
|---|
| 233 | | - } |
|---|
| 234 | | - /* |
|---|
| 235 | | - * if (app.getLicenseTypes() != null && |
|---|
| 236 | | - * !app.getLicenseTypes().isEmpty()) { throw new |
|---|
| 237 | | - * SeCurisServiceException(ErrorCodes.NOT_FOUND, |
|---|
| 238 | | - * "Application can not be deleted becasue has assigned one or more License types, ID: " |
|---|
| 239 | | - * + appid); } |
|---|
| 240 | | - */ |
|---|
| 241 | | - em.remove(app); |
|---|
| 242 | | - return Response.ok(Utils.createMap("success", true, "id", appid)).build(); |
|---|
| 243 | | - } |
|---|
| 244 | | - |
|---|
| 263 | + /** |
|---|
| 264 | + * delete<p> |
|---|
| 265 | + * Delete an application by ID. |
|---|
| 266 | + * <p>Note: deletion is not allowed if there are dependent entities (enforced by DB/cascade).</p> |
|---|
| 267 | + * |
|---|
| 268 | + * @param appId |
|---|
| 269 | + * @param request |
|---|
| 270 | + */ |
|---|
| 271 | + @DELETE |
|---|
| 272 | + @Path("/{appid}") |
|---|
| 273 | + @EnsureTransaction |
|---|
| 274 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 275 | + @Securable(roles = Rol.ADMIN) |
|---|
| 276 | + @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 277 | + public Response delete(@PathParam("appid") String appid, @Context HttpServletRequest request) { |
|---|
| 278 | + LOG.info("Deleting app with id: {}", appid); |
|---|
| 279 | + Application app = em.find(Application.class, Integer.parseInt(appid)); |
|---|
| 280 | + if (app == null) { |
|---|
| 281 | + LOG.error("Application with id {} can not be deleted, It was not found in DB", appid); |
|---|
| 282 | + return Response.status(Status.NOT_FOUND) |
|---|
| 283 | + .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application not found with ID: " + appid) |
|---|
| 284 | + .build(); |
|---|
| 285 | + } |
|---|
| 286 | + em.remove(app); |
|---|
| 287 | + return Response.ok(Utils.createMap("success", true, "id", appid)).build(); |
|---|
| 288 | + } |
|---|
| 245 | 289 | } |
|---|
| 290 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.net.URI; |
|---|
| .. | .. |
|---|
| 32 | 35 | import net.curisit.securis.utils.TokenHelper; |
|---|
| 33 | 36 | |
|---|
| 34 | 37 | /** |
|---|
| 35 | | - * Basic services for login and basic app wrkflow |
|---|
| 36 | | - * |
|---|
| 37 | | - * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 38 | + * BasicServices |
|---|
| 39 | + * <p> |
|---|
| 40 | + * Minimal public endpoints for service liveness, version info and token checks. |
|---|
| 41 | + * Also provides entry routing to SPA (admin/login/licenses) via /index.jsp. |
|---|
| 42 | + * |
|---|
| 43 | + * Security: |
|---|
| 44 | + * <ul> |
|---|
| 45 | + * <li>/check requires a valid bearer token (via {@link Securable}).</li> |
|---|
| 46 | + * <li>/logout just logs intention; token invalidation is outside this class.</li> |
|---|
| 47 | + * </ul> |
|---|
| 48 | + * |
|---|
| 49 | + * Author: roberto <roberto.sanchez@curisit.net> |
|---|
| 50 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 38 | 51 | */ |
|---|
| 39 | 52 | @Path("/") |
|---|
| 40 | 53 | @ApplicationScoped |
|---|
| 41 | 54 | public class BasicServices { |
|---|
| 42 | 55 | |
|---|
| 43 | | - private static final Logger LOG = LogManager.getLogger(BasicServices.class); |
|---|
| 56 | + private static final Logger LOG = LogManager.getLogger(BasicServices.class); |
|---|
| 44 | 57 | |
|---|
| 45 | | - @Inject |
|---|
| 46 | | - TokenHelper tokenHelper; |
|---|
| 58 | + @Inject TokenHelper tokenHelper; |
|---|
| 59 | + @Context EntityManager em; |
|---|
| 47 | 60 | |
|---|
| 48 | | - @Context |
|---|
| 49 | | - EntityManager em; |
|---|
| 61 | + @Inject public BasicServices() {} |
|---|
| 50 | 62 | |
|---|
| 51 | | - @Inject |
|---|
| 52 | | - public BasicServices() { |
|---|
| 53 | | - } |
|---|
| 63 | + /** |
|---|
| 64 | + * info<p> |
|---|
| 65 | + * Simple liveness text endpoint. |
|---|
| 66 | + * |
|---|
| 67 | + * @param request |
|---|
| 68 | + * @return response |
|---|
| 69 | + */ |
|---|
| 70 | + @GET |
|---|
| 71 | + @Path("/info") |
|---|
| 72 | + @Produces({ MediaType.TEXT_PLAIN }) |
|---|
| 73 | + public Response info(@Context HttpServletRequest request) { |
|---|
| 74 | + return Response.ok().entity("License server running OK. Date: " + new Date()).build(); |
|---|
| 75 | + } |
|---|
| 54 | 76 | |
|---|
| 55 | | - @GET |
|---|
| 56 | | - @Path("/info") |
|---|
| 57 | | - @Produces({ MediaType.TEXT_PLAIN }) |
|---|
| 58 | | - public Response info(@Context HttpServletRequest request) { |
|---|
| 59 | | - return Response.ok().entity("License server running OK. Date: " + new Date()).build(); |
|---|
| 60 | | - } |
|---|
| 77 | + /** |
|---|
| 78 | + * version<p> |
|---|
| 79 | + * Returns semantic app version as JSON. |
|---|
| 80 | + * |
|---|
| 81 | + * @param request |
|---|
| 82 | + * @return version |
|---|
| 83 | + */ |
|---|
| 84 | + @GET |
|---|
| 85 | + @Path("/version") |
|---|
| 86 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 87 | + public Map<String, String> version(@Context HttpServletRequest request) { |
|---|
| 88 | + Map<String, String> resp = new HashMap<>(); |
|---|
| 89 | + resp.put("version", AppVersion.getInstance().getCompleteVersion()); |
|---|
| 90 | + return resp; |
|---|
| 91 | + } |
|---|
| 61 | 92 | |
|---|
| 62 | | - @GET |
|---|
| 63 | | - @Path("/version") |
|---|
| 64 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 65 | | - public Map<String, String> version(@Context HttpServletRequest request) { |
|---|
| 66 | | - Map<String, String> resp = new HashMap<>(); |
|---|
| 67 | | - |
|---|
| 68 | | - // Get the real version |
|---|
| 69 | | - String version = AppVersion.getInstance().getCompleteVersion(); |
|---|
| 70 | | - resp.put("version", version); |
|---|
| 71 | | - return resp; |
|---|
| 72 | | - } |
|---|
| 93 | + /** |
|---|
| 94 | + * init<p> |
|---|
| 95 | + * Redirects SPA modules to the main index page. |
|---|
| 96 | + * |
|---|
| 97 | + * @param module |
|---|
| 98 | + * @param request |
|---|
| 99 | + * @return response |
|---|
| 100 | + */ |
|---|
| 101 | + @GET |
|---|
| 102 | + @Path("/{module:(admin)|(login)|(licenses)}") |
|---|
| 103 | + @Produces({ MediaType.TEXT_HTML }) |
|---|
| 104 | + public Response init(@PathParam("module") String module, @Context HttpServletRequest request) { |
|---|
| 105 | + LOG.info("App index main.html"); |
|---|
| 106 | + URI uri = UriBuilder.fromUri("/index.jsp").build(); |
|---|
| 107 | + return Response.seeOther(uri).build(); |
|---|
| 108 | + } |
|---|
| 73 | 109 | |
|---|
| 74 | | - @GET |
|---|
| 75 | | - @Path("/{module:(admin)|(login)|(licenses)}") |
|---|
| 76 | | - @Produces({ MediaType.TEXT_HTML }) |
|---|
| 77 | | - public Response init(@PathParam("module") String module, @Context HttpServletRequest request) { |
|---|
| 78 | | - LOG.info("App index main.html"); |
|---|
| 79 | | - String page = "/index.jsp"; |
|---|
| 80 | | - URI uri = UriBuilder.fromUri(page).build(); |
|---|
| 81 | | - return Response.seeOther(uri).build(); |
|---|
| 82 | | - } |
|---|
| 110 | + /** |
|---|
| 111 | + * check<p> |
|---|
| 112 | + * Validates a token (from header or query param). |
|---|
| 113 | + * |
|---|
| 114 | + * @param token X-Token header |
|---|
| 115 | + * @param token2 token query param fallback |
|---|
| 116 | + * @return 200 with user/date if valid, 401/403 otherwise |
|---|
| 117 | + */ |
|---|
| 118 | + @GET |
|---|
| 119 | + @Securable() |
|---|
| 120 | + @Path("/check") |
|---|
| 121 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 122 | + @EnsureTransaction |
|---|
| 123 | + public Response check(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token, @QueryParam("token") String token2) { |
|---|
| 124 | + if (token == null) token = token2; |
|---|
| 125 | + if (token == null) { |
|---|
| 126 | + return Response.status(Status.FORBIDDEN).build(); |
|---|
| 127 | + } |
|---|
| 128 | + boolean valid = tokenHelper.isTokenValid(token); |
|---|
| 129 | + if (!valid) { |
|---|
| 130 | + return Response.status(Status.UNAUTHORIZED).build(); |
|---|
| 131 | + } |
|---|
| 83 | 132 | |
|---|
| 84 | | - /** |
|---|
| 85 | | - * Check if current token is valid |
|---|
| 86 | | - * |
|---|
| 87 | | - * @param user |
|---|
| 88 | | - * @param password |
|---|
| 89 | | - * @param request |
|---|
| 90 | | - * @return |
|---|
| 91 | | - */ |
|---|
| 92 | | - @GET |
|---|
| 93 | | - @Securable() |
|---|
| 94 | | - @Path("/check") |
|---|
| 95 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 96 | | - @EnsureTransaction |
|---|
| 97 | | - public Response check(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token, @QueryParam("token") String token2) { |
|---|
| 98 | | - if (token == null) { |
|---|
| 99 | | - token = token2; |
|---|
| 100 | | - } |
|---|
| 101 | | - if (token == null) { |
|---|
| 102 | | - return Response.status(Status.FORBIDDEN).build(); |
|---|
| 103 | | - } |
|---|
| 104 | | - boolean valid = tokenHelper.isTokenValid(token); |
|---|
| 105 | | - if (!valid) { |
|---|
| 106 | | - return Response.status(Status.UNAUTHORIZED).build(); |
|---|
| 107 | | - } |
|---|
| 133 | + String user = tokenHelper.extractUserFromToken(token); |
|---|
| 134 | + Date date = tokenHelper.extractDateCreationFromToken(token); |
|---|
| 135 | + return Response.ok(Utils.createMap("valid", true, "user", user, "date", date)).build(); |
|---|
| 136 | + } |
|---|
| 108 | 137 | |
|---|
| 109 | | - String user = tokenHelper.extractUserFromToken(token); |
|---|
| 110 | | - Date date = tokenHelper.extractDateCreationFromToken(token); |
|---|
| 111 | | - |
|---|
| 112 | | - return Response.ok(Utils.createMap("valid", true, "user", user, "date", date)).build(); |
|---|
| 113 | | - } |
|---|
| 114 | | - |
|---|
| 115 | | - @GET |
|---|
| 116 | | - @POST |
|---|
| 117 | | - @Path("/logout") |
|---|
| 118 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 119 | | - public Response logout(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 120 | | - if (token == null) { |
|---|
| 121 | | - Response.status(Status.BAD_REQUEST).build(); |
|---|
| 122 | | - } |
|---|
| 123 | | - String user = tokenHelper.extractUserFromToken(token); |
|---|
| 124 | | - LOG.info("User {} has logged out", user); |
|---|
| 125 | | - return Response.ok().build(); |
|---|
| 126 | | - } |
|---|
| 138 | + /** |
|---|
| 139 | + * logout<p> |
|---|
| 140 | + * Logs logout event. (Token invalidation is handled elsewhere.) |
|---|
| 141 | + * |
|---|
| 142 | + * @param token |
|---|
| 143 | + * @return response |
|---|
| 144 | + */ |
|---|
| 145 | + @GET |
|---|
| 146 | + @POST |
|---|
| 147 | + @Path("/logout") |
|---|
| 148 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 149 | + public Response logout(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 150 | + if (token == null) { |
|---|
| 151 | + Response.status(Status.BAD_REQUEST).build(); |
|---|
| 152 | + } |
|---|
| 153 | + String user = tokenHelper.extractUserFromToken(token); |
|---|
| 154 | + LOG.info("User {} has logged out", user); |
|---|
| 155 | + return Response.ok().build(); |
|---|
| 156 | + } |
|---|
| 127 | 157 | } |
|---|
| 158 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.File; |
|---|
| .. | .. |
|---|
| 63 | 66 | import net.curisit.securis.utils.LicUtils; |
|---|
| 64 | 67 | |
|---|
| 65 | 68 | /** |
|---|
| 66 | | - * License resource, this service will provide methods to create, modify and |
|---|
| 67 | | - * delete licenses |
|---|
| 68 | | - * |
|---|
| 69 | | - * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 69 | + * LicenseResource |
|---|
| 70 | + * <p> |
|---|
| 71 | + * REST resource in charge of managing licenses: list, fetch, create, activate, |
|---|
| 72 | + * email delivery, cancel, block/unblock, modify and delete. It relies on |
|---|
| 73 | + * {@link BasicSecurityContext} to scope access (organizations/apps) and |
|---|
| 74 | + * on {@link EnsureTransaction} for mutating endpoints that need a TX. |
|---|
| 75 | + * <p> |
|---|
| 76 | + * Key rules: |
|---|
| 77 | + * <ul> |
|---|
| 78 | + * <li>Non-admin users must belong to the license's organization.</li> |
|---|
| 79 | + * <li>License creation validates code CRC, activation code and email.</li> |
|---|
| 80 | + * <li>Request payload must match Pack constraints (org/type/pack codes).</li> |
|---|
| 81 | + * <li>History is recorded for key actions (CREATE/ACTIVATE/DOWNLOAD/etc.).</li> |
|---|
| 82 | + * </ul> |
|---|
| 83 | + * |
|---|
| 84 | + * @author roberto |
|---|
| 85 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 70 | 86 | */ |
|---|
| 71 | 87 | @Path("/license") |
|---|
| 72 | 88 | public class LicenseResource { |
|---|
| 73 | 89 | |
|---|
| 74 | 90 | private static final Logger LOG = LogManager.getLogger(LicenseResource.class); |
|---|
| 75 | 91 | |
|---|
| 76 | | - @Inject |
|---|
| 77 | | - private EmailManager emailManager; |
|---|
| 92 | + @Inject private EmailManager emailManager; |
|---|
| 93 | + @Inject private UserHelper userHelper; |
|---|
| 94 | + @Inject private LicenseHelper licenseHelper; |
|---|
| 95 | + @Inject private LicenseGenerator licenseGenerator; |
|---|
| 78 | 96 | |
|---|
| 79 | | - @Inject |
|---|
| 80 | | - private UserHelper userHelper; |
|---|
| 81 | | - |
|---|
| 82 | | - @Inject |
|---|
| 83 | | - private LicenseHelper licenseHelper; |
|---|
| 84 | | - |
|---|
| 85 | | - @Context |
|---|
| 86 | | - EntityManager em; |
|---|
| 87 | | - |
|---|
| 88 | | - @Inject |
|---|
| 89 | | - private LicenseGenerator licenseGenerator; |
|---|
| 97 | + @Context EntityManager em; |
|---|
| 90 | 98 | |
|---|
| 91 | 99 | /** |
|---|
| 92 | | - * |
|---|
| 93 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 100 | + * index |
|---|
| 101 | + * <p> |
|---|
| 102 | + * List all licenses for a given pack. If the caller is not admin, |
|---|
| 103 | + * verifies the pack belongs to an accessible organization. |
|---|
| 104 | + * |
|---|
| 105 | + * @param packId Pack identifier to filter licenses (required). |
|---|
| 106 | + * @param bsc Security context to evaluate roles and scoping. |
|---|
| 107 | + * @return 200 OK with a list (possibly empty), or 401 if unauthorized. |
|---|
| 94 | 108 | */ |
|---|
| 95 | 109 | @GET |
|---|
| 96 | 110 | @Path("/") |
|---|
| .. | .. |
|---|
| 98 | 112 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 99 | 113 | public Response index(@QueryParam("packId") Integer packId, @Context BasicSecurityContext bsc) { |
|---|
| 100 | 114 | LOG.info("Getting licenses list "); |
|---|
| 101 | | - |
|---|
| 102 | | - // EntityManager em = emProvider.get(); |
|---|
| 103 | 115 | em.clear(); |
|---|
| 104 | 116 | |
|---|
| 105 | 117 | if (!bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) { |
|---|
| 106 | 118 | Pack pack = em.find(Pack.class, packId); |
|---|
| 107 | | - if (pack == null) { |
|---|
| 108 | | - return Response.ok().build(); |
|---|
| 109 | | - } |
|---|
| 119 | + if (pack == null) return Response.ok().build(); |
|---|
| 110 | 120 | if (!bsc.getOrganizationsIds().contains(pack.getOrganization().getId())) { |
|---|
| 111 | 121 | LOG.error("Pack with id {} not accesible by user {}", pack, bsc.getUserPrincipal()); |
|---|
| 112 | | - return Response.status(Status.UNAUTHORIZED).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Unathorized access to pack licenses").build(); |
|---|
| 122 | + return Response.status(Status.UNAUTHORIZED) |
|---|
| 123 | + .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Unathorized access to pack licenses") |
|---|
| 124 | + .build(); |
|---|
| 113 | 125 | } |
|---|
| 114 | 126 | } |
|---|
| 115 | 127 | TypedQuery<License> q = em.createNamedQuery("list-licenses-by-pack", License.class); |
|---|
| 116 | 128 | q.setParameter("packId", packId); |
|---|
| 117 | 129 | List<License> list = q.getResultList(); |
|---|
| 118 | | - |
|---|
| 119 | 130 | return Response.ok(list).build(); |
|---|
| 120 | 131 | } |
|---|
| 121 | 132 | |
|---|
| 122 | 133 | /** |
|---|
| 123 | | - * |
|---|
| 124 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 125 | | - * @throws SeCurisServiceException |
|---|
| 134 | + * get |
|---|
| 135 | + * <p> |
|---|
| 136 | + * Fetch a single license by id, enforcing access scope for non-admin users. |
|---|
| 137 | + * |
|---|
| 138 | + * @param licId License id (required). |
|---|
| 139 | + * @param bsc Security context. |
|---|
| 140 | + * @return 200 OK with the license. |
|---|
| 141 | + * @throws SeCurisServiceException 404 if not found, 401 if out of scope. |
|---|
| 126 | 142 | */ |
|---|
| 127 | 143 | @GET |
|---|
| 128 | 144 | @Path("/{licId}") |
|---|
| .. | .. |
|---|
| 130 | 146 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 131 | 147 | public Response get(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 132 | 148 | LOG.info("Getting organization data for id: {}: ", licId); |
|---|
| 133 | | - |
|---|
| 134 | | - // EntityManager em = emProvider.get(); |
|---|
| 135 | 149 | em.clear(); |
|---|
| 136 | 150 | License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 137 | 151 | return Response.ok(lic).build(); |
|---|
| 138 | 152 | } |
|---|
| 139 | 153 | |
|---|
| 140 | 154 | /** |
|---|
| 141 | | - * |
|---|
| 142 | | - * @return The license file, only of license is active |
|---|
| 143 | | - * @throws SeCurisServiceException |
|---|
| 155 | + * download |
|---|
| 156 | + * <p> |
|---|
| 157 | + * Download the license file. Only allowed when the license is ACTIVE |
|---|
| 158 | + * and license data exists. Adds a DOWNLOAD entry in history. |
|---|
| 159 | + * |
|---|
| 160 | + * @param licId License id. |
|---|
| 161 | + * @param bsc Security context. |
|---|
| 162 | + * @return 200 OK with the binary as application/octet-stream and a |
|---|
| 163 | + * Content-Disposition header; otherwise specific error codes. |
|---|
| 164 | + * @throws SeCurisServiceException if state or data is invalid. |
|---|
| 144 | 165 | */ |
|---|
| 145 | 166 | @GET |
|---|
| 146 | 167 | @Path("/{licId}/download") |
|---|
| .. | .. |
|---|
| 149 | 170 | @EnsureTransaction |
|---|
| 150 | 171 | public Response download(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 151 | 172 | |
|---|
| 152 | | - // EntityManager em = emProvider.get(); |
|---|
| 153 | 173 | License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 154 | 174 | |
|---|
| 155 | 175 | if (lic.getLicenseData() == null) { |
|---|
| .. | .. |
|---|
| 166 | 186 | } |
|---|
| 167 | 187 | |
|---|
| 168 | 188 | /** |
|---|
| 169 | | - * Activate the given license |
|---|
| 170 | | - * |
|---|
| 171 | | - * @param licId |
|---|
| 172 | | - * @param bsc |
|---|
| 173 | | - * @return |
|---|
| 174 | | - * @throws SeCurisServiceException |
|---|
| 189 | + * activate |
|---|
| 190 | + * <p> |
|---|
| 191 | + * Set license to ACTIVE provided status transition is valid, pack has |
|---|
| 192 | + * available units and request data passes validation/uniqueness. |
|---|
| 193 | + * Adds an ACTIVATE entry in history. |
|---|
| 194 | + * |
|---|
| 195 | + * @param licId License id. |
|---|
| 196 | + * @param bsc Security context. |
|---|
| 197 | + * @return 200 OK with updated license. |
|---|
| 198 | + * @throws SeCurisServiceException if invalid transition, no availability or invalid request data. |
|---|
| 175 | 199 | */ |
|---|
| 176 | 200 | @PUT |
|---|
| 177 | 201 | @POST |
|---|
| .. | .. |
|---|
| 182 | 206 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 183 | 207 | public Response activate(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 184 | 208 | |
|---|
| 185 | | - // EntityManager em = emProvider.get(); |
|---|
| 186 | 209 | License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 187 | 210 | |
|---|
| 188 | 211 | if (!License.Status.isActionValid(License.Action.ACTIVATION, lic.getStatus())) { |
|---|
| .. | .. |
|---|
| 211 | 234 | } |
|---|
| 212 | 235 | |
|---|
| 213 | 236 | /** |
|---|
| 214 | | - * Send license file by email to the organization |
|---|
| 215 | | - * |
|---|
| 216 | | - * @param licId |
|---|
| 217 | | - * @param bsc |
|---|
| 218 | | - * @return |
|---|
| 219 | | - * @throws SeCurisServiceException |
|---|
| 237 | + * send |
|---|
| 238 | + * <p> |
|---|
| 239 | + * Email the license file to the license owner. Builds a temporary file |
|---|
| 240 | + * using the application license filename and cleans it afterwards. |
|---|
| 241 | + * Adds a SEND entry in history. |
|---|
| 242 | + * |
|---|
| 243 | + * @param licId License id. |
|---|
| 244 | + * @param addCC whether to CC the current operator. |
|---|
| 245 | + * @param bsc Security context. |
|---|
| 246 | + * @return 200 OK with the license (no state change). |
|---|
| 247 | + * @throws SeCurisServiceException when no license file exists or user full name is missing. |
|---|
| 248 | + * @throws SeCurisException if JSON/signature process fails. |
|---|
| 220 | 249 | */ |
|---|
| 221 | 250 | @SuppressWarnings("deprecation") |
|---|
| 222 | 251 | @PUT |
|---|
| .. | .. |
|---|
| 229 | 258 | public Response send(@PathParam("licId") Integer licId, @DefaultValue("false") @FormParam("add_cc") Boolean addCC, @Context BasicSecurityContext bsc) |
|---|
| 230 | 259 | throws SeCurisServiceException, SeCurisException { |
|---|
| 231 | 260 | |
|---|
| 232 | | - // EntityManager em = emProvider.get(); |
|---|
| 233 | 261 | License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 234 | 262 | Application app = lic.getPack().getLicenseType().getApplication(); |
|---|
| 235 | 263 | File licFile = null; |
|---|
| .. | .. |
|---|
| 259 | 287 | } |
|---|
| 260 | 288 | } |
|---|
| 261 | 289 | |
|---|
| 262 | | - // lic.setModificationTimestamp(new Date()); |
|---|
| 263 | | - // em.merge(lic); |
|---|
| 264 | 290 | em.persist(licenseHelper.createLicenseHistoryAction(lic, user, LicenseHistory.Actions.SEND, "Email sent to: " + lic.getEmail())); |
|---|
| 265 | 291 | return Response.ok(lic).build(); |
|---|
| 266 | 292 | } |
|---|
| 267 | 293 | |
|---|
| 268 | 294 | /** |
|---|
| 269 | | - * Cancel given license |
|---|
| 270 | | - * |
|---|
| 271 | | - * @param licId |
|---|
| 272 | | - * @param bsc |
|---|
| 273 | | - * @return |
|---|
| 274 | | - * @throws SeCurisServiceException |
|---|
| 295 | + * cancel |
|---|
| 296 | + * <p> |
|---|
| 297 | + * Cancel a license (requires valid state transition and a non-null reason). |
|---|
| 298 | + * Delegates to {@link LicenseHelper#cancelLicense}. |
|---|
| 299 | + * |
|---|
| 300 | + * @param licId License id. |
|---|
| 301 | + * @param actionData DTO carrying the cancellation reason. |
|---|
| 302 | + * @param bsc Security context. |
|---|
| 303 | + * @return 200 OK with updated license. |
|---|
| 304 | + * @throws SeCurisServiceException when state is invalid or reason is missing. |
|---|
| 275 | 305 | */ |
|---|
| 276 | 306 | @PUT |
|---|
| 277 | 307 | @POST |
|---|
| .. | .. |
|---|
| 282 | 312 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 283 | 313 | public Response cancel(@PathParam("licId") Integer licId, CancellationLicenseActionBean actionData, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 284 | 314 | |
|---|
| 285 | | - // EntityManager em = emProvider.get(); |
|---|
| 286 | 315 | License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 287 | 316 | |
|---|
| 288 | 317 | if (!License.Status.isActionValid(License.Action.CANCEL, lic.getStatus())) { |
|---|
| .. | .. |
|---|
| 300 | 329 | } |
|---|
| 301 | 330 | |
|---|
| 302 | 331 | /** |
|---|
| 303 | | - * Check if there is some pack with the same code |
|---|
| 304 | | - * |
|---|
| 305 | | - * @param code |
|---|
| 306 | | - * Pack code |
|---|
| 307 | | - * @param em |
|---|
| 308 | | - * DB session object |
|---|
| 309 | | - * @return <code>true</code> if code is already used, <code>false</code> |
|---|
| 310 | | - * otherwise |
|---|
| 332 | + * create |
|---|
| 333 | + * <p> |
|---|
| 334 | + * Create a license. Validates: |
|---|
| 335 | + * <ul> |
|---|
| 336 | + * <li>Unique license code and valid CRC.</li> |
|---|
| 337 | + * <li>Activation code presence and uniqueness.</li> |
|---|
| 338 | + * <li>Valid user email.</li> |
|---|
| 339 | + * <li>Pack existence, ACTIVE status and scope authorization.</li> |
|---|
| 340 | + * <li>Request data consistency and unblock status (if provided).</li> |
|---|
| 341 | + * </ul> |
|---|
| 342 | + * If request data is provided and the Pack has availability, the license is |
|---|
| 343 | + * generated and set to ACTIVE immediately. |
|---|
| 344 | + * |
|---|
| 345 | + * @param lic License payload. |
|---|
| 346 | + * @param bsc Security context. |
|---|
| 347 | + * @return 200 OK with created license. |
|---|
| 348 | + * @throws SeCurisServiceException on validation failures. |
|---|
| 311 | 349 | */ |
|---|
| 312 | | - private boolean checkIfCodeExists(String code, EntityManager em) { |
|---|
| 313 | | - TypedQuery<License> query = em.createNamedQuery("license-by-code", License.class); |
|---|
| 314 | | - query.setParameter("code", code); |
|---|
| 315 | | - int lics = query.getResultList().size(); |
|---|
| 316 | | - return lics > 0; |
|---|
| 317 | | - } |
|---|
| 318 | | - |
|---|
| 319 | 350 | @POST |
|---|
| 320 | 351 | @Path("/") |
|---|
| 321 | 352 | @Consumes(MediaType.APPLICATION_JSON) |
|---|
| .. | .. |
|---|
| 323 | 354 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 324 | 355 | @EnsureTransaction |
|---|
| 325 | 356 | public Response create(License lic, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 326 | | - // EntityManager em = emProvider.get(); |
|---|
| 327 | | - |
|---|
| 328 | 357 | if (checkIfCodeExists(lic.getCode(), em)) { |
|---|
| 329 | 358 | throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The license code is already used in an existing license"); |
|---|
| 330 | 359 | } |
|---|
| .. | .. |
|---|
| 370 | 399 | } |
|---|
| 371 | 400 | |
|---|
| 372 | 401 | if (pack.getNumAvailables() > 0) { |
|---|
| 373 | | - |
|---|
| 374 | 402 | SignedLicenseBean signedLicense = generateLicense(lic, em); |
|---|
| 375 | | - // If user provide a request data the license status is passed |
|---|
| 376 | | - // directly to ACTIVE |
|---|
| 403 | + // Move directly to ACTIVE when request data is provided |
|---|
| 377 | 404 | lic.setStatus(LicenseStatus.ACTIVE); |
|---|
| 378 | 405 | try { |
|---|
| 379 | 406 | lic.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class)); |
|---|
| .. | .. |
|---|
| 404 | 431 | return Response.ok(lic).build(); |
|---|
| 405 | 432 | } |
|---|
| 406 | 433 | |
|---|
| 434 | + /** |
|---|
| 435 | + * modify |
|---|
| 436 | + * <p> |
|---|
| 437 | + * Update license basic fields (comments, fullName, email) and, when |
|---|
| 438 | + * status is CREATED and request payload changes, re-normalize/validate and |
|---|
| 439 | + * regenerate the signed license data. Adds a MODIFY history entry. |
|---|
| 440 | + * |
|---|
| 441 | + * @param lic New values. |
|---|
| 442 | + * @param licId License id. |
|---|
| 443 | + * @param bsc Security context. |
|---|
| 444 | + * @return 200 OK with updated license. |
|---|
| 445 | + * @throws SeCurisServiceException if validation fails. |
|---|
| 446 | + */ |
|---|
| 447 | + @SuppressWarnings("deprecation") |
|---|
| 448 | + @PUT |
|---|
| 449 | + @POST |
|---|
| 450 | + @Path("/{licId}") |
|---|
| 451 | + @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 452 | + @EnsureTransaction |
|---|
| 453 | + @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 454 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 455 | + public Response modify(License lic, @PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 456 | + LOG.info("Modifying license with id: {}", licId); |
|---|
| 457 | + |
|---|
| 458 | + License currentLicense = getCurrentLicense(licId, bsc, em); |
|---|
| 459 | + currentLicense.setComments(lic.getComments()); |
|---|
| 460 | + currentLicense.setFullName(lic.getFullName()); |
|---|
| 461 | + currentLicense.setEmail(lic.getEmail()); |
|---|
| 462 | + if (currentLicense.getActivationCode() == null) { |
|---|
| 463 | + currentLicense.setActivationCode(lic.getActivationCode()); |
|---|
| 464 | + } |
|---|
| 465 | + |
|---|
| 466 | + if (currentLicense.getStatus() == LicenseStatus.CREATED && !ObjectUtils.equals(currentLicense.getReqDataHash(), lic.getReqDataHash())) { |
|---|
| 467 | + if (lic.getRequestData() != null) { |
|---|
| 468 | + SignedLicenseBean signedLicense = generateLicense(lic, em); |
|---|
| 469 | + try { |
|---|
| 470 | + // Normalize the request JSON and update signed license JSON |
|---|
| 471 | + currentLicense.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class)); |
|---|
| 472 | + LOG.info("JSON generated for request: \n{}", currentLicense.getRequestData()); |
|---|
| 473 | + if (BlockedRequest.isRequestBlocked(currentLicense.getRequestData(), em)) { |
|---|
| 474 | + throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be used again"); |
|---|
| 475 | + } |
|---|
| 476 | + currentLicense.setLicenseData(JsonUtils.toJSON(signedLicense)); |
|---|
| 477 | + } catch (SeCurisException e) { |
|---|
| 478 | + LOG.error("Error generaing license JSON", e); |
|---|
| 479 | + throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generaing license JSON"); |
|---|
| 480 | + } |
|---|
| 481 | + } else { |
|---|
| 482 | + currentLicense.setRequestData(null); |
|---|
| 483 | + } |
|---|
| 484 | + } |
|---|
| 485 | + |
|---|
| 486 | + currentLicense.setModificationTimestamp(new Date()); |
|---|
| 487 | + em.persist(currentLicense); |
|---|
| 488 | + em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.MODIFY)); |
|---|
| 489 | + |
|---|
| 490 | + return Response.ok(currentLicense).build(); |
|---|
| 491 | + } |
|---|
| 492 | + |
|---|
| 493 | + /** |
|---|
| 494 | + * delete |
|---|
| 495 | + * <p> |
|---|
| 496 | + * Delete the license when the current status allows it. If the license |
|---|
| 497 | + * was BLOCKED, removes the BlockedRequest entry to unblock the request. |
|---|
| 498 | + * |
|---|
| 499 | + * @param licId License id. |
|---|
| 500 | + * @param bsc Security context. |
|---|
| 501 | + * @return 200 OK with a success payload. |
|---|
| 502 | + * @throws SeCurisServiceException if status does not allow deletion. |
|---|
| 503 | + */ |
|---|
| 504 | + @DELETE |
|---|
| 505 | + @Path("/{licId}") |
|---|
| 506 | + @EnsureTransaction |
|---|
| 507 | + @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 508 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 509 | + public Response delete(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 510 | + LOG.info("Deleting license with id: {}", licId); |
|---|
| 511 | + License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 512 | + |
|---|
| 513 | + if (!License.Status.isActionValid(License.Action.DELETE, lic.getStatus())) { |
|---|
| 514 | + LOG.error("License {} can not be deleted with status {}", lic.getCode(), lic.getStatus()); |
|---|
| 515 | + throw new SeCurisServiceException(ErrorCodes.WRONG_STATUS, "License can not be deleted in current status: " + lic.getStatus().name()); |
|---|
| 516 | + } |
|---|
| 517 | + if (lic.getStatus() == LicenseStatus.BLOCKED) { |
|---|
| 518 | + BlockedRequest blockedReq = em.find(BlockedRequest.class, lic.getReqDataHash()); |
|---|
| 519 | + if (blockedReq != null) { |
|---|
| 520 | + em.remove(blockedReq); |
|---|
| 521 | + } |
|---|
| 522 | + } |
|---|
| 523 | + |
|---|
| 524 | + em.remove(lic); |
|---|
| 525 | + return Response.ok(Utils.createMap("success", true, "id", licId)).build(); |
|---|
| 526 | + } |
|---|
| 527 | + |
|---|
| 528 | + /** |
|---|
| 529 | + * block |
|---|
| 530 | + * <p> |
|---|
| 531 | + * Block the license request data (allowed only from CANCELLED state). |
|---|
| 532 | + * Persists a {@link BlockedRequest} and transitions the license to BLOCKED. |
|---|
| 533 | + * |
|---|
| 534 | + * @param licId License id. |
|---|
| 535 | + * @param bsc Security context. |
|---|
| 536 | + * @return 200 OK with a success payload. |
|---|
| 537 | + * @throws SeCurisServiceException if state is not CANCELLED or already blocked. |
|---|
| 538 | + */ |
|---|
| 539 | + @POST |
|---|
| 540 | + @Path("/{licId}/block") |
|---|
| 541 | + @EnsureTransaction |
|---|
| 542 | + @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 543 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 544 | + public Response block(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 545 | + LOG.info("Blocking license with id: {}", licId); |
|---|
| 546 | + License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 547 | + |
|---|
| 548 | + if (!License.Status.isActionValid(License.Action.BLOCK, lic.getStatus())) { |
|---|
| 549 | + LOG.error("License can only be blocked in CANCELLED status, current: {}", lic.getStatus().name()); |
|---|
| 550 | + throw new SeCurisServiceException(ErrorCodes.WRONG_STATUS, "License can only be blocked in CANCELLED status"); |
|---|
| 551 | + } |
|---|
| 552 | + if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) { |
|---|
| 553 | + throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is already blocked"); |
|---|
| 554 | + } |
|---|
| 555 | + BlockedRequest blockedReq = new BlockedRequest(); |
|---|
| 556 | + blockedReq.setCreationTimestamp(new Date()); |
|---|
| 557 | + blockedReq.setBlockedBy(userHelper.getUser(bsc, em)); |
|---|
| 558 | + blockedReq.setRequestData(lic.getRequestData()); |
|---|
| 559 | + |
|---|
| 560 | + em.persist(blockedReq); |
|---|
| 561 | + lic.setStatus(LicenseStatus.BLOCKED); |
|---|
| 562 | + lic.setModificationTimestamp(new Date()); |
|---|
| 563 | + em.merge(lic); |
|---|
| 564 | + |
|---|
| 565 | + em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.BLOCK)); |
|---|
| 566 | + return Response.ok(Utils.createMap("success", true, "id", licId)).build(); |
|---|
| 567 | + } |
|---|
| 568 | + |
|---|
| 569 | + /** |
|---|
| 570 | + * unblock |
|---|
| 571 | + * <p> |
|---|
| 572 | + * Remove the block for the license request data (if present) and move |
|---|
| 573 | + * license back to CANCELLED. Adds an UNBLOCK history entry. |
|---|
| 574 | + * |
|---|
| 575 | + * @param licId License id. |
|---|
| 576 | + * @param bsc Security context. |
|---|
| 577 | + * @return 200 OK with a success payload. |
|---|
| 578 | + * @throws SeCurisServiceException never if not blocked (returns success anyway). |
|---|
| 579 | + */ |
|---|
| 580 | + @POST |
|---|
| 581 | + @Path("/{licId}/unblock") |
|---|
| 582 | + @EnsureTransaction |
|---|
| 583 | + @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 584 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 585 | + public Response unblock(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 586 | + LOG.info("Unblocking license with id: {}", licId); |
|---|
| 587 | + License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 588 | + |
|---|
| 589 | + if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) { |
|---|
| 590 | + BlockedRequest blockedReq = em.find(BlockedRequest.class, lic.getReqDataHash()); |
|---|
| 591 | + em.remove(blockedReq); |
|---|
| 592 | + |
|---|
| 593 | + lic.setStatus(LicenseStatus.CANCELLED); |
|---|
| 594 | + lic.setModificationTimestamp(new Date()); |
|---|
| 595 | + em.merge(lic); |
|---|
| 596 | + em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.UNBLOCK)); |
|---|
| 597 | + } else { |
|---|
| 598 | + LOG.info("Request data for license {} is NOT blocked", licId); |
|---|
| 599 | + } |
|---|
| 600 | + |
|---|
| 601 | + return Response.ok(Utils.createMap("success", true, "id", licId)).build(); |
|---|
| 602 | + } |
|---|
| 603 | + |
|---|
| 604 | + // --------------------------------------------------------------------- |
|---|
| 605 | + // Helpers |
|---|
| 606 | + // --------------------------------------------------------------------- |
|---|
| 607 | + |
|---|
| 608 | + /** |
|---|
| 609 | + * checkIfCodeExists<p> |
|---|
| 610 | + * Check if there is an existing license with the same code. |
|---|
| 611 | + * |
|---|
| 612 | + * @param code |
|---|
| 613 | + * @param entityManager |
|---|
| 614 | + */ |
|---|
| 615 | + private boolean checkIfCodeExists(String code, EntityManager em) { |
|---|
| 616 | + TypedQuery<License> query = em.createNamedQuery("license-by-code", License.class); |
|---|
| 617 | + query.setParameter("code", code); |
|---|
| 618 | + int lics = query.getResultList().size(); |
|---|
| 619 | + return lics > 0; |
|---|
| 620 | + } |
|---|
| 621 | + |
|---|
| 622 | + /** |
|---|
| 623 | + * generateLicense<p> |
|---|
| 624 | + * Generate a signed license from request data and pack metadata/expiration. |
|---|
| 625 | + * |
|---|
| 626 | + * @param license License with requestData and packId populated. |
|---|
| 627 | + * @param em Entity manager. |
|---|
| 628 | + * @return Signed license bean. |
|---|
| 629 | + * @throws SeCurisServiceException if validation/generation fails. |
|---|
| 630 | + */ |
|---|
| 407 | 631 | private SignedLicenseBean generateLicense(License license, EntityManager em) throws SeCurisServiceException { |
|---|
| 408 | 632 | SignedLicenseBean sl = null; |
|---|
| 409 | 633 | Pack pack = em.find(Pack.class, license.getPackId()); |
|---|
| .. | .. |
|---|
| 419 | 643 | } |
|---|
| 420 | 644 | |
|---|
| 421 | 645 | /** |
|---|
| 422 | | - * We check if the given request data is valid for the current Pack and has |
|---|
| 423 | | - * a valid format |
|---|
| 424 | | - * |
|---|
| 425 | | - * @param pack |
|---|
| 426 | | - * @param requestData |
|---|
| 427 | | - * @throws SeCurisServiceException |
|---|
| 646 | + * validateRequestData<p> |
|---|
| 647 | + * Validate that requestData matches the Pack and is well-formed. |
|---|
| 648 | + * |
|---|
| 649 | + * @param pack Target pack (org/type constraints). |
|---|
| 650 | + * @param requestData Raw JSON string with the license request. |
|---|
| 651 | + * @param activationCode Activation code from the license payload. |
|---|
| 652 | + * @return Parsed {@link RequestBean}. |
|---|
| 653 | + * @throws SeCurisServiceException on format mismatch or wrong codes. |
|---|
| 428 | 654 | */ |
|---|
| 429 | 655 | private RequestBean validateRequestData(Pack pack, String requestData, String activationCode) throws SeCurisServiceException { |
|---|
| 430 | 656 | if (requestData == null) { |
|---|
| .. | .. |
|---|
| 456 | 682 | return rb; |
|---|
| 457 | 683 | } |
|---|
| 458 | 684 | |
|---|
| 459 | | - @SuppressWarnings("deprecation") |
|---|
| 460 | | - @PUT |
|---|
| 461 | | - @POST |
|---|
| 462 | | - @Path("/{licId}") |
|---|
| 463 | | - @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 464 | | - @EnsureTransaction |
|---|
| 465 | | - @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 466 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 467 | | - public Response modify(License lic, @PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 468 | | - LOG.info("Modifying license with id: {}", licId); |
|---|
| 469 | | - |
|---|
| 470 | | - // EntityManager em = emProvider.get(); |
|---|
| 471 | | - |
|---|
| 472 | | - License currentLicense = getCurrentLicense(licId, bsc, em); |
|---|
| 473 | | - currentLicense.setComments(lic.getComments()); |
|---|
| 474 | | - currentLicense.setFullName(lic.getFullName()); |
|---|
| 475 | | - currentLicense.setEmail(lic.getEmail()); |
|---|
| 476 | | - if (currentLicense.getActivationCode() == null) { |
|---|
| 477 | | - currentLicense.setActivationCode(lic.getActivationCode()); |
|---|
| 478 | | - } |
|---|
| 479 | | - |
|---|
| 480 | | - if (currentLicense.getStatus() == LicenseStatus.CREATED && !ObjectUtils.equals(currentLicense.getReqDataHash(), lic.getReqDataHash())) { |
|---|
| 481 | | - if (lic.getRequestData() != null) { |
|---|
| 482 | | - SignedLicenseBean signedLicense = generateLicense(lic, em); |
|---|
| 483 | | - try { |
|---|
| 484 | | - // Next 2 lines are necessary to normalize the String that |
|---|
| 485 | | - // contains |
|---|
| 486 | | - // the request. |
|---|
| 487 | | - currentLicense.setRequestData(JsonUtils.toJSON(signedLicense, RequestBean.class)); |
|---|
| 488 | | - LOG.info("JSON generated for request: \n{}", currentLicense.getRequestData()); |
|---|
| 489 | | - if (BlockedRequest.isRequestBlocked(currentLicense.getRequestData(), em)) { |
|---|
| 490 | | - throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is blocked and cannot be used again"); |
|---|
| 491 | | - } |
|---|
| 492 | | - currentLicense.setLicenseData(JsonUtils.toJSON(signedLicense)); |
|---|
| 493 | | - } catch (SeCurisException e) { |
|---|
| 494 | | - LOG.error("Error generaing license JSON", e); |
|---|
| 495 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_FORMAT, "Error generaing license JSON"); |
|---|
| 496 | | - } |
|---|
| 497 | | - } else { |
|---|
| 498 | | - // This set method could pass a null value |
|---|
| 499 | | - currentLicense.setRequestData(null); |
|---|
| 500 | | - } |
|---|
| 501 | | - } |
|---|
| 502 | | - |
|---|
| 503 | | - currentLicense.setModificationTimestamp(new Date()); |
|---|
| 504 | | - em.persist(currentLicense); |
|---|
| 505 | | - em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.MODIFY)); |
|---|
| 506 | | - |
|---|
| 507 | | - return Response.ok(currentLicense).build(); |
|---|
| 508 | | - } |
|---|
| 509 | | - |
|---|
| 510 | | - @DELETE |
|---|
| 511 | | - @Path("/{licId}") |
|---|
| 512 | | - @EnsureTransaction |
|---|
| 513 | | - @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 514 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 515 | | - public Response delete(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 516 | | - LOG.info("Deleting license with id: {}", licId); |
|---|
| 517 | | - // EntityManager em = emProvider.get(); |
|---|
| 518 | | - License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 519 | | - |
|---|
| 520 | | - if (!License.Status.isActionValid(License.Action.DELETE, lic.getStatus())) { |
|---|
| 521 | | - LOG.error("License {} can not be deleted with status {}", lic.getCode(), lic.getStatus()); |
|---|
| 522 | | - throw new SeCurisServiceException(ErrorCodes.WRONG_STATUS, "License can not be deleted in current status: " + lic.getStatus().name()); |
|---|
| 523 | | - } |
|---|
| 524 | | - if (lic.getStatus() == LicenseStatus.BLOCKED) { |
|---|
| 525 | | - // If license is removed and it's blocked then the blocked request |
|---|
| 526 | | - // should be removed, that is, |
|---|
| 527 | | - // the license deletion will unblock the request data |
|---|
| 528 | | - BlockedRequest blockedReq = em.find(BlockedRequest.class, lic.getReqDataHash()); |
|---|
| 529 | | - if (blockedReq != null) { |
|---|
| 530 | | - // This if is to avoid some race condition or if the request has |
|---|
| 531 | | - // been already removed manually |
|---|
| 532 | | - em.remove(blockedReq); |
|---|
| 533 | | - } |
|---|
| 534 | | - } |
|---|
| 535 | | - |
|---|
| 536 | | - em.remove(lic); |
|---|
| 537 | | - return Response.ok(Utils.createMap("success", true, "id", licId)).build(); |
|---|
| 538 | | - } |
|---|
| 539 | | - |
|---|
| 540 | | - @POST |
|---|
| 541 | | - @Path("/{licId}/block") |
|---|
| 542 | | - @EnsureTransaction |
|---|
| 543 | | - @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 544 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 545 | | - public Response block(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 546 | | - LOG.info("Blocking license with id: {}", licId); |
|---|
| 547 | | - // EntityManager em = emProvider.get(); |
|---|
| 548 | | - License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 549 | | - |
|---|
| 550 | | - if (!License.Status.isActionValid(License.Action.BLOCK, lic.getStatus())) { |
|---|
| 551 | | - LOG.error("License can only be blocked in CANCELLED status, current: {}", lic.getStatus().name()); |
|---|
| 552 | | - throw new SeCurisServiceException(ErrorCodes.WRONG_STATUS, "License can only be blocked in CANCELLED status"); |
|---|
| 553 | | - } |
|---|
| 554 | | - if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) { |
|---|
| 555 | | - throw new SeCurisServiceException(ErrorCodes.BLOCKED_REQUEST_DATA, "Given request data is already blocked"); |
|---|
| 556 | | - } |
|---|
| 557 | | - BlockedRequest blockedReq = new BlockedRequest(); |
|---|
| 558 | | - blockedReq.setCreationTimestamp(new Date()); |
|---|
| 559 | | - blockedReq.setBlockedBy(userHelper.getUser(bsc, em)); |
|---|
| 560 | | - blockedReq.setRequestData(lic.getRequestData()); |
|---|
| 561 | | - |
|---|
| 562 | | - em.persist(blockedReq); |
|---|
| 563 | | - lic.setStatus(LicenseStatus.BLOCKED); |
|---|
| 564 | | - lic.setModificationTimestamp(new Date()); |
|---|
| 565 | | - em.merge(lic); |
|---|
| 566 | | - |
|---|
| 567 | | - em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.BLOCK)); |
|---|
| 568 | | - return Response.ok(Utils.createMap("success", true, "id", licId)).build(); |
|---|
| 569 | | - } |
|---|
| 570 | | - |
|---|
| 571 | | - @POST |
|---|
| 572 | | - @Path("/{licId}/unblock") |
|---|
| 573 | | - @EnsureTransaction |
|---|
| 574 | | - @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 575 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 576 | | - public Response unblock(@PathParam("licId") Integer licId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 577 | | - LOG.info("Unblocking license with id: {}", licId); |
|---|
| 578 | | - // EntityManager em = emProvider.get(); |
|---|
| 579 | | - License lic = getCurrentLicense(licId, bsc, em); |
|---|
| 580 | | - |
|---|
| 581 | | - if (BlockedRequest.isRequestBlocked(lic.getRequestData(), em)) { |
|---|
| 582 | | - BlockedRequest blockedReq = em.find(BlockedRequest.class, lic.getReqDataHash()); |
|---|
| 583 | | - em.remove(blockedReq); |
|---|
| 584 | | - |
|---|
| 585 | | - lic.setStatus(LicenseStatus.CANCELLED); |
|---|
| 586 | | - lic.setModificationTimestamp(new Date()); |
|---|
| 587 | | - em.merge(lic); |
|---|
| 588 | | - em.persist(licenseHelper.createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.UNBLOCK)); |
|---|
| 589 | | - } else { |
|---|
| 590 | | - LOG.info("Request data for license {} is NOT blocked", licId); |
|---|
| 591 | | - } |
|---|
| 592 | | - |
|---|
| 593 | | - return Response.ok(Utils.createMap("success", true, "id", licId)).build(); |
|---|
| 594 | | - } |
|---|
| 595 | | - |
|---|
| 685 | + /** |
|---|
| 686 | + * getCurrentLicense<p> |
|---|
| 687 | + * Load a license and verify scope for non-admin users. |
|---|
| 688 | + * |
|---|
| 689 | + * @param licId License id. |
|---|
| 690 | + * @param bsc Security context. |
|---|
| 691 | + * @param em Entity manager. |
|---|
| 692 | + * @return License entity. |
|---|
| 693 | + * @throws SeCurisServiceException if id is missing, not found or unauthorized. |
|---|
| 694 | + */ |
|---|
| 596 | 695 | private License getCurrentLicense(Integer licId, BasicSecurityContext bsc, EntityManager em) throws SeCurisServiceException { |
|---|
| 597 | 696 | if (licId == null || "".equals(Integer.toString(licId))) { |
|---|
| 598 | 697 | LOG.error("License ID is mandatory"); |
|---|
| .. | .. |
|---|
| 611 | 710 | return lic; |
|---|
| 612 | 711 | } |
|---|
| 613 | 712 | |
|---|
| 713 | + // --------------------------------------------------------------------- |
|---|
| 714 | + // DTOs |
|---|
| 715 | + // --------------------------------------------------------------------- |
|---|
| 716 | + |
|---|
| 717 | + /** |
|---|
| 718 | + * DTO used to carry a cancellation reason for the cancel endpoint. |
|---|
| 719 | + */ |
|---|
| 614 | 720 | @JsonAutoDetect |
|---|
| 615 | 721 | @JsonIgnoreProperties(ignoreUnknown = true) |
|---|
| 616 | 722 | static class CancellationLicenseActionBean { |
|---|
| .. | .. |
|---|
| 618 | 724 | private String reason; |
|---|
| 619 | 725 | } |
|---|
| 620 | 726 | } |
|---|
| 727 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Date; |
|---|
| .. | .. |
|---|
| 44 | 47 | import net.curisit.securis.utils.TokenHelper; |
|---|
| 45 | 48 | |
|---|
| 46 | 49 | /** |
|---|
| 47 | | - * LicenseType resource, this service will provide methods to create, modify and |
|---|
| 48 | | - * delete license types |
|---|
| 49 | | - * |
|---|
| 50 | | - * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 50 | + * LicenseTypeResource |
|---|
| 51 | + * <p> |
|---|
| 52 | + * CRUD for license types. Non-admin queries are scoped to the applications |
|---|
| 53 | + * accessible by the caller. Metadata changes are reconciled and, when keys |
|---|
| 54 | + * change, can be propagated to dependent entities via {@link MetadataHelper}. |
|---|
| 55 | + * |
|---|
| 56 | + * @author JRA |
|---|
| 57 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 51 | 58 | */ |
|---|
| 52 | 59 | @Path("/licensetype") |
|---|
| 53 | 60 | public class LicenseTypeResource { |
|---|
| 54 | 61 | |
|---|
| 55 | 62 | private static final Logger LOG = LogManager.getLogger(LicenseTypeResource.class); |
|---|
| 56 | 63 | |
|---|
| 57 | | - @Inject |
|---|
| 58 | | - TokenHelper tokenHelper; |
|---|
| 64 | + @Inject TokenHelper tokenHelper; |
|---|
| 65 | + @Inject MetadataHelper metadataHelper; |
|---|
| 59 | 66 | |
|---|
| 60 | | - @Inject |
|---|
| 61 | | - MetadataHelper metadataHelper; |
|---|
| 67 | + @Context EntityManager em; |
|---|
| 62 | 68 | |
|---|
| 63 | | - @Context |
|---|
| 64 | | - EntityManager em; |
|---|
| 65 | | - |
|---|
| 66 | | - public LicenseTypeResource() { |
|---|
| 67 | | - } |
|---|
| 69 | + public LicenseTypeResource() { } |
|---|
| 68 | 70 | |
|---|
| 69 | 71 | /** |
|---|
| 70 | | - * |
|---|
| 71 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 72 | + * index |
|---|
| 73 | + * <p> |
|---|
| 74 | + * List license types. Non-admin users get only types for their allowed apps. |
|---|
| 75 | + * |
|---|
| 76 | + * @param bsc security context. |
|---|
| 77 | + * @return 200 OK with list (possibly empty). |
|---|
| 72 | 78 | */ |
|---|
| 73 | 79 | @GET |
|---|
| 74 | 80 | @Path("/") |
|---|
| .. | .. |
|---|
| 76 | 82 | @Securable |
|---|
| 77 | 83 | public Response index(@Context BasicSecurityContext bsc) { |
|---|
| 78 | 84 | LOG.info("Getting license types list "); |
|---|
| 79 | | - |
|---|
| 80 | | - // EntityManager em = emProvider.get(); |
|---|
| 81 | 85 | em.clear(); |
|---|
| 82 | 86 | TypedQuery<LicenseType> q; |
|---|
| 83 | 87 | if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) { |
|---|
| .. | .. |
|---|
| 87 | 91 | return Response.ok().build(); |
|---|
| 88 | 92 | } |
|---|
| 89 | 93 | q = em.createNamedQuery("list-license_types-by_apps-id", LicenseType.class); |
|---|
| 90 | | - |
|---|
| 91 | 94 | q.setParameter("list_ids", bsc.getApplicationsIds()); |
|---|
| 92 | 95 | } |
|---|
| 93 | 96 | List<LicenseType> list = q.getResultList(); |
|---|
| 94 | | - |
|---|
| 95 | 97 | return Response.ok(list).build(); |
|---|
| 96 | 98 | } |
|---|
| 97 | 99 | |
|---|
| 98 | 100 | /** |
|---|
| 99 | | - * |
|---|
| 100 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 101 | | - * @throws SeCurisServiceException |
|---|
| 101 | + * get |
|---|
| 102 | + * <p> |
|---|
| 103 | + * Fetch a license type by id. |
|---|
| 104 | + * |
|---|
| 105 | + * @param ltid LicenseType id (string form). |
|---|
| 106 | + * @param token (unused) header token. |
|---|
| 107 | + * @return 200 OK with the entity. |
|---|
| 108 | + * @throws SeCurisServiceException 404 if not found. |
|---|
| 102 | 109 | */ |
|---|
| 103 | 110 | @GET |
|---|
| 104 | 111 | @Path("/{ltid}") |
|---|
| .. | .. |
|---|
| 111 | 118 | return Response.status(Status.NOT_FOUND).build(); |
|---|
| 112 | 119 | } |
|---|
| 113 | 120 | |
|---|
| 114 | | - // EntityManager em = emProvider.get(); |
|---|
| 115 | 121 | em.clear(); |
|---|
| 116 | 122 | LicenseType lt = em.find(LicenseType.class, Integer.parseInt(ltid)); |
|---|
| 117 | 123 | if (lt == null) { |
|---|
| .. | .. |
|---|
| 121 | 127 | return Response.ok(lt).build(); |
|---|
| 122 | 128 | } |
|---|
| 123 | 129 | |
|---|
| 130 | + /** |
|---|
| 131 | + * create |
|---|
| 132 | + * <p> |
|---|
| 133 | + * Create a new license type. Requires ADMIN. Sets application reference, |
|---|
| 134 | + * persists metadata entries and stamps creation time. |
|---|
| 135 | + * |
|---|
| 136 | + * @param lt Payload. |
|---|
| 137 | + * @param token (unused) token header. |
|---|
| 138 | + * @return 200 OK with created entity, or 404 if app missing. |
|---|
| 139 | + */ |
|---|
| 124 | 140 | @POST |
|---|
| 125 | 141 | @Path("/") |
|---|
| 126 | 142 | @Consumes(MediaType.APPLICATION_JSON) |
|---|
| .. | .. |
|---|
| 130 | 146 | @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 131 | 147 | public Response create(LicenseType lt, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 132 | 148 | LOG.info("Creating new license type"); |
|---|
| 133 | | - // EntityManager em = emProvider.get(); |
|---|
| 134 | 149 | |
|---|
| 135 | 150 | try { |
|---|
| 136 | 151 | setApplication(lt, lt.getApplicationId(), em); |
|---|
| .. | .. |
|---|
| 158 | 173 | return Response.ok(lt).build(); |
|---|
| 159 | 174 | } |
|---|
| 160 | 175 | |
|---|
| 161 | | - private Set<String> getMdKeys(Set<LicenseTypeMetadata> mds) { |
|---|
| 162 | | - Set<String> ids = new HashSet<String>(); |
|---|
| 163 | | - if (mds != null) { |
|---|
| 164 | | - for (LicenseTypeMetadata md : mds) { |
|---|
| 165 | | - ids.add(md.getKey()); |
|---|
| 166 | | - } |
|---|
| 167 | | - } |
|---|
| 168 | | - return ids; |
|---|
| 169 | | - } |
|---|
| 170 | | - |
|---|
| 176 | + /** |
|---|
| 177 | + * modify |
|---|
| 178 | + * <p> |
|---|
| 179 | + * Update an existing license type. Reconciles metadata: |
|---|
| 180 | + * removes keys not present in the new set; merges existing; persists new ones. |
|---|
| 181 | + * If keys changed, {@link MetadataHelper#propagateMetadata} is invoked. |
|---|
| 182 | + * |
|---|
| 183 | + * @param lt New values. |
|---|
| 184 | + * @param ltid LicenseType id. |
|---|
| 185 | + * @param token (unused) token. |
|---|
| 186 | + * @return 200 OK with updated entity; 404 if not found or app missing. |
|---|
| 187 | + */ |
|---|
| 171 | 188 | @PUT |
|---|
| 172 | 189 | @POST |
|---|
| 173 | 190 | @Path("/{ltid}") |
|---|
| .. | .. |
|---|
| 178 | 195 | @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 179 | 196 | public Response modify(LicenseType lt, @PathParam("ltid") String ltid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 180 | 197 | LOG.info("Modifying license type with id: {}", ltid); |
|---|
| 181 | | - // EntityManager em = emProvider.get(); |
|---|
| 182 | 198 | LicenseType currentlt = em.find(LicenseType.class, Integer.parseInt(ltid)); |
|---|
| 183 | 199 | if (currentlt == null) { |
|---|
| 184 | 200 | LOG.error("LicenseType with id {} not found in DB", ltid); |
|---|
| .. | .. |
|---|
| 230 | 246 | return Response.ok(currentlt).build(); |
|---|
| 231 | 247 | } |
|---|
| 232 | 248 | |
|---|
| 233 | | - private void setApplication(LicenseType licType, Integer applicationId, EntityManager em) throws SeCurisException { |
|---|
| 234 | | - Application app = null; |
|---|
| 235 | | - if (applicationId != null) { |
|---|
| 236 | | - app = em.find(Application.class, applicationId); |
|---|
| 237 | | - if (app == null) { |
|---|
| 238 | | - LOG.error("LicenseType application with id {} not found in DB", applicationId); |
|---|
| 239 | | - |
|---|
| 240 | | - throw new SecurityException("License type's app not found with ID: " + applicationId); |
|---|
| 241 | | - } |
|---|
| 242 | | - } |
|---|
| 243 | | - licType.setApplication(app); |
|---|
| 244 | | - } |
|---|
| 245 | | - |
|---|
| 249 | + /** |
|---|
| 250 | + * delete |
|---|
| 251 | + * <p> |
|---|
| 252 | + * Delete a license type by id. Requires ADMIN. |
|---|
| 253 | + * |
|---|
| 254 | + * @param ltid LicenseType id. |
|---|
| 255 | + * @param req request (unused). |
|---|
| 256 | + * @return 200 OK on success; 404 if not found. |
|---|
| 257 | + */ |
|---|
| 246 | 258 | @DELETE |
|---|
| 247 | 259 | @Path("/{ltid}") |
|---|
| 248 | 260 | @EnsureTransaction |
|---|
| 249 | 261 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 250 | 262 | @Securable(roles = Rol.ADMIN) |
|---|
| 251 | 263 | @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 252 | | - public Response delete(@PathParam("ltid") String ltid, @Context HttpServletRequest request) { |
|---|
| 264 | + public Response delete(@PathParam("ltid") String ltid, @Context HttpServletRequest req) { |
|---|
| 253 | 265 | LOG.info("Deleting app with id: {}", ltid); |
|---|
| 254 | | - // EntityManager em = emProvider.get(); |
|---|
| 255 | 266 | LicenseType app = em.find(LicenseType.class, Integer.parseInt(ltid)); |
|---|
| 256 | 267 | if (app == null) { |
|---|
| 257 | 268 | LOG.error("LicenseType with id {} can not be deleted, It was not found in DB", ltid); |
|---|
| .. | .. |
|---|
| 262 | 273 | return Response.ok(Utils.createMap("success", true, "id", ltid)).build(); |
|---|
| 263 | 274 | } |
|---|
| 264 | 275 | |
|---|
| 276 | + // --------------------------------------------------------------------- |
|---|
| 277 | + // Helpers |
|---|
| 278 | + // --------------------------------------------------------------------- |
|---|
| 279 | + |
|---|
| 280 | + private Set<String> getMdKeys(Set<LicenseTypeMetadata> mds) { |
|---|
| 281 | + Set<String> ids = new HashSet<String>(); |
|---|
| 282 | + if (mds != null) { |
|---|
| 283 | + for (LicenseTypeMetadata md : mds) { |
|---|
| 284 | + ids.add(md.getKey()); |
|---|
| 285 | + } |
|---|
| 286 | + } |
|---|
| 287 | + return ids; |
|---|
| 288 | + } |
|---|
| 289 | + |
|---|
| 290 | + /** |
|---|
| 291 | + * setApplication<p> |
|---|
| 292 | + * Resolve and set the application of a license type. |
|---|
| 293 | + * |
|---|
| 294 | + * @param licType target LicenseType. |
|---|
| 295 | + * @param applicationId id of the application (nullable). |
|---|
| 296 | + * @param em entity manager. |
|---|
| 297 | + * @throws SeCurisException if id provided but not found. |
|---|
| 298 | + */ |
|---|
| 299 | + private void setApplication(LicenseType licType, Integer applicationId, EntityManager em) throws SeCurisException { |
|---|
| 300 | + Application app = null; |
|---|
| 301 | + if (applicationId != null) { |
|---|
| 302 | + app = em.find(Application.class, applicationId); |
|---|
| 303 | + if (app == null) { |
|---|
| 304 | + LOG.error("LicenseType application with id {} not found in DB", applicationId); |
|---|
| 305 | + throw new SecurityException("License type's app not found with ID: " + applicationId); |
|---|
| 306 | + } |
|---|
| 307 | + } |
|---|
| 308 | + licType.setApplication(app); |
|---|
| 309 | + } |
|---|
| 265 | 310 | } |
|---|
| 311 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Date; |
|---|
| .. | .. |
|---|
| 39 | 42 | import net.curisit.securis.utils.TokenHelper; |
|---|
| 40 | 43 | |
|---|
| 41 | 44 | /** |
|---|
| 42 | | - * Organization resource, this service will provide methods to create, modify |
|---|
| 43 | | - * and delete organizations |
|---|
| 44 | | - * |
|---|
| 45 | | - * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 45 | + * OrganizationResource |
|---|
| 46 | + * <p> |
|---|
| 47 | + * CRUD and listing of organizations. Non-admin users are scoped by their |
|---|
| 48 | + * accessible organization ids when listing. |
|---|
| 49 | + * |
|---|
| 50 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 46 | 51 | */ |
|---|
| 47 | 52 | @Path("/organization") |
|---|
| 48 | 53 | @RequestScoped |
|---|
| .. | .. |
|---|
| 50 | 55 | |
|---|
| 51 | 56 | private static final Logger LOG = LogManager.getLogger(OrganizationResource.class); |
|---|
| 52 | 57 | |
|---|
| 53 | | - @Context |
|---|
| 54 | | - EntityManager em; |
|---|
| 58 | + @Context EntityManager em; |
|---|
| 59 | + @Context BasicSecurityContext bsc; |
|---|
| 55 | 60 | |
|---|
| 56 | | - @Context |
|---|
| 57 | | - BasicSecurityContext bsc; |
|---|
| 58 | | - |
|---|
| 59 | | - public OrganizationResource() { |
|---|
| 60 | | - } |
|---|
| 61 | + public OrganizationResource() { } |
|---|
| 61 | 62 | |
|---|
| 62 | 63 | /** |
|---|
| 63 | | - * |
|---|
| 64 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 64 | + * index |
|---|
| 65 | + * <p> |
|---|
| 66 | + * List organizations. For admins returns all; for non-admins filters |
|---|
| 67 | + * by the ids in {@link BasicSecurityContext#getOrganizationsIds()}. |
|---|
| 68 | + * |
|---|
| 69 | + * @return 200 OK with the list. |
|---|
| 65 | 70 | */ |
|---|
| 66 | 71 | @GET |
|---|
| 67 | 72 | @Path("/") |
|---|
| .. | .. |
|---|
| 69 | 74 | @Securable |
|---|
| 70 | 75 | public Response index() { |
|---|
| 71 | 76 | LOG.info("Getting organizations list "); |
|---|
| 72 | | - |
|---|
| 73 | | - // EntityManager em = emProvider.get(); |
|---|
| 74 | 77 | em.clear(); |
|---|
| 75 | 78 | TypedQuery<Organization> q; |
|---|
| 76 | 79 | if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) { |
|---|
| .. | .. |
|---|
| 84 | 87 | q.setParameter("list_ids", bsc.getOrganizationsIds()); |
|---|
| 85 | 88 | } |
|---|
| 86 | 89 | } |
|---|
| 87 | | - |
|---|
| 88 | 90 | List<Organization> list = q.getResultList(); |
|---|
| 89 | | - |
|---|
| 90 | 91 | return Response.ok(list).build(); |
|---|
| 91 | 92 | } |
|---|
| 92 | 93 | |
|---|
| 93 | 94 | /** |
|---|
| 94 | | - * |
|---|
| 95 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 95 | + * get |
|---|
| 96 | + * <p> |
|---|
| 97 | + * Fetch an organization by id. |
|---|
| 98 | + * |
|---|
| 99 | + * @param orgid organization id (string form). |
|---|
| 100 | + * @param token header token (unused). |
|---|
| 101 | + * @return 200 OK with entity or 404 if not found. |
|---|
| 96 | 102 | */ |
|---|
| 97 | 103 | @GET |
|---|
| 98 | 104 | @Path("/{orgid}") |
|---|
| .. | .. |
|---|
| 104 | 110 | LOG.error("Organization ID is mandatory"); |
|---|
| 105 | 111 | return Response.status(Status.NOT_FOUND).build(); |
|---|
| 106 | 112 | } |
|---|
| 107 | | - |
|---|
| 108 | | - // EntityManager em = emProvider.get(); |
|---|
| 109 | 113 | em.clear(); |
|---|
| 110 | 114 | Organization org = em.find(Organization.class, Integer.parseInt(orgid)); |
|---|
| 111 | 115 | if (org == null) { |
|---|
| .. | .. |
|---|
| 115 | 119 | return Response.ok(org).build(); |
|---|
| 116 | 120 | } |
|---|
| 117 | 121 | |
|---|
| 118 | | - private boolean isCyclicalRelationship(int currentId, Organization parent) { |
|---|
| 119 | | - while (parent != null) { |
|---|
| 120 | | - if (parent.getId() == currentId) { |
|---|
| 121 | | - return true; |
|---|
| 122 | | - } |
|---|
| 123 | | - parent = parent.getParentOrganization(); |
|---|
| 124 | | - } |
|---|
| 125 | | - return false; |
|---|
| 126 | | - } |
|---|
| 127 | | - |
|---|
| 122 | + /** |
|---|
| 123 | + * create |
|---|
| 124 | + * <p> |
|---|
| 125 | + * Create a new organization, setting optional parent and user members. |
|---|
| 126 | + * Requires ADMIN. |
|---|
| 127 | + * |
|---|
| 128 | + * @param org payload with code/name/etc., optional parentOrgId and usersIds. |
|---|
| 129 | + * @return 200 OK with created organization or 404 when parent/user not found. |
|---|
| 130 | + */ |
|---|
| 128 | 131 | @POST |
|---|
| 129 | 132 | @Path("/") |
|---|
| 130 | 133 | @Consumes(MediaType.APPLICATION_JSON) |
|---|
| .. | .. |
|---|
| 134 | 137 | @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 135 | 138 | public Response create(Organization org) { |
|---|
| 136 | 139 | LOG.info("Creating new organization"); |
|---|
| 137 | | - // EntityManager em = emProvider.get(); |
|---|
| 138 | 140 | |
|---|
| 139 | 141 | try { |
|---|
| 140 | 142 | this.setParentOrg(org, org.getParentOrgId(), em); |
|---|
| .. | .. |
|---|
| 162 | 164 | return Response.ok(org).build(); |
|---|
| 163 | 165 | } |
|---|
| 164 | 166 | |
|---|
| 165 | | - private void setParentOrg(Organization org, Integer parentOrgId, EntityManager em) throws SeCurisException { |
|---|
| 166 | | - Organization parentOrg = null; |
|---|
| 167 | | - if (parentOrgId != null) { |
|---|
| 168 | | - parentOrg = em.find(Organization.class, parentOrgId); |
|---|
| 169 | | - if (parentOrg == null) { |
|---|
| 170 | | - LOG.error("Organization parent with id {} not found in DB", org.getParentOrgId()); |
|---|
| 171 | | - throw new SecurityException("Organization's parent not found with ID: " + org.getParentOrgId()); |
|---|
| 172 | | - } |
|---|
| 173 | | - } |
|---|
| 174 | | - |
|---|
| 175 | | - org.setParentOrganization(parentOrg); |
|---|
| 176 | | - } |
|---|
| 177 | | - |
|---|
| 178 | | - private void setOrgUsers(Organization org, Set<String> usersIds, EntityManager em) throws SeCurisException { |
|---|
| 179 | | - Set<User> users = null; |
|---|
| 180 | | - if (usersIds != null && !usersIds.isEmpty()) { |
|---|
| 181 | | - users = new HashSet<>(); |
|---|
| 182 | | - for (String username : usersIds) { |
|---|
| 183 | | - User user = em.find(User.class, username); |
|---|
| 184 | | - if (user == null) { |
|---|
| 185 | | - LOG.error("Organization user with id '{}' not found in DB", username); |
|---|
| 186 | | - throw new SecurityException("Organization's user not found with ID: " + username); |
|---|
| 187 | | - } |
|---|
| 188 | | - users.add(user); |
|---|
| 189 | | - } |
|---|
| 190 | | - } |
|---|
| 191 | | - |
|---|
| 192 | | - org.setUsers(users); |
|---|
| 193 | | - } |
|---|
| 194 | | - |
|---|
| 167 | + /** |
|---|
| 168 | + * modify |
|---|
| 169 | + * <p> |
|---|
| 170 | + * Update an organization. Validates no cyclic parent relationship, |
|---|
| 171 | + * updates parent and user set. Requires ADMIN. |
|---|
| 172 | + * |
|---|
| 173 | + * @param org new values (including optional parent/usersIds). |
|---|
| 174 | + * @param orgid target id. |
|---|
| 175 | + * @param token (unused) header token. |
|---|
| 176 | + * @return 200 OK with updated organization, or specific error status. |
|---|
| 177 | + */ |
|---|
| 195 | 178 | @PUT |
|---|
| 196 | 179 | @POST |
|---|
| 197 | 180 | @Path("/{orgid}") |
|---|
| .. | .. |
|---|
| 202 | 185 | @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 203 | 186 | public Response modify(Organization org, @PathParam("orgid") String orgid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 204 | 187 | LOG.info("Modifying organization with id: {}", orgid); |
|---|
| 205 | | - // EntityManager em = emProvider.get(); |
|---|
| 206 | 188 | Organization currentOrg = em.find(Organization.class, Integer.parseInt(orgid)); |
|---|
| 207 | 189 | if (currentOrg == null) { |
|---|
| 208 | 190 | LOG.error("Organization with id {} not found in DB", orgid); |
|---|
| .. | .. |
|---|
| 233 | 215 | return Response.ok(currentOrg).build(); |
|---|
| 234 | 216 | } |
|---|
| 235 | 217 | |
|---|
| 218 | + /** |
|---|
| 219 | + * delete |
|---|
| 220 | + * <p> |
|---|
| 221 | + * Delete an organization if it has no children. Requires ADMIN. |
|---|
| 222 | + * |
|---|
| 223 | + * @param orgid target id. |
|---|
| 224 | + * @param req request (unused). |
|---|
| 225 | + * @return 200 OK with success map, or 404/403 on constraints. |
|---|
| 226 | + */ |
|---|
| 236 | 227 | @DELETE |
|---|
| 237 | 228 | @Path("/{orgid}") |
|---|
| 238 | 229 | @EnsureTransaction |
|---|
| 239 | 230 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 240 | 231 | @Securable(roles = Rol.ADMIN) |
|---|
| 241 | 232 | @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 242 | | - public Response delete(@PathParam("orgid") String orgid, @Context HttpServletRequest request) { |
|---|
| 233 | + public Response delete(@PathParam("orgid") String orgid, @Context HttpServletRequest req) { |
|---|
| 243 | 234 | LOG.info("Deleting organization with id: {}", orgid); |
|---|
| 244 | | - // EntityManager em = emProvider.get(); |
|---|
| 245 | 235 | Organization org = em.find(Organization.class, Integer.parseInt(orgid)); |
|---|
| 246 | 236 | if (org == null) { |
|---|
| 247 | 237 | LOG.error("Organization with id {} can not be deleted, It was not found in DB", orgid); |
|---|
| .. | .. |
|---|
| 256 | 246 | return Response.ok(Utils.createMap("success", true, "id", orgid)).build(); |
|---|
| 257 | 247 | } |
|---|
| 258 | 248 | |
|---|
| 249 | + // --------------------------------------------------------------------- |
|---|
| 250 | + // Helpers |
|---|
| 251 | + // --------------------------------------------------------------------- |
|---|
| 252 | + |
|---|
| 253 | + /** |
|---|
| 254 | + * isCyclicalRelationship<p> |
|---|
| 255 | + * Detects cycles by walking up the parent chain. |
|---|
| 256 | + * |
|---|
| 257 | + * @param currentId |
|---|
| 258 | + * @param parent |
|---|
| 259 | + * @return isCyclicalRelationship |
|---|
| 260 | + */ |
|---|
| 261 | + private boolean isCyclicalRelationship(int currentId, Organization parent) { |
|---|
| 262 | + while (parent != null) { |
|---|
| 263 | + if (parent.getId() == currentId) return true; |
|---|
| 264 | + parent = parent.getParentOrganization(); |
|---|
| 265 | + } |
|---|
| 266 | + return false; |
|---|
| 267 | + } |
|---|
| 268 | + |
|---|
| 269 | + /** |
|---|
| 270 | + * setParentOrg<p> |
|---|
| 271 | + * Resolve and set parent organization (nullable). |
|---|
| 272 | + * |
|---|
| 273 | + * @param org |
|---|
| 274 | + * @param parentOrgId |
|---|
| 275 | + * @param entitymanager |
|---|
| 276 | + * @throws SeCurisException |
|---|
| 277 | + */ |
|---|
| 278 | + private void setParentOrg(Organization org, Integer parentOrgId, EntityManager em) throws SeCurisException { |
|---|
| 279 | + Organization parentOrg = null; |
|---|
| 280 | + if (parentOrgId != null) { |
|---|
| 281 | + parentOrg = em.find(Organization.class, parentOrgId); |
|---|
| 282 | + if (parentOrg == null) { |
|---|
| 283 | + LOG.error("Organization parent with id {} not found in DB", org.getParentOrgId()); |
|---|
| 284 | + throw new SecurityException("Organization's parent not found with ID: " + org.getParentOrgId()); |
|---|
| 285 | + } |
|---|
| 286 | + } |
|---|
| 287 | + org.setParentOrganization(parentOrg); |
|---|
| 288 | + } |
|---|
| 289 | + |
|---|
| 290 | + /** |
|---|
| 291 | + * setOrgUsers<p> |
|---|
| 292 | + * Replace organization users from the provided usernames set. |
|---|
| 293 | + * |
|---|
| 294 | + * @param org |
|---|
| 295 | + * @param usersIds |
|---|
| 296 | + * @param entityManager |
|---|
| 297 | + * @throws SeCurisException |
|---|
| 298 | + */ |
|---|
| 299 | + private void setOrgUsers(Organization org, Set<String> usersIds, EntityManager em) throws SeCurisException { |
|---|
| 300 | + Set<User> users = null; |
|---|
| 301 | + if (usersIds != null && !usersIds.isEmpty()) { |
|---|
| 302 | + users = new HashSet<>(); |
|---|
| 303 | + for (String username : usersIds) { |
|---|
| 304 | + User user = em.find(User.class, username); |
|---|
| 305 | + if (user == null) { |
|---|
| 306 | + LOG.error("Organization user with id '{}' not found in DB", username); |
|---|
| 307 | + throw new SecurityException("Organization's user not found with ID: " + username); |
|---|
| 308 | + } |
|---|
| 309 | + users.add(user); |
|---|
| 310 | + } |
|---|
| 311 | + } |
|---|
| 312 | + org.setUsers(users); |
|---|
| 313 | + } |
|---|
| 259 | 314 | } |
|---|
| 315 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.security.Principal; |
|---|
| .. | .. |
|---|
| 53 | 56 | import net.curisit.securis.utils.TokenHelper; |
|---|
| 54 | 57 | |
|---|
| 55 | 58 | /** |
|---|
| 56 | | - * Pack resource, this service will provide methods to create, modify and delete |
|---|
| 57 | | - * packs |
|---|
| 58 | | - * |
|---|
| 59 | | - * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 59 | + * PackResource |
|---|
| 60 | + * <p> |
|---|
| 61 | + * Manages Packs (group of licenses bound to an organization, application/type, |
|---|
| 62 | + * and configuration/metadata). Provides list/filter, get, create, modify, |
|---|
| 63 | + * state transitions (activate/hold/cancel) and deletion. |
|---|
| 64 | + * |
|---|
| 65 | + * @author JRA |
|---|
| 66 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 60 | 67 | */ |
|---|
| 61 | 68 | @Path("/pack") |
|---|
| 62 | 69 | public class PackResource { |
|---|
| 63 | 70 | |
|---|
| 64 | 71 | private static final Logger LOG = LogManager.getLogger(PackResource.class); |
|---|
| 65 | 72 | |
|---|
| 66 | | - @Inject |
|---|
| 67 | | - TokenHelper tokenHelper; |
|---|
| 73 | + @Inject TokenHelper tokenHelper; |
|---|
| 74 | + @Inject MetadataHelper metadataHelper; |
|---|
| 75 | + @Inject private LicenseHelper licenseHelper; |
|---|
| 68 | 76 | |
|---|
| 69 | | - @Inject |
|---|
| 70 | | - MetadataHelper metadataHelper; |
|---|
| 71 | | - |
|---|
| 72 | | - @Context |
|---|
| 73 | | - EntityManager em; |
|---|
| 74 | | - |
|---|
| 75 | | - @Inject |
|---|
| 76 | | - private LicenseHelper licenseHelper; |
|---|
| 77 | + @Context EntityManager em; |
|---|
| 77 | 78 | |
|---|
| 78 | 79 | /** |
|---|
| 79 | | - * |
|---|
| 80 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 80 | + * index |
|---|
| 81 | + * <p> |
|---|
| 82 | + * List packs with optional filters (organizationId, applicationId, licenseTypeId). |
|---|
| 83 | + * For non-admins, results are scoped by both apps and orgs from {@link BasicSecurityContext}. |
|---|
| 84 | + * |
|---|
| 85 | + * @param uriInfo supplies query parameters. |
|---|
| 86 | + * @param bsc security scope/roles. |
|---|
| 87 | + * @return 200 OK with the list (possibly empty). |
|---|
| 81 | 88 | */ |
|---|
| 82 | 89 | @GET |
|---|
| 83 | 90 | @Path("/") |
|---|
| .. | .. |
|---|
| 86 | 93 | public Response index(@Context UriInfo uriInfo, @Context BasicSecurityContext bsc) { |
|---|
| 87 | 94 | LOG.info("Getting packs list "); |
|---|
| 88 | 95 | MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters(); |
|---|
| 89 | | - |
|---|
| 90 | | - // EntityManager em = emProvider.get(); |
|---|
| 91 | 96 | em.clear(); |
|---|
| 92 | 97 | |
|---|
| 93 | 98 | TypedQuery<Pack> q = createQuery(queryParams, bsc); |
|---|
| 94 | 99 | if (q == null) { |
|---|
| 95 | 100 | return Response.ok().build(); |
|---|
| 96 | 101 | } |
|---|
| 97 | | - |
|---|
| 98 | 102 | List<Pack> list = q.getResultList(); |
|---|
| 99 | | - |
|---|
| 100 | 103 | return Response.ok(list).build(); |
|---|
| 101 | 104 | } |
|---|
| 102 | 105 | |
|---|
| 103 | | - private String generateWhereFromParams(boolean addWhere, MultivaluedMap<String, String> queryParams) { |
|---|
| 104 | | - List<String> conditions = new ArrayList<>(); |
|---|
| 105 | | - if (queryParams.containsKey("organizationId")) { |
|---|
| 106 | | - conditions.add(String.format("pa.organization.id = %s", queryParams.getFirst("organizationId"))); |
|---|
| 107 | | - } |
|---|
| 108 | | - if (queryParams.containsKey("applicationId")) { |
|---|
| 109 | | - conditions.add(String.format("pa.licenseType.application.id = %s", queryParams.getFirst("applicationId"))); |
|---|
| 110 | | - } |
|---|
| 111 | | - if (queryParams.containsKey("licenseTypeId")) { |
|---|
| 112 | | - conditions.add(String.format("pa.licenseType.id = %s", queryParams.getFirst("licenseTypeId"))); |
|---|
| 113 | | - } |
|---|
| 114 | | - String connector = addWhere ? " where " : " and "; |
|---|
| 115 | | - return (conditions.isEmpty() ? "" : connector) + String.join(" and ", conditions); |
|---|
| 116 | | - } |
|---|
| 117 | | - |
|---|
| 118 | | - private TypedQuery<Pack> createQuery(MultivaluedMap<String, String> queryParams, BasicSecurityContext bsc) { |
|---|
| 119 | | - TypedQuery<Pack> q; |
|---|
| 120 | | - String hql = "SELECT pa FROM Pack pa"; |
|---|
| 121 | | - if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) { |
|---|
| 122 | | - hql += generateWhereFromParams(true, queryParams); |
|---|
| 123 | | - q = em.createQuery(hql, Pack.class); |
|---|
| 124 | | - } else { |
|---|
| 125 | | - if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) { |
|---|
| 126 | | - return null; |
|---|
| 127 | | - } |
|---|
| 128 | | - if (bsc.getOrganizationsIds() == null || bsc.getOrganizationsIds().isEmpty()) { |
|---|
| 129 | | - hql += " where pa.licenseType.application.id in :list_ids_app "; |
|---|
| 130 | | - } else { |
|---|
| 131 | | - hql += " where pa.organization.id in :list_ids_org and pa.licenseType.application.id in :list_ids_app "; |
|---|
| 132 | | - } |
|---|
| 133 | | - hql += generateWhereFromParams(false, queryParams); |
|---|
| 134 | | - q = em.createQuery(hql, Pack.class); |
|---|
| 135 | | - if (hql.contains("list_ids_org")) { |
|---|
| 136 | | - q.setParameter("list_ids_org", bsc.getOrganizationsIds()); |
|---|
| 137 | | - } |
|---|
| 138 | | - q.setParameter("list_ids_app", bsc.getApplicationsIds()); |
|---|
| 139 | | - LOG.info("Getting packs from orgs: {} and apps: {}", bsc.getOrganizationsIds(), bsc.getApplicationsIds()); |
|---|
| 140 | | - } |
|---|
| 141 | | - |
|---|
| 142 | | - return q; |
|---|
| 143 | | - } |
|---|
| 144 | | - |
|---|
| 145 | | - private Response generateErrorUnathorizedAccess(Pack pack, Principal user) { |
|---|
| 146 | | - LOG.error("Pack with id {} not accesible by user {}", pack, user); |
|---|
| 147 | | - return Response.status(Status.UNAUTHORIZED).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Unathorized access to pack").build(); |
|---|
| 148 | | - } |
|---|
| 149 | | - |
|---|
| 150 | 106 | /** |
|---|
| 151 | | - * |
|---|
| 152 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 107 | + * get |
|---|
| 108 | + * <p> |
|---|
| 109 | + * Fetch a pack by id. If the caller is an ADVANCE user, validates |
|---|
| 110 | + * the organization scope. |
|---|
| 111 | + * |
|---|
| 112 | + * @param packId pack id. |
|---|
| 113 | + * @param bsc security context. |
|---|
| 114 | + * @return 200 OK with entity, or 404/401 accordingly. |
|---|
| 153 | 115 | */ |
|---|
| 154 | 116 | @GET |
|---|
| 155 | 117 | @Path("/{packId}") |
|---|
| .. | .. |
|---|
| 162 | 124 | return Response.status(Status.NOT_FOUND).build(); |
|---|
| 163 | 125 | } |
|---|
| 164 | 126 | |
|---|
| 165 | | - // EntityManager em = emProvider.get(); |
|---|
| 166 | 127 | em.clear(); |
|---|
| 167 | 128 | Pack pack = em.find(Pack.class, packId); |
|---|
| 168 | 129 | if (pack == null) { |
|---|
| .. | .. |
|---|
| 175 | 136 | return Response.ok(pack).build(); |
|---|
| 176 | 137 | } |
|---|
| 177 | 138 | |
|---|
| 139 | + /** |
|---|
| 140 | + * create |
|---|
| 141 | + * <p> |
|---|
| 142 | + * Create a new pack. Validates code uniqueness, sets organization and |
|---|
| 143 | + * license type references, stamps creator and timestamps, and persists |
|---|
| 144 | + * metadata entries. |
|---|
| 145 | + * |
|---|
| 146 | + * @param pack payload. |
|---|
| 147 | + * @param bsc security context (for createdBy). |
|---|
| 148 | + * @return 200 OK with created pack or 404 when references not found. |
|---|
| 149 | + * @throws SeCurisServiceException on duplicated code. |
|---|
| 150 | + */ |
|---|
| 178 | 151 | @POST |
|---|
| 179 | 152 | @Path("/") |
|---|
| 180 | 153 | @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| .. | .. |
|---|
| 184 | 157 | @EnsureTransaction |
|---|
| 185 | 158 | public Response create(Pack pack, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 186 | 159 | LOG.info("Creating new pack"); |
|---|
| 187 | | - // EntityManager em = emProvider.get(); |
|---|
| 188 | 160 | |
|---|
| 189 | 161 | if (checkIfCodeExists(pack.getCode(), em)) { |
|---|
| 190 | 162 | throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The pack code is already used in an existing pack"); |
|---|
| .. | .. |
|---|
| 221 | 193 | } |
|---|
| 222 | 194 | |
|---|
| 223 | 195 | /** |
|---|
| 224 | | - * Check if there is some pack with the same code |
|---|
| 225 | | - * |
|---|
| 226 | | - * @param code |
|---|
| 227 | | - * Pack code |
|---|
| 228 | | - * @param em |
|---|
| 229 | | - * DB session object |
|---|
| 230 | | - * @return <code>true</code> if code is already used, <code>false</code> |
|---|
| 231 | | - * otherwise |
|---|
| 196 | + * modify |
|---|
| 197 | + * <p> |
|---|
| 198 | + * Update a pack basic fields and reconcile metadata (remove/merge/persist). |
|---|
| 199 | + * If metadata keys changed, marks dependent licenses metadata as obsolete via |
|---|
| 200 | + * {@link MetadataHelper#markObsoleteMetadata}. |
|---|
| 201 | + * |
|---|
| 202 | + * @param pack payload values. |
|---|
| 203 | + * @param packId target id. |
|---|
| 204 | + * @return 200 OK with updated pack or 404 on ref errors. |
|---|
| 232 | 205 | */ |
|---|
| 233 | | - private boolean checkIfCodeExists(String code, EntityManager em) { |
|---|
| 234 | | - TypedQuery<Pack> query = em.createNamedQuery("pack-by-code", Pack.class); |
|---|
| 235 | | - query.setParameter("code", code); |
|---|
| 236 | | - int packs = query.getResultList().size(); |
|---|
| 237 | | - return packs > 0; |
|---|
| 238 | | - } |
|---|
| 239 | | - |
|---|
| 240 | | - /** |
|---|
| 241 | | - * |
|---|
| 242 | | - * @return The next available code suffix in pack for license code |
|---|
| 243 | | - * @throws SeCurisServiceException |
|---|
| 244 | | - */ |
|---|
| 245 | | - @GET |
|---|
| 246 | | - @Path("/{packId}/next_license_code") |
|---|
| 247 | | - @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 248 | | - @Produces({ MediaType.TEXT_PLAIN }) |
|---|
| 249 | | - public Response getCodeSuffix(@PathParam("packId") Integer packId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 250 | | - // EntityManager em = emProvider.get(); |
|---|
| 251 | | - |
|---|
| 252 | | - if (packId == null) { |
|---|
| 253 | | - throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The pack code is mandatory"); |
|---|
| 254 | | - } |
|---|
| 255 | | - Integer codeSuffix = licenseHelper.getNextCodeSuffix(packId, em); |
|---|
| 256 | | - Pack pack = em.find(Pack.class, packId); |
|---|
| 257 | | - ; |
|---|
| 258 | | - |
|---|
| 259 | | - String licCode = LicUtils.getLicenseCode(pack.getCode(), codeSuffix); |
|---|
| 260 | | - return Response.ok(licCode).build(); |
|---|
| 261 | | - } |
|---|
| 262 | | - |
|---|
| 263 | | - private void setPackLicenseType(Pack pack, Integer licTypeId, EntityManager em) throws SeCurisException { |
|---|
| 264 | | - LicenseType lt = null; |
|---|
| 265 | | - if (licTypeId != null) { |
|---|
| 266 | | - lt = em.find(LicenseType.class, pack.getLicTypeId()); |
|---|
| 267 | | - if (lt == null) { |
|---|
| 268 | | - LOG.error("Pack license type with id {} not found in DB", licTypeId); |
|---|
| 269 | | - throw new SeCurisException("Pack license type not found with ID: " + licTypeId); |
|---|
| 270 | | - } |
|---|
| 271 | | - } |
|---|
| 272 | | - pack.setLicenseType(lt); |
|---|
| 273 | | - } |
|---|
| 274 | | - |
|---|
| 275 | | - private Set<String> getMdKeys(Set<PackMetadata> mds) { |
|---|
| 276 | | - Set<String> ids = new HashSet<String>(); |
|---|
| 277 | | - if (mds != null) { |
|---|
| 278 | | - for (PackMetadata md : mds) { |
|---|
| 279 | | - ids.add(md.getKey()); |
|---|
| 280 | | - } |
|---|
| 281 | | - } |
|---|
| 282 | | - return ids; |
|---|
| 283 | | - } |
|---|
| 284 | | - |
|---|
| 285 | 206 | @PUT |
|---|
| 286 | 207 | @POST |
|---|
| 287 | 208 | @Path("/{packId}") |
|---|
| .. | .. |
|---|
| 292 | 213 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 293 | 214 | public Response modify(Pack pack, @PathParam("packId") Integer packId) { |
|---|
| 294 | 215 | LOG.info("Modifying pack with id: {}", packId); |
|---|
| 295 | | - // EntityManager em = emProvider.get(); |
|---|
| 296 | 216 | Pack currentPack = em.find(Pack.class, packId); |
|---|
| 297 | 217 | |
|---|
| 298 | 218 | try { |
|---|
| .. | .. |
|---|
| 348 | 268 | return Response.ok(currentPack).build(); |
|---|
| 349 | 269 | } |
|---|
| 350 | 270 | |
|---|
| 271 | + /** |
|---|
| 272 | + * activate |
|---|
| 273 | + * <p> |
|---|
| 274 | + * Move a pack to ACTIVE (only from allowed states). |
|---|
| 275 | + * |
|---|
| 276 | + * @param packId target pack id. |
|---|
| 277 | + * @return 200 OK with updated pack or error when invalid transition. |
|---|
| 278 | + * @throws SeCurisServiceException when invalid state transition. |
|---|
| 279 | + */ |
|---|
| 351 | 280 | @POST |
|---|
| 352 | 281 | @Path("/{packId}/activate") |
|---|
| 353 | 282 | @EnsureTransaction |
|---|
| .. | .. |
|---|
| 357 | 286 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 358 | 287 | public Response activate(@PathParam("packId") Integer packId) throws SeCurisServiceException { |
|---|
| 359 | 288 | LOG.info("Activating pack with id: {}", packId); |
|---|
| 360 | | - // EntityManager em = emProvider.get(); |
|---|
| 361 | 289 | |
|---|
| 362 | 290 | Pack currentPack = em.find(Pack.class, packId); |
|---|
| 363 | 291 | |
|---|
| .. | .. |
|---|
| 372 | 300 | return Response.ok(currentPack).build(); |
|---|
| 373 | 301 | } |
|---|
| 374 | 302 | |
|---|
| 303 | + /** |
|---|
| 304 | + * onhold |
|---|
| 305 | + * <p> |
|---|
| 306 | + * Put a pack ON_HOLD from allowed states. |
|---|
| 307 | + * |
|---|
| 308 | + * @param packId id. |
|---|
| 309 | + * @return 200 OK with updated pack or error on invalid state. |
|---|
| 310 | + * @throws SeCurisServiceException on invalid state. |
|---|
| 311 | + */ |
|---|
| 375 | 312 | @POST |
|---|
| 376 | | - @Path("/{packId}/putonhold") |
|---|
| 313 | + @Path("/{packId}/putonhold}") |
|---|
| 377 | 314 | @EnsureTransaction |
|---|
| 378 | 315 | @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 379 | 316 | @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| .. | .. |
|---|
| 381 | 318 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 382 | 319 | public Response onhold(@PathParam("packId") Integer packId) throws SeCurisServiceException { |
|---|
| 383 | 320 | LOG.info("Putting On hold pack with id: {}", packId); |
|---|
| 384 | | - // EntityManager em = emProvider.get(); |
|---|
| 385 | 321 | |
|---|
| 386 | 322 | Pack currentPack = em.find(Pack.class, packId); |
|---|
| 387 | 323 | |
|---|
| .. | .. |
|---|
| 396 | 332 | return Response.ok(currentPack).build(); |
|---|
| 397 | 333 | } |
|---|
| 398 | 334 | |
|---|
| 335 | + /** |
|---|
| 336 | + * cancel |
|---|
| 337 | + * <p> |
|---|
| 338 | + * Cancel a pack. Cascades cancel to ACTIVE/PRE_ACTIVE licenses in the pack |
|---|
| 339 | + * via {@link LicenseHelper#cancelLicense}. |
|---|
| 340 | + * |
|---|
| 341 | + * @param packId id. |
|---|
| 342 | + * @param reason cancellation reason. |
|---|
| 343 | + * @param bsc actor for history entries. |
|---|
| 344 | + * @return 200 OK with updated pack. |
|---|
| 345 | + * @throws SeCurisServiceException on invalid state. |
|---|
| 346 | + */ |
|---|
| 399 | 347 | @POST |
|---|
| 400 | 348 | @Path("/{packId}/cancel") |
|---|
| 401 | 349 | @EnsureTransaction |
|---|
| .. | .. |
|---|
| 405 | 353 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 406 | 354 | public Response cancel(@PathParam("packId") Integer packId, @FormParam("reason") String reason, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 407 | 355 | LOG.info("Cancelling pack with id: {}", packId); |
|---|
| 408 | | - // EntityManager em = emProvider.get(); |
|---|
| 409 | 356 | |
|---|
| 410 | 357 | Pack currentPack = em.find(Pack.class, packId); |
|---|
| 411 | 358 | |
|---|
| .. | .. |
|---|
| 426 | 373 | return Response.ok(currentPack).build(); |
|---|
| 427 | 374 | } |
|---|
| 428 | 375 | |
|---|
| 429 | | - private void setPackOrganization(Pack currentPack, Integer orgId, EntityManager em) throws SeCurisException { |
|---|
| 430 | | - Organization org = null; |
|---|
| 431 | | - if (orgId != null) { |
|---|
| 432 | | - org = em.find(Organization.class, orgId); |
|---|
| 433 | | - if (org == null) { |
|---|
| 434 | | - LOG.error("Organization pack with id {} not found in DB", orgId); |
|---|
| 435 | | - throw new SeCurisException("Pack organization not found with ID: " + orgId); |
|---|
| 436 | | - } |
|---|
| 376 | + /** |
|---|
| 377 | + * getCodeSuffix |
|---|
| 378 | + * <p> |
|---|
| 379 | + * Compute the next available license code for a pack, by asking the helper |
|---|
| 380 | + * for the next numeric suffix and composing with {@link LicUtils}. |
|---|
| 381 | + * |
|---|
| 382 | + * @param packId id. |
|---|
| 383 | + * @param bsc (unused) security context. |
|---|
| 384 | + * @return 200 OK with the full code text. |
|---|
| 385 | + * @throws SeCurisServiceException if packId missing. |
|---|
| 386 | + */ |
|---|
| 387 | + @GET |
|---|
| 388 | + @Path("/{packId}/next_license_code") |
|---|
| 389 | + @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| 390 | + @Produces({ MediaType.TEXT_PLAIN }) |
|---|
| 391 | + public Response getCodeSuffix(@PathParam("packId") Integer packId, @Context BasicSecurityContext bsc) throws SeCurisServiceException { |
|---|
| 392 | + if (packId == null) { |
|---|
| 393 | + throw new SeCurisServiceException(ErrorCodes.INVALID_DATA, "The pack code is mandatory"); |
|---|
| 437 | 394 | } |
|---|
| 438 | | - currentPack.setOrganization(org); |
|---|
| 395 | + Integer codeSuffix = licenseHelper.getNextCodeSuffix(packId, em); |
|---|
| 396 | + Pack pack = em.find(Pack.class, packId); |
|---|
| 397 | + String licCode = LicUtils.getLicenseCode(pack.getCode(), codeSuffix); |
|---|
| 398 | + return Response.ok(licCode).build(); |
|---|
| 439 | 399 | } |
|---|
| 440 | 400 | |
|---|
| 401 | + /** |
|---|
| 402 | + * delete |
|---|
| 403 | + * <p> |
|---|
| 404 | + * Delete a pack after ensuring there are no ACTIVE/PRE_ACTIVE licenses. |
|---|
| 405 | + * Removes remaining licenses then the pack itself. |
|---|
| 406 | + * |
|---|
| 407 | + * @param packId String id. |
|---|
| 408 | + * @return 200 OK with success map, 404 if missing, or 409 if active license exists. |
|---|
| 409 | + * @throws SeCurisServiceException on constraint errors. |
|---|
| 410 | + */ |
|---|
| 441 | 411 | @DELETE |
|---|
| 442 | 412 | @Path("/{packId}") |
|---|
| 443 | 413 | @Securable(roles = Rol.ADMIN | Rol.ADVANCE) |
|---|
| .. | .. |
|---|
| 446 | 416 | @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 447 | 417 | public Response delete(@PathParam("packId") String packId) throws SeCurisServiceException { |
|---|
| 448 | 418 | LOG.info("Deleting pack with id: {}", packId); |
|---|
| 449 | | - // EntityManager em = emProvider.get(); |
|---|
| 450 | 419 | Pack pack = em.find(Pack.class, Integer.parseInt(packId)); |
|---|
| 451 | 420 | if (pack == null) { |
|---|
| 452 | 421 | LOG.error("Pack with id {} can not be deleted, It was not found in DB", packId); |
|---|
| 453 | 422 | return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Pack was not found, ID: " + packId).build(); |
|---|
| 454 | 423 | } |
|---|
| 455 | | - // Pack metadata is removed in cascade automatically. |
|---|
| 456 | 424 | |
|---|
| 457 | 425 | Set<License> licenses = pack.getLicenses(); |
|---|
| 458 | 426 | for (License license : licenses) { |
|---|
| .. | .. |
|---|
| 466 | 434 | return Response.ok(Utils.createMap("success", true, "id", packId)).build(); |
|---|
| 467 | 435 | } |
|---|
| 468 | 436 | |
|---|
| 437 | + // --------------------------------------------------------------------- |
|---|
| 438 | + // Helpers |
|---|
| 439 | + // --------------------------------------------------------------------- |
|---|
| 440 | + |
|---|
| 441 | + /** |
|---|
| 442 | + * generateWhereFromParams<p> |
|---|
| 443 | + * Generate where clause to include to a query |
|---|
| 444 | + * |
|---|
| 445 | + * @param addWhere |
|---|
| 446 | + * @param queryParams |
|---|
| 447 | + * @return whereClause |
|---|
| 448 | + */ |
|---|
| 449 | + private String generateWhereFromParams(boolean addWhere, MultivaluedMap<String, String> queryParams) { |
|---|
| 450 | + List<String> conditions = new ArrayList<>(); |
|---|
| 451 | + if (queryParams.containsKey("organizationId")) { |
|---|
| 452 | + conditions.add(String.format("pa.organization.id = %s", queryParams.getFirst("organizationId"))); |
|---|
| 453 | + } |
|---|
| 454 | + if (queryParams.containsKey("applicationId")) { |
|---|
| 455 | + conditions.add(String.format("pa.licenseType.application.id = %s", queryParams.getFirst("applicationId"))); |
|---|
| 456 | + } |
|---|
| 457 | + if (queryParams.containsKey("licenseTypeId")) { |
|---|
| 458 | + conditions.add(String.format("pa.licenseType.id = %s", queryParams.getFirst("licenseTypeId"))); |
|---|
| 459 | + } |
|---|
| 460 | + String connector = addWhere ? " where " : " and "; |
|---|
| 461 | + return (conditions.isEmpty() ? "" : connector) + String.join(" and ", conditions); |
|---|
| 462 | + } |
|---|
| 463 | + |
|---|
| 464 | + /** |
|---|
| 465 | + * createQuery<p> |
|---|
| 466 | + * Build a typed query considering role-based scopes and filters. |
|---|
| 467 | + * |
|---|
| 468 | + * @param queryParams |
|---|
| 469 | + * @param basicSecurityContext |
|---|
| 470 | + */ |
|---|
| 471 | + private TypedQuery<Pack> createQuery(MultivaluedMap<String, String> queryParams, BasicSecurityContext bsc) { |
|---|
| 472 | + TypedQuery<Pack> q; |
|---|
| 473 | + String hql = "SELECT pa FROM Pack pa"; |
|---|
| 474 | + if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) { |
|---|
| 475 | + hql += generateWhereFromParams(true, queryParams); |
|---|
| 476 | + q = em.createQuery(hql, Pack.class); |
|---|
| 477 | + } else { |
|---|
| 478 | + if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) { |
|---|
| 479 | + return null; |
|---|
| 480 | + } |
|---|
| 481 | + if (bsc.getOrganizationsIds() == null || bsc.getOrganizationsIds().isEmpty()) { |
|---|
| 482 | + hql += " where pa.licenseType.application.id in :list_ids_app "; |
|---|
| 483 | + } else { |
|---|
| 484 | + hql += " where pa.organization.id in :list_ids_org and pa.licenseType.application.id in :list_ids_app "; |
|---|
| 485 | + } |
|---|
| 486 | + hql += generateWhereFromParams(false, queryParams); |
|---|
| 487 | + q = em.createQuery(hql, Pack.class); |
|---|
| 488 | + if (hql.contains("list_ids_org")) { |
|---|
| 489 | + q.setParameter("list_ids_org", bsc.getOrganizationsIds()); |
|---|
| 490 | + } |
|---|
| 491 | + q.setParameter("list_ids_app", bsc.getApplicationsIds()); |
|---|
| 492 | + LOG.info("Getting packs from orgs: {} and apps: {}", bsc.getOrganizationsIds(), bsc.getApplicationsIds()); |
|---|
| 493 | + } |
|---|
| 494 | + return q; |
|---|
| 495 | + } |
|---|
| 496 | + |
|---|
| 497 | + /** |
|---|
| 498 | + * generateErrorUnathorizedAccess<p> |
|---|
| 499 | + * Convenience 401 generator with log. |
|---|
| 500 | + * |
|---|
| 501 | + * @param pack |
|---|
| 502 | + * @param user |
|---|
| 503 | + */ |
|---|
| 504 | + private Response generateErrorUnathorizedAccess(Pack pack, Principal user) { |
|---|
| 505 | + LOG.error("Pack with id {} not accesible by user {}", pack, user); |
|---|
| 506 | + return Response.status(Status.UNAUTHORIZED).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Unathorized access to pack").build(); |
|---|
| 507 | + } |
|---|
| 508 | + |
|---|
| 509 | + /** |
|---|
| 510 | + * setPackLicenseType<p> |
|---|
| 511 | + * Set the pack type |
|---|
| 512 | + * |
|---|
| 513 | + * @param pack |
|---|
| 514 | + * @param licTypeId |
|---|
| 515 | + * @param em |
|---|
| 516 | + * @throws SeCurisException |
|---|
| 517 | + */ |
|---|
| 518 | + private void setPackLicenseType(Pack pack, Integer licTypeId, EntityManager em) throws SeCurisException { |
|---|
| 519 | + LicenseType lt = null; |
|---|
| 520 | + if (licTypeId != null) { |
|---|
| 521 | + lt = em.find(LicenseType.class, pack.getLicTypeId()); |
|---|
| 522 | + if (lt == null) { |
|---|
| 523 | + LOG.error("Pack license type with id {} not found in DB", licTypeId); |
|---|
| 524 | + throw new SeCurisException("Pack license type not found with ID: " + licTypeId); |
|---|
| 525 | + } |
|---|
| 526 | + } |
|---|
| 527 | + pack.setLicenseType(lt); |
|---|
| 528 | + } |
|---|
| 529 | + |
|---|
| 530 | + /** |
|---|
| 531 | + * getMdKeys<p> |
|---|
| 532 | + * Get the MD keys |
|---|
| 533 | + * |
|---|
| 534 | + * @param mds |
|---|
| 535 | + * @return mdKeys |
|---|
| 536 | + */ |
|---|
| 537 | + private Set<String> getMdKeys(Set<PackMetadata> mds) { |
|---|
| 538 | + Set<String> ids = new HashSet<String>(); |
|---|
| 539 | + if (mds != null) { |
|---|
| 540 | + for (PackMetadata md : mds) { |
|---|
| 541 | + ids.add(md.getKey()); |
|---|
| 542 | + } |
|---|
| 543 | + } |
|---|
| 544 | + return ids; |
|---|
| 545 | + } |
|---|
| 546 | + |
|---|
| 547 | + /** |
|---|
| 548 | + * checkIfCodeExists<p> |
|---|
| 549 | + * Check if the code already exist |
|---|
| 550 | + * |
|---|
| 551 | + * @param code |
|---|
| 552 | + * @param em |
|---|
| 553 | + * @return codeExist |
|---|
| 554 | + */ |
|---|
| 555 | + private boolean checkIfCodeExists(String code, EntityManager em) { |
|---|
| 556 | + TypedQuery<Pack> query = em.createNamedQuery("pack-by-code", Pack.class); |
|---|
| 557 | + query.setParameter("code", code); |
|---|
| 558 | + int packs = query.getResultList().size(); |
|---|
| 559 | + return packs > 0; |
|---|
| 560 | + } |
|---|
| 561 | + |
|---|
| 562 | + /** |
|---|
| 563 | + * setPackOrganization<p> |
|---|
| 564 | + * Set the organization of the pack |
|---|
| 565 | + * |
|---|
| 566 | + * @param currentPack |
|---|
| 567 | + * @param orgId |
|---|
| 568 | + * @param em |
|---|
| 569 | + * @throws SeCurisException |
|---|
| 570 | + */ |
|---|
| 571 | + private void setPackOrganization(Pack currentPack, Integer orgId, EntityManager em) throws SeCurisException { |
|---|
| 572 | + Organization org = null; |
|---|
| 573 | + if (orgId != null) { |
|---|
| 574 | + org = em.find(Organization.class, orgId); |
|---|
| 575 | + if (org == null) { |
|---|
| 576 | + LOG.error("Organization pack with id {} not found in DB", orgId); |
|---|
| 577 | + throw new SeCurisException("Pack organization not found with ID: " + orgId); |
|---|
| 578 | + } |
|---|
| 579 | + } |
|---|
| 580 | + currentPack.setOrganization(org); |
|---|
| 581 | + } |
|---|
| 469 | 582 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Date; |
|---|
| .. | .. |
|---|
| 47 | 50 | import net.curisit.securis.utils.TokenHelper; |
|---|
| 48 | 51 | |
|---|
| 49 | 52 | /** |
|---|
| 50 | | - * User resource |
|---|
| 51 | | - * |
|---|
| 53 | + * UserResource |
|---|
| 54 | + * <p> |
|---|
| 55 | + * REST resource that manages users (CRUD + authentication helpers). |
|---|
| 56 | + * All endpoints are guarded and ADMIN-only unless otherwise stated. |
|---|
| 57 | + * <p> |
|---|
| 58 | + * Notes: |
|---|
| 59 | + * - Uses {@link BasicSecurityContext} authorization via @Securable and @RolesAllowed. |
|---|
| 60 | + * - Uses JPA {@link EntityManager} injected through @Context. |
|---|
| 61 | + * - Mutating endpoints are wrapped in @EnsureTransaction to guarantee commit/rollback. |
|---|
| 62 | + * - Passwords are stored as SHA-256 hashes (see {@link Utils#sha256(String)}). |
|---|
| 63 | + * |
|---|
| 64 | + * Endpoints: |
|---|
| 65 | + * GET /user/ -> list users |
|---|
| 66 | + * GET /user/{uid} -> get user by username |
|---|
| 67 | + * POST /user/ -> create user (idempotent: upsert semantics) |
|---|
| 68 | + * PUT /user/{uid} -> update user (creates if not exists) |
|---|
| 69 | + * POST /user/login -> password authentication; returns token and basic identity |
|---|
| 70 | + * POST /user/check -> validates a token and returns token metadata |
|---|
| 71 | + * GET /user/logout -> invalidates HTTP session (non-token based) |
|---|
| 72 | + * |
|---|
| 73 | + * Thread-safety: RequestScoped. No shared mutable state. |
|---|
| 74 | + * |
|---|
| 52 | 75 | * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 76 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 53 | 77 | */ |
|---|
| 54 | 78 | @Path("/user") |
|---|
| 55 | 79 | @RequestScoped |
|---|
| 56 | 80 | public class UserResource { |
|---|
| 57 | 81 | |
|---|
| 58 | | - @Inject |
|---|
| 59 | | - TokenHelper tokenHelper; |
|---|
| 82 | + /** Token encoder/decoder & validator. */ |
|---|
| 83 | + @Inject TokenHelper tokenHelper; |
|---|
| 60 | 84 | |
|---|
| 61 | | - @Inject |
|---|
| 62 | | - private CacheTTL cache; |
|---|
| 85 | + /** Small cache to invalidate role/org derived data after user mutations. */ |
|---|
| 86 | + @Inject private CacheTTL cache; |
|---|
| 63 | 87 | |
|---|
| 64 | | - @Context |
|---|
| 65 | | - EntityManager em; |
|---|
| 88 | + /** JPA entity manager bound to the current request context. */ |
|---|
| 89 | + @Context EntityManager em; |
|---|
| 66 | 90 | |
|---|
| 67 | | - private static final Logger LOG = LogManager.getLogger(UserResource.class); |
|---|
| 91 | + private static final Logger LOG = LogManager.getLogger(UserResource.class); |
|---|
| 68 | 92 | |
|---|
| 69 | | - public UserResource() { |
|---|
| 70 | | - } |
|---|
| 93 | + /** |
|---|
| 94 | + * UserResource |
|---|
| 95 | + * Default constructor for CDI. |
|---|
| 96 | + */ |
|---|
| 97 | + public UserResource() { |
|---|
| 98 | + } |
|---|
| 71 | 99 | |
|---|
| 72 | | - /** |
|---|
| 73 | | - * |
|---|
| 74 | | - * @return the server version in format majorVersion.minorVersion |
|---|
| 75 | | - */ |
|---|
| 76 | | - @GET |
|---|
| 77 | | - @Path("/") |
|---|
| 78 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 79 | | - @Securable(roles = Rol.ADMIN) |
|---|
| 80 | | - @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 81 | | - public Response index() { |
|---|
| 82 | | - LOG.info("Getting users list "); |
|---|
| 100 | + // --------------------------------------------------------------------- |
|---|
| 101 | + // Read operations |
|---|
| 102 | + // --------------------------------------------------------------------- |
|---|
| 83 | 103 | |
|---|
| 84 | | - // EntityManager em = emProvider.get(); |
|---|
| 85 | | - em.clear(); |
|---|
| 86 | | - TypedQuery<User> q = em.createNamedQuery("list-users", User.class); |
|---|
| 104 | + /** |
|---|
| 105 | + * index |
|---|
| 106 | + * <p> |
|---|
| 107 | + * List all users. |
|---|
| 108 | + * |
|---|
| 109 | + * Security: ADMIN only. |
|---|
| 110 | + * |
|---|
| 111 | + * @return 200 OK with JSON array of {@link User}, or 200 OK with empty list. |
|---|
| 112 | + */ |
|---|
| 113 | + @GET |
|---|
| 114 | + @Path("/") |
|---|
| 115 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 116 | + @Securable(roles = Rol.ADMIN) |
|---|
| 117 | + @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 118 | + public Response index() { |
|---|
| 119 | + LOG.info("Getting users list "); |
|---|
| 87 | 120 | |
|---|
| 88 | | - List<User> list = q.getResultList(); |
|---|
| 121 | + em.clear(); |
|---|
| 122 | + TypedQuery<User> q = em.createNamedQuery("list-users", User.class); |
|---|
| 123 | + List<User> list = q.getResultList(); |
|---|
| 89 | 124 | |
|---|
| 90 | | - return Response.ok(list).build(); |
|---|
| 91 | | - } |
|---|
| 125 | + return Response.ok(list).build(); |
|---|
| 126 | + } |
|---|
| 92 | 127 | |
|---|
| 93 | | - /** |
|---|
| 94 | | - * |
|---|
| 95 | | - * @return The user |
|---|
| 96 | | - */ |
|---|
| 97 | | - @GET |
|---|
| 98 | | - @Path("/{uid}") |
|---|
| 99 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 100 | | - @Securable(roles = Rol.ADMIN) |
|---|
| 101 | | - @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 102 | | - public Response get(@PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 103 | | - LOG.info("Getting user data for id: {}: ", uid); |
|---|
| 104 | | - if (uid == null || "".equals(uid)) { |
|---|
| 105 | | - LOG.error("User ID is mandatory"); |
|---|
| 106 | | - return Response.status(Status.NOT_FOUND).build(); |
|---|
| 107 | | - } |
|---|
| 128 | + /** |
|---|
| 129 | + * get |
|---|
| 130 | + * <p> |
|---|
| 131 | + * Retrieve a single user by username. |
|---|
| 132 | + * |
|---|
| 133 | + * Security: ADMIN only. |
|---|
| 134 | + * |
|---|
| 135 | + * @param uid Username (primary key). |
|---|
| 136 | + * @param token Optional token header (unused here, enforced by filters). |
|---|
| 137 | + * @return 200 OK with user payload or 404 if not found/invalid uid. |
|---|
| 138 | + */ |
|---|
| 139 | + @GET |
|---|
| 140 | + @Path("/{uid}") |
|---|
| 141 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 142 | + @Securable(roles = Rol.ADMIN) |
|---|
| 143 | + @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 144 | + public Response get(@PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 145 | + LOG.info("Getting user data for id: {}: ", uid); |
|---|
| 146 | + if (uid == null || "".equals(uid)) { |
|---|
| 147 | + LOG.error("User ID is mandatory"); |
|---|
| 148 | + return Response.status(Status.NOT_FOUND).build(); |
|---|
| 149 | + } |
|---|
| 108 | 150 | |
|---|
| 109 | | - // EntityManager em = emProvider.get(); |
|---|
| 110 | | - em.clear(); |
|---|
| 111 | | - User lt = em.find(User.class, uid); |
|---|
| 112 | | - if (lt == null) { |
|---|
| 113 | | - LOG.error("User with id {} not found in DB", uid); |
|---|
| 114 | | - return Response.status(Status.NOT_FOUND).build(); |
|---|
| 115 | | - } |
|---|
| 116 | | - return Response.ok(lt).build(); |
|---|
| 117 | | - } |
|---|
| 151 | + em.clear(); |
|---|
| 152 | + User lt = em.find(User.class, uid); |
|---|
| 153 | + if (lt == null) { |
|---|
| 154 | + LOG.error("User with id {} not found in DB", uid); |
|---|
| 155 | + return Response.status(Status.NOT_FOUND).build(); |
|---|
| 156 | + } |
|---|
| 157 | + return Response.ok(lt).build(); |
|---|
| 158 | + } |
|---|
| 118 | 159 | |
|---|
| 119 | | - @POST |
|---|
| 120 | | - @Path("/") |
|---|
| 121 | | - @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 122 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 123 | | - @EnsureTransaction |
|---|
| 124 | | - @Securable(roles = Rol.ADMIN) |
|---|
| 125 | | - @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 126 | | - public Response create(User user, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 127 | | - LOG.info("Creating new user"); |
|---|
| 128 | | - // EntityManager em = emProvider.get(); |
|---|
| 129 | | - User currentUser = em.find(User.class, user.getUsername()); |
|---|
| 130 | | - if (currentUser != null) { |
|---|
| 131 | | - LOG.info("User with id {} was found in DB, we'll try to modify it", user.getUsername()); |
|---|
| 132 | | - return modify(user, user.getUsername(), token); |
|---|
| 133 | | - } |
|---|
| 160 | + // --------------------------------------------------------------------- |
|---|
| 161 | + // Create / Update / Delete |
|---|
| 162 | + // --------------------------------------------------------------------- |
|---|
| 134 | 163 | |
|---|
| 135 | | - try { |
|---|
| 136 | | - this.setUserOrgs(user, user.getOrgsIds(), em); |
|---|
| 137 | | - } catch (SeCurisException e) { |
|---|
| 138 | | - return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build(); |
|---|
| 139 | | - } |
|---|
| 140 | | - try { |
|---|
| 141 | | - this.setUserApps(user, user.getAppsIds(), em); |
|---|
| 142 | | - } catch (SeCurisException e) { |
|---|
| 143 | | - return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build(); |
|---|
| 144 | | - } |
|---|
| 145 | | - if (user.getPassword() != null && !"".equals(user.getPassword())) { |
|---|
| 146 | | - user.setPassword(Utils.sha256(user.getPassword())); |
|---|
| 147 | | - } else { |
|---|
| 148 | | - return Response.status(DefaultExceptionHandler.DEFAULT_APP_ERROR_STATUS_CODE).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "User password is mandatory") |
|---|
| 149 | | - .build(); |
|---|
| 150 | | - } |
|---|
| 151 | | - user.setModificationTimestamp(new Date()); |
|---|
| 152 | | - user.setLastLogin(null); |
|---|
| 153 | | - user.setCreationTimestamp(new Date()); |
|---|
| 154 | | - em.persist(user); |
|---|
| 164 | + /** |
|---|
| 165 | + * create |
|---|
| 166 | + * <p> |
|---|
| 167 | + * Create a new user. If the username already exists, delegates to {@link #modify(User, String, String)} |
|---|
| 168 | + * to behave like an upsert. |
|---|
| 169 | + * |
|---|
| 170 | + * Security: ADMIN only. |
|---|
| 171 | + * Transaction: yes (via @EnsureTransaction). |
|---|
| 172 | + * |
|---|
| 173 | + * @param user Incoming user payload. Password must be non-empty (plain text). |
|---|
| 174 | + * Password is SHA-256 hashed before persist. |
|---|
| 175 | + * @param token Security token header (unused here; enforced by filters). |
|---|
| 176 | + * @return 200 OK with created/updated user; 4xx on validation errors. |
|---|
| 177 | + */ |
|---|
| 178 | + @POST |
|---|
| 179 | + @Path("/") |
|---|
| 180 | + @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 181 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 182 | + @EnsureTransaction |
|---|
| 183 | + @Securable(roles = Rol.ADMIN) |
|---|
| 184 | + @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 185 | + public Response create(User user, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 186 | + LOG.info("Creating new user"); |
|---|
| 155 | 187 | |
|---|
| 156 | | - return Response.ok(user).build(); |
|---|
| 157 | | - } |
|---|
| 188 | + User currentUser = em.find(User.class, user.getUsername()); |
|---|
| 189 | + if (currentUser != null) { |
|---|
| 190 | + LOG.info("User with id {} was found in DB, we'll try to modify it", user.getUsername()); |
|---|
| 191 | + return modify(user, user.getUsername(), token); |
|---|
| 192 | + } |
|---|
| 158 | 193 | |
|---|
| 159 | | - private void setUserOrgs(User user, Set<Integer> orgsIds, EntityManager em) throws SeCurisException { |
|---|
| 160 | | - Set<Organization> orgs = null; |
|---|
| 161 | | - if (orgsIds != null && !orgsIds.isEmpty()) { |
|---|
| 162 | | - orgs = new HashSet<>(); |
|---|
| 163 | | - for (Integer orgId : orgsIds) { |
|---|
| 164 | | - Organization o = em.find(Organization.class, orgId); |
|---|
| 165 | | - if (o == null) { |
|---|
| 166 | | - LOG.error("User organization with id {} not found in DB", orgId); |
|---|
| 167 | | - throw new SeCurisException("User's organization not found with ID: " + orgId); |
|---|
| 168 | | - } |
|---|
| 169 | | - orgs.add(o); |
|---|
| 170 | | - } |
|---|
| 171 | | - } |
|---|
| 194 | + try { |
|---|
| 195 | + this.setUserOrgs(user, user.getOrgsIds(), em); |
|---|
| 196 | + } catch (SeCurisException e) { |
|---|
| 197 | + return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build(); |
|---|
| 198 | + } |
|---|
| 199 | + try { |
|---|
| 200 | + this.setUserApps(user, user.getAppsIds(), em); |
|---|
| 201 | + } catch (SeCurisException e) { |
|---|
| 202 | + return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build(); |
|---|
| 203 | + } |
|---|
| 172 | 204 | |
|---|
| 173 | | - user.setOrganizations(orgs); |
|---|
| 205 | + // Password must be provided on create |
|---|
| 206 | + if (user.getPassword() != null && !"".equals(user.getPassword())) { |
|---|
| 207 | + user.setPassword(Utils.sha256(user.getPassword())); |
|---|
| 208 | + } else { |
|---|
| 209 | + return Response.status(DefaultExceptionHandler.DEFAULT_APP_ERROR_STATUS_CODE) |
|---|
| 210 | + .header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "User password is mandatory") |
|---|
| 211 | + .build(); |
|---|
| 212 | + } |
|---|
| 174 | 213 | |
|---|
| 175 | | - } |
|---|
| 214 | + user.setModificationTimestamp(new Date()); |
|---|
| 215 | + user.setLastLogin(null); |
|---|
| 216 | + user.setCreationTimestamp(new Date()); |
|---|
| 217 | + em.persist(user); |
|---|
| 176 | 218 | |
|---|
| 177 | | - private void setUserApps(User user, Set<Integer> appsIds, EntityManager em) throws SeCurisException { |
|---|
| 178 | | - Set<Application> apps = null; |
|---|
| 179 | | - if (appsIds != null && !appsIds.isEmpty()) { |
|---|
| 180 | | - apps = new HashSet<>(); |
|---|
| 181 | | - for (Integer appId : appsIds) { |
|---|
| 182 | | - Application o = em.find(Application.class, appId); |
|---|
| 183 | | - if (o == null) { |
|---|
| 184 | | - LOG.error("User application with id {} not found in DB", appId); |
|---|
| 185 | | - throw new SeCurisException("User's application not found with ID: " + appId); |
|---|
| 186 | | - } |
|---|
| 187 | | - apps.add(o); |
|---|
| 188 | | - } |
|---|
| 189 | | - } |
|---|
| 219 | + return Response.ok(user).build(); |
|---|
| 220 | + } |
|---|
| 190 | 221 | |
|---|
| 191 | | - user.setApplications(apps); |
|---|
| 192 | | - } |
|---|
| 222 | + /** |
|---|
| 223 | + * setUserOrgs |
|---|
| 224 | + * <p> |
|---|
| 225 | + * Resolve and set the organizations for a user from a set of IDs. |
|---|
| 226 | + * Validates each id exists in DB. |
|---|
| 227 | + * |
|---|
| 228 | + * @param user Target user entity. |
|---|
| 229 | + * @param orgsIds Organization ids to assign (nullable/empty allowed). |
|---|
| 230 | + * @param em EntityManager. |
|---|
| 231 | + * @throws SeCurisException if any of the referenced organizations does not exist. |
|---|
| 232 | + */ |
|---|
| 233 | + private void setUserOrgs(User user, Set<Integer> orgsIds, EntityManager em) throws SeCurisException { |
|---|
| 234 | + Set<Organization> orgs = null; |
|---|
| 235 | + if (orgsIds != null && !orgsIds.isEmpty()) { |
|---|
| 236 | + orgs = new HashSet<>(); |
|---|
| 237 | + for (Integer orgId : orgsIds) { |
|---|
| 238 | + Organization o = em.find(Organization.class, orgId); |
|---|
| 239 | + if (o == null) { |
|---|
| 240 | + LOG.error("User organization with id {} not found in DB", orgId); |
|---|
| 241 | + throw new SeCurisException("User's organization not found with ID: " + orgId); |
|---|
| 242 | + } |
|---|
| 243 | + orgs.add(o); |
|---|
| 244 | + } |
|---|
| 245 | + } |
|---|
| 246 | + user.setOrganizations(orgs); |
|---|
| 247 | + } |
|---|
| 193 | 248 | |
|---|
| 194 | | - @PUT |
|---|
| 195 | | - @POST |
|---|
| 196 | | - @Path("/{uid}") |
|---|
| 197 | | - @EnsureTransaction |
|---|
| 198 | | - @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 199 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 200 | | - @Securable(roles = Rol.ADMIN) |
|---|
| 201 | | - @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 202 | | - public Response modify(User user, @PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 203 | | - LOG.info("Modifying user with id: {}", uid); |
|---|
| 204 | | - // EntityManager em = emProvider.get(); |
|---|
| 205 | | - User currentUser = em.find(User.class, uid); |
|---|
| 206 | | - if (currentUser == null) { |
|---|
| 207 | | - LOG.info("User with id {} not found in DB, we'll try to create it", uid); |
|---|
| 208 | | - return create(user, token); |
|---|
| 209 | | - } |
|---|
| 249 | + /** |
|---|
| 250 | + * setUserApps |
|---|
| 251 | + * <p> |
|---|
| 252 | + * Resolve and set the applications for a user from a set of IDs. |
|---|
| 253 | + * Validates each id exists in DB. |
|---|
| 254 | + * |
|---|
| 255 | + * @param user Target user entity. |
|---|
| 256 | + * @param appsIds Application ids to assign (nullable/empty allowed). |
|---|
| 257 | + * @param em EntityManager. |
|---|
| 258 | + * @throws SeCurisException if any of the referenced applications does not exist. |
|---|
| 259 | + */ |
|---|
| 260 | + private void setUserApps(User user, Set<Integer> appsIds, EntityManager em) throws SeCurisException { |
|---|
| 261 | + Set<Application> apps = null; |
|---|
| 262 | + if (appsIds != null && !appsIds.isEmpty()) { |
|---|
| 263 | + apps = new HashSet<>(); |
|---|
| 264 | + for (Integer appId : appsIds) { |
|---|
| 265 | + Application o = em.find(Application.class, appId); |
|---|
| 266 | + if (o == null) { |
|---|
| 267 | + LOG.error("User application with id {} not found in DB", appId); |
|---|
| 268 | + throw new SeCurisException("User's application not found with ID: " + appId); |
|---|
| 269 | + } |
|---|
| 270 | + apps.add(o); |
|---|
| 271 | + } |
|---|
| 272 | + } |
|---|
| 273 | + user.setApplications(apps); |
|---|
| 274 | + } |
|---|
| 210 | 275 | |
|---|
| 211 | | - try { |
|---|
| 212 | | - this.setUserOrgs(currentUser, user.getOrgsIds(), em); |
|---|
| 213 | | - } catch (SeCurisException e) { |
|---|
| 214 | | - return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build(); |
|---|
| 215 | | - } |
|---|
| 216 | | - try { |
|---|
| 217 | | - this.setUserApps(currentUser, user.getAppsIds(), em); |
|---|
| 218 | | - } catch (SeCurisException e) { |
|---|
| 219 | | - return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build(); |
|---|
| 220 | | - } |
|---|
| 221 | | - currentUser.setFirstName(user.getFirstName()); |
|---|
| 222 | | - currentUser.setLastName(user.getLastName()); |
|---|
| 223 | | - currentUser.setRoles(user.getRoles()); |
|---|
| 224 | | - currentUser.setLang(user.getLang()); |
|---|
| 225 | | - currentUser.setModificationTimestamp(new Date()); |
|---|
| 226 | | - if (user.getPassword() != null && !"".equals(user.getPassword())) { |
|---|
| 227 | | - currentUser.setPassword(Utils.sha256(user.getPassword())); |
|---|
| 228 | | - } else { |
|---|
| 229 | | - // Password has not been modified |
|---|
| 230 | | - // return |
|---|
| 231 | | - } |
|---|
| 276 | + /** |
|---|
| 277 | + * modify |
|---|
| 278 | + * <p> |
|---|
| 279 | + * Update an existing user. If the user does not exist, delegates to {@link #create(User, String)}. |
|---|
| 280 | + * Password is updated only if a non-empty password is provided. |
|---|
| 281 | + * Organizations & applications are fully replaced with the given ids. |
|---|
| 282 | + * |
|---|
| 283 | + * Security: ADMIN only. |
|---|
| 284 | + * Transaction: yes (via @EnsureTransaction). |
|---|
| 285 | + * |
|---|
| 286 | + * @param user Incoming user payload. |
|---|
| 287 | + * @param uid Username (path param) to update. |
|---|
| 288 | + * @param token Security token header (unused here). |
|---|
| 289 | + * @return 200 OK with updated user; 404 if reference entities are missing. |
|---|
| 290 | + */ |
|---|
| 291 | + @PUT |
|---|
| 292 | + @POST |
|---|
| 293 | + @Path("/{uid}") |
|---|
| 294 | + @EnsureTransaction |
|---|
| 295 | + @Consumes(MediaType.APPLICATION_JSON) |
|---|
| 296 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 297 | + @Securable(roles = Rol.ADMIN) |
|---|
| 298 | + @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 299 | + public Response modify(User user, @PathParam("uid") String uid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { |
|---|
| 300 | + LOG.info("Modifying user with id: {}", uid); |
|---|
| 232 | 301 | |
|---|
| 233 | | - currentUser.setLastLogin(user.getLastLogin()); |
|---|
| 302 | + User currentUser = em.find(User.class, uid); |
|---|
| 303 | + if (currentUser == null) { |
|---|
| 304 | + LOG.info("User with id {} not found in DB, we'll try to create it", uid); |
|---|
| 305 | + return create(user, token); |
|---|
| 306 | + } |
|---|
| 234 | 307 | |
|---|
| 235 | | - em.persist(currentUser); |
|---|
| 236 | | - clearUserCache(currentUser.getUsername()); |
|---|
| 308 | + try { |
|---|
| 309 | + this.setUserOrgs(currentUser, user.getOrgsIds(), em); |
|---|
| 310 | + } catch (SeCurisException e) { |
|---|
| 311 | + return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build(); |
|---|
| 312 | + } |
|---|
| 313 | + try { |
|---|
| 314 | + this.setUserApps(currentUser, user.getAppsIds(), em); |
|---|
| 315 | + } catch (SeCurisException e) { |
|---|
| 316 | + return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build(); |
|---|
| 317 | + } |
|---|
| 237 | 318 | |
|---|
| 238 | | - return Response.ok(currentUser).build(); |
|---|
| 239 | | - } |
|---|
| 319 | + currentUser.setFirstName(user.getFirstName()); |
|---|
| 320 | + currentUser.setLastName(user.getLastName()); |
|---|
| 321 | + currentUser.setRoles(user.getRoles()); |
|---|
| 322 | + currentUser.setLang(user.getLang()); |
|---|
| 323 | + currentUser.setModificationTimestamp(new Date()); |
|---|
| 240 | 324 | |
|---|
| 241 | | - @DELETE |
|---|
| 242 | | - @Path("/{uid}") |
|---|
| 243 | | - @EnsureTransaction |
|---|
| 244 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 245 | | - @Securable(roles = Rol.ADMIN) |
|---|
| 246 | | - @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 247 | | - public Response delete(@PathParam("uid") String uid, @Context HttpServletRequest request) { |
|---|
| 248 | | - LOG.info("Deleting app with id: {}", uid); |
|---|
| 249 | | - // EntityManager em = emProvider.get(); |
|---|
| 250 | | - User user = em.find(User.class, uid); |
|---|
| 251 | | - if (user == null) { |
|---|
| 252 | | - LOG.error("User with id {} can not be deleted, It was not found in DB", uid); |
|---|
| 253 | | - return Response.status(Status.NOT_FOUND).build(); |
|---|
| 254 | | - } |
|---|
| 325 | + // Optional password update |
|---|
| 326 | + if (user.getPassword() != null && !"".equals(user.getPassword())) { |
|---|
| 327 | + currentUser.setPassword(Utils.sha256(user.getPassword())); |
|---|
| 328 | + } |
|---|
| 255 | 329 | |
|---|
| 256 | | - em.remove(user); |
|---|
| 257 | | - clearUserCache(user.getUsername()); |
|---|
| 258 | | - return Response.ok(Utils.createMap("success", true, "id", uid)).build(); |
|---|
| 259 | | - } |
|---|
| 330 | + // lastLogin can be set through API (rare), otherwise managed at login |
|---|
| 331 | + currentUser.setLastLogin(user.getLastLogin()); |
|---|
| 260 | 332 | |
|---|
| 261 | | - private void clearUserCache(String username) { |
|---|
| 262 | | - cache.remove("roles_" + username); |
|---|
| 263 | | - cache.remove("orgs_" + username); |
|---|
| 264 | | - } |
|---|
| 333 | + em.persist(currentUser); |
|---|
| 334 | + clearUserCache(currentUser.getUsername()); |
|---|
| 265 | 335 | |
|---|
| 266 | | - @POST |
|---|
| 267 | | - @Path("/login") |
|---|
| 268 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 269 | | - public Response login(@FormParam("username") String username, @FormParam("password") String password, @Context HttpServletRequest request) throws SeCurisServiceException { |
|---|
| 270 | | - LOG.info("index session: " + request.getSession()); |
|---|
| 336 | + return Response.ok(currentUser).build(); |
|---|
| 337 | + } |
|---|
| 271 | 338 | |
|---|
| 272 | | - // EntityManager em = emProvider.get(); |
|---|
| 273 | | - User user = em.find(User.class, username); |
|---|
| 274 | | - if (user == null) { |
|---|
| 275 | | - LOG.error("Unknown username {} used in login service", username); |
|---|
| 276 | | - throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials"); |
|---|
| 277 | | - } |
|---|
| 278 | | - String securedPassword = Utils.sha256(password); |
|---|
| 339 | + /** |
|---|
| 340 | + * delete |
|---|
| 341 | + * <p> |
|---|
| 342 | + * Delete a user by username. |
|---|
| 343 | + * |
|---|
| 344 | + * Security: ADMIN only. |
|---|
| 345 | + * Transaction: yes (via @EnsureTransaction). |
|---|
| 346 | + * |
|---|
| 347 | + * @param uid Username to delete. |
|---|
| 348 | + * @param request Http servlet request (unused). |
|---|
| 349 | + * @return 200 OK on success; 404 if user does not exist. |
|---|
| 350 | + */ |
|---|
| 351 | + @DELETE |
|---|
| 352 | + @Path("/{uid}") |
|---|
| 353 | + @EnsureTransaction |
|---|
| 354 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 355 | + @Securable(roles = Rol.ADMIN) |
|---|
| 356 | + @RolesAllowed(BasicSecurityContext.ROL_ADMIN) |
|---|
| 357 | + public Response delete(@PathParam("uid") String uid, @Context HttpServletRequest request) { |
|---|
| 358 | + LOG.info("Deleting app with id: {}", uid); |
|---|
| 279 | 359 | |
|---|
| 280 | | - if (securedPassword == null || !securedPassword.equals(user.getPassword())) { |
|---|
| 281 | | - throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials"); |
|---|
| 282 | | - } |
|---|
| 283 | | - user.setLastLogin(new Date()); |
|---|
| 284 | | - em.getTransaction().begin(); |
|---|
| 285 | | - try { |
|---|
| 286 | | - em.persist(user); |
|---|
| 287 | | - em.getTransaction().commit(); |
|---|
| 288 | | - } catch (PersistenceException ex) { |
|---|
| 289 | | - LOG.error("Error updating last login date for user: {}", username); |
|---|
| 290 | | - LOG.error(ex); |
|---|
| 291 | | - em.getTransaction().rollback(); |
|---|
| 292 | | - } |
|---|
| 293 | | - clearUserCache(username); |
|---|
| 294 | | - String userFullName = String.format("%s %s", user.getFirstName(), user.getLastName() == null ? "" : user.getLastName()).trim(); |
|---|
| 295 | | - String tokenAuth = tokenHelper.generateToken(username); |
|---|
| 296 | | - return Response.ok(Utils.createMap("success", true, "token", tokenAuth, "username", username, "full_name", userFullName)).build(); |
|---|
| 297 | | - } |
|---|
| 360 | + User user = em.find(User.class, uid); |
|---|
| 361 | + if (user == null) { |
|---|
| 362 | + LOG.error("User with id {} can not be deleted, It was not found in DB", uid); |
|---|
| 363 | + return Response.status(Status.NOT_FOUND).build(); |
|---|
| 364 | + } |
|---|
| 298 | 365 | |
|---|
| 299 | | - /** |
|---|
| 300 | | - * Check if current token is valid |
|---|
| 301 | | - * |
|---|
| 302 | | - * @param user |
|---|
| 303 | | - * @param password |
|---|
| 304 | | - * @param request |
|---|
| 305 | | - * @return |
|---|
| 306 | | - */ |
|---|
| 307 | | - @POST |
|---|
| 308 | | - @Path("/check") |
|---|
| 309 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 310 | | - public Response check(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token, @QueryParam("token") String token2) { |
|---|
| 311 | | - if (token == null) { |
|---|
| 312 | | - token = token2; |
|---|
| 313 | | - } |
|---|
| 314 | | - if (token == null) { |
|---|
| 315 | | - return Response.status(Status.FORBIDDEN).build(); |
|---|
| 316 | | - } |
|---|
| 366 | + em.remove(user); |
|---|
| 367 | + clearUserCache(user.getUsername()); |
|---|
| 368 | + return Response.ok(Utils.createMap("success", true, "id", uid)).build(); |
|---|
| 369 | + } |
|---|
| 317 | 370 | |
|---|
| 318 | | - LOG.info("Token : " + token); |
|---|
| 319 | | - String user = tokenHelper.extractUserFromToken(token); |
|---|
| 320 | | - LOG.info("Token user: " + user); |
|---|
| 321 | | - Date date = tokenHelper.extractDateCreationFromToken(token); |
|---|
| 322 | | - LOG.info("Token date: " + date); |
|---|
| 323 | | - boolean valid = tokenHelper.isTokenValid(token); |
|---|
| 371 | + /** |
|---|
| 372 | + * clearUserCache |
|---|
| 373 | + * <p> |
|---|
| 374 | + * Helper to invalidate cached role/org projections after changes. |
|---|
| 375 | + * |
|---|
| 376 | + * @param username The user whose cache entries must be cleared. |
|---|
| 377 | + */ |
|---|
| 378 | + private void clearUserCache(String username) { |
|---|
| 379 | + cache.remove("roles_" + username); |
|---|
| 380 | + cache.remove("orgs_" + username); |
|---|
| 381 | + } |
|---|
| 324 | 382 | |
|---|
| 325 | | - LOG.info("Is Token valid: " + valid); |
|---|
| 383 | + // --------------------------------------------------------------------- |
|---|
| 384 | + // Auth helpers |
|---|
| 385 | + // --------------------------------------------------------------------- |
|---|
| 326 | 386 | |
|---|
| 327 | | - return Response.ok(Utils.createMap("valid", true, "user", user, "date", date, "token", token)).build(); |
|---|
| 328 | | - } |
|---|
| 387 | + /** |
|---|
| 388 | + * login |
|---|
| 389 | + * <p> |
|---|
| 390 | + * Validates username & password against stored SHA-256 hash. On success, |
|---|
| 391 | + * updates lastLogin timestamp, clears cache and returns an auth token. |
|---|
| 392 | + * |
|---|
| 393 | + * Token format: Base64("<secret> <user> <ISO8601-date>") |
|---|
| 394 | + * where secret = SHA-256(seed + user + date). |
|---|
| 395 | + * |
|---|
| 396 | + * @param username Plain username. |
|---|
| 397 | + * @param password Plain password (SHA-256 will be computed server-side). |
|---|
| 398 | + * @param request Http request, used to log underlying session (not required for token flow). |
|---|
| 399 | + * @return 200 OK with {token, username, full_name}; 401 on invalid credentials. |
|---|
| 400 | + * @throws SeCurisServiceException if user is missing or password mismatch. |
|---|
| 401 | + */ |
|---|
| 402 | + @POST |
|---|
| 403 | + @Path("/login") |
|---|
| 404 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 405 | + public Response login(@FormParam("username") String username, @FormParam("password") String password, @Context HttpServletRequest request) throws SeCurisServiceException { |
|---|
| 406 | + LOG.info("index session: " + request.getSession()); |
|---|
| 329 | 407 | |
|---|
| 330 | | - @GET |
|---|
| 331 | | - @Path("/logout") |
|---|
| 332 | | - @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 333 | | - public Response logout(@Context HttpServletRequest request) { |
|---|
| 334 | | - request.getSession().invalidate(); |
|---|
| 335 | | - return Response.ok().build(); |
|---|
| 336 | | - } |
|---|
| 408 | + User user = em.find(User.class, username); |
|---|
| 409 | + if (user == null) { |
|---|
| 410 | + LOG.error("Unknown username {} used in login service", username); |
|---|
| 411 | + throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials"); |
|---|
| 412 | + } |
|---|
| 413 | + String securedPassword = Utils.sha256(password); |
|---|
| 414 | + |
|---|
| 415 | + if (securedPassword == null || !securedPassword.equals(user.getPassword())) { |
|---|
| 416 | + throw new SeCurisServiceException(ErrorCodes.UNAUTHORIZED_ACCESS, "Wrong credentials"); |
|---|
| 417 | + } |
|---|
| 418 | + |
|---|
| 419 | + user.setLastLogin(new Date()); |
|---|
| 420 | + em.getTransaction().begin(); |
|---|
| 421 | + try { |
|---|
| 422 | + em.persist(user); |
|---|
| 423 | + em.getTransaction().commit(); |
|---|
| 424 | + } catch (PersistenceException ex) { |
|---|
| 425 | + LOG.error("Error updating last login date for user: {}", username); |
|---|
| 426 | + LOG.error(ex); |
|---|
| 427 | + em.getTransaction().rollback(); |
|---|
| 428 | + } |
|---|
| 429 | + |
|---|
| 430 | + clearUserCache(username); |
|---|
| 431 | + String userFullName = String.format("%s %s", user.getFirstName(), user.getLastName() == null ? "" : user.getLastName()).trim(); |
|---|
| 432 | + String tokenAuth = tokenHelper.generateToken(username); |
|---|
| 433 | + return Response.ok(Utils.createMap("success", true, "token", tokenAuth, "username", username, "full_name", userFullName)).build(); |
|---|
| 434 | + } |
|---|
| 435 | + |
|---|
| 436 | + /** |
|---|
| 437 | + * check |
|---|
| 438 | + * <p> |
|---|
| 439 | + * Validates a token and echoes token claims (user, creation date, token string). |
|---|
| 440 | + * Accepts header or query param for convenience. |
|---|
| 441 | + * |
|---|
| 442 | + * @param token Token in header {@link TokenHelper#TOKEN_HEADER_PÀRAM}, may be null. |
|---|
| 443 | + * @param token2 Token in query param 'token', used if header is null. |
|---|
| 444 | + * @return 200 OK with {valid, user, date, token} or 403 if token missing. |
|---|
| 445 | + */ |
|---|
| 446 | + @POST |
|---|
| 447 | + @Path("/check") |
|---|
| 448 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 449 | + public Response check(@HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token, @QueryParam("token") String token2) { |
|---|
| 450 | + if (token == null) { |
|---|
| 451 | + token = token2; |
|---|
| 452 | + } |
|---|
| 453 | + if (token == null) { |
|---|
| 454 | + return Response.status(Status.FORBIDDEN).build(); |
|---|
| 455 | + } |
|---|
| 456 | + |
|---|
| 457 | + LOG.info("Token : " + token); |
|---|
| 458 | + String user = tokenHelper.extractUserFromToken(token); |
|---|
| 459 | + LOG.info("Token user: " + user); |
|---|
| 460 | + Date date = tokenHelper.extractDateCreationFromToken(token); |
|---|
| 461 | + LOG.info("Token date: " + date); |
|---|
| 462 | + boolean valid = tokenHelper.isTokenValid(token); |
|---|
| 463 | + |
|---|
| 464 | + LOG.info("Is Token valid: " + valid); |
|---|
| 465 | + |
|---|
| 466 | + return Response.ok(Utils.createMap("valid", true, "user", user, "date", date, "token", token)).build(); |
|---|
| 467 | + } |
|---|
| 468 | + |
|---|
| 469 | + /** |
|---|
| 470 | + * logout |
|---|
| 471 | + * <p> |
|---|
| 472 | + * Invalidates the HTTP session (useful if the UI also tracks session). |
|---|
| 473 | + * Note: token-based auth is stateless; tokens are not revoked here. |
|---|
| 474 | + * |
|---|
| 475 | + * @param request HttpServletRequest to invalidate session. |
|---|
| 476 | + * @return 200 OK always. |
|---|
| 477 | + */ |
|---|
| 478 | + @GET |
|---|
| 479 | + @Path("/logout") |
|---|
| 480 | + @Produces({ MediaType.APPLICATION_JSON }) |
|---|
| 481 | + public Response logout(@Context HttpServletRequest request) { |
|---|
| 482 | + request.getSession().invalidate(); |
|---|
| 483 | + return Response.ok().build(); |
|---|
| 484 | + } |
|---|
| 337 | 485 | } |
|---|
| 486 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services.exception; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import net.curisit.integrity.exception.CurisException; |
|---|
| 4 | 7 | |
|---|
| 8 | +/** |
|---|
| 9 | + * SeCurisServiceException |
|---|
| 10 | + * <p> |
|---|
| 11 | + * Checked exception for service-layer errors with an attached numeric error code. |
|---|
| 12 | + * Extends {@link CurisException} and is intended to be translated to HTTP responses |
|---|
| 13 | + * by upstream exception mappers/handlers. |
|---|
| 14 | + * |
|---|
| 15 | + * Usage: |
|---|
| 16 | + * - Prefer specific {@code ErrorCodes.*} when throwing. |
|---|
| 17 | + * - Use the single-arg constructor to default to UNEXPECTED_ERROR. |
|---|
| 18 | + * |
|---|
| 19 | + * @author JRA |
|---|
| 20 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 21 | + */ |
|---|
| 5 | 22 | public class SeCurisServiceException extends CurisException { |
|---|
| 6 | 23 | |
|---|
| 7 | | - private int errorCode = 0; |
|---|
| 24 | + /** Numeric error code associated with this exception. */ |
|---|
| 25 | + private int errorCode = 0; |
|---|
| 8 | 26 | |
|---|
| 9 | | - public SeCurisServiceException(int errorCode, String msg) { |
|---|
| 10 | | - super(msg); |
|---|
| 11 | | - this.errorCode = errorCode; |
|---|
| 12 | | - } |
|---|
| 27 | + /** |
|---|
| 28 | + * Constructor with explicit error code. |
|---|
| 29 | + * |
|---|
| 30 | + * @param errorCode See {@link ErrorCodes}. |
|---|
| 31 | + * @param msg Human-readable message (safe to expose). |
|---|
| 32 | + */ |
|---|
| 33 | + public SeCurisServiceException(int errorCode, String msg) { |
|---|
| 34 | + super(msg); |
|---|
| 35 | + this.errorCode = errorCode; |
|---|
| 36 | + } |
|---|
| 13 | 37 | |
|---|
| 14 | | - public SeCurisServiceException(String msg) { |
|---|
| 15 | | - super(msg); |
|---|
| 16 | | - this.errorCode = ErrorCodes.UNEXPECTED_ERROR; |
|---|
| 17 | | - } |
|---|
| 38 | + /** |
|---|
| 39 | + * Constructor defaulting to {@link ErrorCodes#UNEXPECTED_ERROR}. |
|---|
| 40 | + * |
|---|
| 41 | + * @param msg Human-readable message (safe to expose). |
|---|
| 42 | + */ |
|---|
| 43 | + public SeCurisServiceException(String msg) { |
|---|
| 44 | + super(msg); |
|---|
| 45 | + this.errorCode = ErrorCodes.UNEXPECTED_ERROR; |
|---|
| 46 | + } |
|---|
| 18 | 47 | |
|---|
| 19 | | - public int getStatus() { |
|---|
| 20 | | - return errorCode; |
|---|
| 21 | | - } |
|---|
| 48 | + /** |
|---|
| 49 | + * getStatus |
|---|
| 50 | + * <p> |
|---|
| 51 | + * Returns the stored numeric error code. |
|---|
| 52 | + * |
|---|
| 53 | + * @return integer error code. |
|---|
| 54 | + */ |
|---|
| 55 | + public int getStatus() { |
|---|
| 56 | + return errorCode; |
|---|
| 57 | + } |
|---|
| 22 | 58 | |
|---|
| 23 | | - /** |
|---|
| 24 | | - * |
|---|
| 25 | | - */ |
|---|
| 26 | | - private static final long serialVersionUID = 1L; |
|---|
| 59 | + private static final long serialVersionUID = 1L; |
|---|
| 27 | 60 | |
|---|
| 28 | | - public static class ErrorCodes { |
|---|
| 29 | | - public static int UNEXPECTED_ERROR = 1000; |
|---|
| 30 | | - public static int INVALID_CREDENTIALS = 1001; |
|---|
| 31 | | - public static int UNAUTHORIZED_ACCESS = 1002; |
|---|
| 32 | | - public static int NOT_FOUND = 1003; |
|---|
| 33 | | - public static int INVALID_FORMAT = 1004; |
|---|
| 34 | | - public static int WRONG_STATUS = 1005; |
|---|
| 35 | | - public static int UNNECESSARY_RENEW = 1006; |
|---|
| 61 | + /** |
|---|
| 62 | + * ErrorCodes |
|---|
| 63 | + * <p> |
|---|
| 64 | + * Canonical set of service-layer error codes. |
|---|
| 65 | + * Grouped by feature areas (1000 generic, 11xx license, 12xx request data, 13xx validation). |
|---|
| 66 | + */ |
|---|
| 67 | + public static class ErrorCodes { |
|---|
| 68 | + public static int UNEXPECTED_ERROR = 1000; |
|---|
| 69 | + public static int INVALID_CREDENTIALS = 1001; |
|---|
| 70 | + public static int UNAUTHORIZED_ACCESS = 1002; |
|---|
| 71 | + public static int NOT_FOUND = 1003; |
|---|
| 72 | + public static int INVALID_FORMAT = 1004; |
|---|
| 73 | + public static int WRONG_STATUS = 1005; |
|---|
| 74 | + public static int UNNECESSARY_RENEW = 1006; |
|---|
| 36 | 75 | |
|---|
| 37 | | - public static int INVALID_LICENSE_REQUEST_DATA = 1100; |
|---|
| 38 | | - public static int LICENSE_NOT_READY_FOR_RENEW = 1101; |
|---|
| 39 | | - public static int LICENSE_DATA_IS_NOT_VALID = 1102; |
|---|
| 40 | | - public static int LICENSE_IS_EXPIRED = 1103; |
|---|
| 41 | | - public static int LICENSE_PACK_IS_NOT_VALID = 1104; |
|---|
| 76 | + public static int INVALID_LICENSE_REQUEST_DATA = 1100; |
|---|
| 77 | + public static int LICENSE_NOT_READY_FOR_RENEW = 1101; |
|---|
| 78 | + public static int LICENSE_DATA_IS_NOT_VALID = 1102; |
|---|
| 79 | + public static int LICENSE_IS_EXPIRED = 1103; |
|---|
| 80 | + public static int LICENSE_PACK_IS_NOT_VALID = 1104; |
|---|
| 42 | 81 | |
|---|
| 43 | | - public static int INVALID_REQUEST_DATA = 1201; |
|---|
| 44 | | - public static int INVALID_REQUEST_DATA_FORMAT = 1202; |
|---|
| 45 | | - public static int BLOCKED_REQUEST_DATA = 1203; |
|---|
| 46 | | - public static int DUPLICATED_REQUEST_DATA = 1204; |
|---|
| 47 | | - public static int NO_AVAILABLE_LICENSES = 1205; |
|---|
| 82 | + public static int INVALID_REQUEST_DATA = 1201; |
|---|
| 83 | + public static int INVALID_REQUEST_DATA_FORMAT = 1202; |
|---|
| 84 | + public static int BLOCKED_REQUEST_DATA = 1203; |
|---|
| 85 | + public static int DUPLICATED_REQUEST_DATA = 1204; |
|---|
| 86 | + public static int NO_AVAILABLE_LICENSES = 1205; |
|---|
| 48 | 87 | |
|---|
| 49 | | - public static int INVALID_DATA = 1301; |
|---|
| 50 | | - } |
|---|
| 88 | + public static int INVALID_DATA = 1301; |
|---|
| 89 | + } |
|---|
| 51 | 90 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services.helpers; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.File; |
|---|
| .. | .. |
|---|
| 30 | 33 | import net.curisit.securis.services.exception.SeCurisServiceException; |
|---|
| 31 | 34 | import net.curisit.securis.services.exception.SeCurisServiceException.ErrorCodes; |
|---|
| 32 | 35 | |
|---|
| 36 | +/** |
|---|
| 37 | + * LicenseHelper |
|---|
| 38 | + * <p> |
|---|
| 39 | + * Stateless utility component for license lifecycle operations and helpers: |
|---|
| 40 | + * - cancelation with history auditing |
|---|
| 41 | + * - license resolution and validity checks |
|---|
| 42 | + * - license file generation in a temp directory |
|---|
| 43 | + * - metadata extraction and expiration date computation from packs |
|---|
| 44 | + * - sequential code suffix allocation per pack |
|---|
| 45 | + * |
|---|
| 46 | + * Thread-safety: ApplicationScoped, stateless. |
|---|
| 47 | + * |
|---|
| 48 | + * @author JRA |
|---|
| 49 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 50 | + */ |
|---|
| 33 | 51 | @ApplicationScoped |
|---|
| 34 | 52 | public class LicenseHelper { |
|---|
| 35 | 53 | |
|---|
| 36 | | - @SuppressWarnings("unused") |
|---|
| 37 | | - private static final Logger LOG = LogManager.getLogger(LicenseHelper.class); |
|---|
| 38 | | - private static final long MS_PER_DAY = 24L * 3600L * 1000L; |
|---|
| 54 | + @SuppressWarnings("unused") |
|---|
| 55 | + private static final Logger LOG = LogManager.getLogger(LicenseHelper.class); |
|---|
| 39 | 56 | |
|---|
| 40 | | - @Inject |
|---|
| 41 | | - private UserHelper userHelper; |
|---|
| 57 | + /** Milliseconds per day (used to derive expiration dates). */ |
|---|
| 58 | + private static final long MS_PER_DAY = 24L * 3600L * 1000L; |
|---|
| 42 | 59 | |
|---|
| 43 | | - /** |
|---|
| 44 | | - * Cancel the license |
|---|
| 45 | | - * |
|---|
| 46 | | - * @param lic |
|---|
| 47 | | - * @param em |
|---|
| 48 | | - */ |
|---|
| 49 | | - public void cancelLicense(License lic, String reason, BasicSecurityContext bsc, EntityManager em) throws SeCurisServiceException { |
|---|
| 50 | | - lic.setStatus(LicenseStatus.CANCELLED); |
|---|
| 51 | | - lic.setCancelledById(bsc.getUserPrincipal().getName()); |
|---|
| 52 | | - lic.setModificationTimestamp(new Date()); |
|---|
| 53 | | - em.persist(lic); |
|---|
| 60 | + @Inject private UserHelper userHelper; |
|---|
| 54 | 61 | |
|---|
| 55 | | - em.persist(createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.CANCEL, "Cancellation reason: " + reason)); |
|---|
| 56 | | - } |
|---|
| 62 | + /** |
|---|
| 63 | + * cancelLicense |
|---|
| 64 | + * <p> |
|---|
| 65 | + * Transitions a license to CANCELLED, records who canceled it and why, |
|---|
| 66 | + * and appends a {@link LicenseHistory} entry. |
|---|
| 67 | + * |
|---|
| 68 | + * @param lic Target license (managed). |
|---|
| 69 | + * @param reason Human-readable cancellation reason (auditable). |
|---|
| 70 | + * @param bsc Current security context (used to identify the user). |
|---|
| 71 | + * @param em Entity manager to persist changes. |
|---|
| 72 | + * @throws SeCurisServiceException never thrown here, declared for symmetry with callers. |
|---|
| 73 | + */ |
|---|
| 74 | + public void cancelLicense(License lic, String reason, BasicSecurityContext bsc, EntityManager em) throws SeCurisServiceException { |
|---|
| 75 | + lic.setStatus(LicenseStatus.CANCELLED); |
|---|
| 76 | + lic.setCancelledById(bsc.getUserPrincipal().getName()); |
|---|
| 77 | + lic.setModificationTimestamp(new Date()); |
|---|
| 78 | + em.persist(lic); |
|---|
| 57 | 79 | |
|---|
| 58 | | - /** |
|---|
| 59 | | - * Validates that the passed license exists and is still valid |
|---|
| 60 | | - * |
|---|
| 61 | | - * @param licBean |
|---|
| 62 | | - * @param em |
|---|
| 63 | | - * @return The License instance in DB |
|---|
| 64 | | - * @throws SeCurisServiceException |
|---|
| 65 | | - */ |
|---|
| 66 | | - public License getActiveLicenseFromDB(LicenseBean licBean, EntityManager em) throws SeCurisServiceException { |
|---|
| 67 | | - License lic = License.findLicenseByCode(licBean.getLicenseCode(), em); |
|---|
| 68 | | - if (lic == null) { |
|---|
| 69 | | - throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license code doesn't exist"); |
|---|
| 70 | | - } |
|---|
| 71 | | - if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) { |
|---|
| 72 | | - throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license in not active"); |
|---|
| 73 | | - } |
|---|
| 74 | | - return lic; |
|---|
| 75 | | - } |
|---|
| 80 | + em.persist(createLicenseHistoryAction(lic, userHelper.getUser(bsc, em), LicenseHistory.Actions.CANCEL, "Cancellation reason: " + reason)); |
|---|
| 81 | + } |
|---|
| 76 | 82 | |
|---|
| 77 | | - public LicenseHistory createLicenseHistoryAction(License lic, User user, String action, String comments) { |
|---|
| 78 | | - LicenseHistory lh = new LicenseHistory(); |
|---|
| 79 | | - lh.setLicense(lic); |
|---|
| 80 | | - lh.setUser(user); |
|---|
| 81 | | - lh.setCreationTimestamp(new Date()); |
|---|
| 82 | | - lh.setAction(action); |
|---|
| 83 | | - lh.setComments(comments); |
|---|
| 84 | | - return lh; |
|---|
| 85 | | - } |
|---|
| 83 | + /** |
|---|
| 84 | + * getActiveLicenseFromDB |
|---|
| 85 | + * <p> |
|---|
| 86 | + * Resolve license by code and verify that it's ACTIVE or PRE_ACTIVE. |
|---|
| 87 | + * |
|---|
| 88 | + * @param licBean License bean containing the code to check. |
|---|
| 89 | + * @param em EntityManager for DB access. |
|---|
| 90 | + * @return The managed {@link License} instance. |
|---|
| 91 | + * @throws SeCurisServiceException if code not found or license not in an active-ish state. |
|---|
| 92 | + */ |
|---|
| 93 | + public License getActiveLicenseFromDB(LicenseBean licBean, EntityManager em) throws SeCurisServiceException { |
|---|
| 94 | + License lic = License.findLicenseByCode(licBean.getLicenseCode(), em); |
|---|
| 95 | + if (lic == null) { |
|---|
| 96 | + throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license code doesn't exist"); |
|---|
| 97 | + } |
|---|
| 98 | + if (lic.getStatus() != LicenseStatus.ACTIVE && lic.getStatus() != LicenseStatus.PRE_ACTIVE) { |
|---|
| 99 | + throw new SeCurisServiceException(ErrorCodes.LICENSE_DATA_IS_NOT_VALID, "Current license in not active"); |
|---|
| 100 | + } |
|---|
| 101 | + return lic; |
|---|
| 102 | + } |
|---|
| 86 | 103 | |
|---|
| 87 | | - public LicenseHistory createLicenseHistoryAction(License lic, User user, String action) { |
|---|
| 88 | | - return createLicenseHistoryAction(lic, user, action, null); |
|---|
| 89 | | - } |
|---|
| 104 | + /** |
|---|
| 105 | + * createLicenseHistoryAction |
|---|
| 106 | + * <p> |
|---|
| 107 | + * Helper to build a {@link LicenseHistory} entry. |
|---|
| 108 | + * |
|---|
| 109 | + * @param lic License affected. |
|---|
| 110 | + * @param user User performing the action. |
|---|
| 111 | + * @param action Action code (see {@link LicenseHistory.Actions}). |
|---|
| 112 | + * @param comments Optional comments, can be null. |
|---|
| 113 | + * @return transient {@link LicenseHistory} ready to persist. |
|---|
| 114 | + */ |
|---|
| 115 | + public LicenseHistory createLicenseHistoryAction(License lic, User user, String action, String comments) { |
|---|
| 116 | + LicenseHistory lh = new LicenseHistory(); |
|---|
| 117 | + lh.setLicense(lic); |
|---|
| 118 | + lh.setUser(user); |
|---|
| 119 | + lh.setCreationTimestamp(new Date()); |
|---|
| 120 | + lh.setAction(action); |
|---|
| 121 | + lh.setComments(comments); |
|---|
| 122 | + return lh; |
|---|
| 123 | + } |
|---|
| 90 | 124 | |
|---|
| 91 | | - /** |
|---|
| 92 | | - * Create a license file in a temporary directory |
|---|
| 93 | | - * |
|---|
| 94 | | - * @param lic |
|---|
| 95 | | - * @param licFileName |
|---|
| 96 | | - * @return |
|---|
| 97 | | - * @throws IOException |
|---|
| 98 | | - */ |
|---|
| 99 | | - public File createTemporaryLicenseFile(License lic, String licFileName) throws IOException { |
|---|
| 100 | | - File f = Files.createTempDirectory("securis-server").toFile(); |
|---|
| 101 | | - f = new File(f, licFileName); |
|---|
| 102 | | - FileUtils.writeStringToFile(f, lic.getLicenseData(), StandardCharsets.UTF_8); |
|---|
| 103 | | - return f; |
|---|
| 104 | | - } |
|---|
| 125 | + /** |
|---|
| 126 | + * createLicenseHistoryAction |
|---|
| 127 | + * <p> |
|---|
| 128 | + * Overload without comments. |
|---|
| 129 | + * |
|---|
| 130 | + * @param lic License affected. |
|---|
| 131 | + * @param user User performing the action. |
|---|
| 132 | + * @param action Action code. |
|---|
| 133 | + * @return transient {@link LicenseHistory}. |
|---|
| 134 | + */ |
|---|
| 135 | + public LicenseHistory createLicenseHistoryAction(License lic, User user, String action) { |
|---|
| 136 | + return createLicenseHistoryAction(lic, user, action, null); |
|---|
| 137 | + } |
|---|
| 105 | 138 | |
|---|
| 106 | | - public Map<String, Object> extractPackMetadata(Set<PackMetadata> packMetadata) { |
|---|
| 107 | | - Map<String, Object> metadata = new HashMap<>(); |
|---|
| 108 | | - for (PackMetadata md : packMetadata) { |
|---|
| 109 | | - metadata.put(md.getKey(), md.getValue()); |
|---|
| 110 | | - } |
|---|
| 139 | + /** |
|---|
| 140 | + * createTemporaryLicenseFile |
|---|
| 141 | + * <p> |
|---|
| 142 | + * Materializes the license payload into a temporary file for emailing/download. |
|---|
| 143 | + * The file is created under a unique temporary directory. |
|---|
| 144 | + * |
|---|
| 145 | + * Caller is responsible for deleting the file and its parent directory. |
|---|
| 146 | + * |
|---|
| 147 | + * @param lic License whose JSON/XML/text payload is in {@code getLicenseData()}. |
|---|
| 148 | + * @param licFileName Desired file name (e.g. "license.lic"). |
|---|
| 149 | + * @return A {@link File} pointing to the newly created file. |
|---|
| 150 | + * @throws IOException If the temporary directory or file cannot be created/written. |
|---|
| 151 | + */ |
|---|
| 152 | + public File createTemporaryLicenseFile(License lic, String licFileName) throws IOException { |
|---|
| 153 | + File f = Files.createTempDirectory("securis-server").toFile(); |
|---|
| 154 | + f = new File(f, licFileName); |
|---|
| 155 | + FileUtils.writeStringToFile(f, lic.getLicenseData(), StandardCharsets.UTF_8); |
|---|
| 156 | + return f; |
|---|
| 157 | + } |
|---|
| 111 | 158 | |
|---|
| 112 | | - return metadata; |
|---|
| 113 | | - } |
|---|
| 159 | + /** |
|---|
| 160 | + * extractPackMetadata |
|---|
| 161 | + * <p> |
|---|
| 162 | + * Converts pack metadata set to a map for license generation. |
|---|
| 163 | + * |
|---|
| 164 | + * @param packMetadata Set of {@link PackMetadata}. |
|---|
| 165 | + * @return Map with keys/values copied from metadata entries. |
|---|
| 166 | + */ |
|---|
| 167 | + public Map<String, Object> extractPackMetadata(Set<PackMetadata> packMetadata) { |
|---|
| 168 | + Map<String, Object> metadata = new HashMap<>(); |
|---|
| 169 | + for (PackMetadata md : packMetadata) { |
|---|
| 170 | + metadata.put(md.getKey(), md.getValue()); |
|---|
| 171 | + } |
|---|
| 172 | + return metadata; |
|---|
| 173 | + } |
|---|
| 114 | 174 | |
|---|
| 115 | | - /** |
|---|
| 116 | | - * If the action is a renew the expiration date is got form pack end valid |
|---|
| 117 | | - * date, if the action is a pre-activation the expiration date is calculated |
|---|
| 118 | | - * using the pack default valid period |
|---|
| 119 | | - * |
|---|
| 120 | | - * @param pack |
|---|
| 121 | | - * @param isPreActivation |
|---|
| 122 | | - * @return |
|---|
| 123 | | - */ |
|---|
| 124 | | - public Date getExpirationDateFromPack(Pack pack, boolean isPreActivation) { |
|---|
| 125 | | - Long validPeriod; |
|---|
| 126 | | - if (pack.getEndValidDate().before(new Date())) { |
|---|
| 127 | | - throw new CurisRuntimeException("Pack end valid period is reached, no new licenses can be activated."); |
|---|
| 128 | | - } |
|---|
| 129 | | - if (isPreActivation) { |
|---|
| 130 | | - validPeriod = pack.getPreactivationValidPeriod() * MS_PER_DAY; |
|---|
| 131 | | - } else { |
|---|
| 132 | | - if (pack.getRenewValidPeriod() <= 0) { |
|---|
| 133 | | - return pack.getEndValidDate(); |
|---|
| 134 | | - } |
|---|
| 135 | | - long renewPeriod = pack.getRenewValidPeriod() * MS_PER_DAY; |
|---|
| 136 | | - long expirationPeriod = pack.getEndValidDate().getTime() - new Date().getTime(); |
|---|
| 137 | | - validPeriod = renewPeriod < expirationPeriod ? renewPeriod : expirationPeriod; |
|---|
| 138 | | - } |
|---|
| 139 | | - Date expirationDate = new Date(new Date().getTime() + validPeriod); |
|---|
| 140 | | - return expirationDate; |
|---|
| 141 | | - } |
|---|
| 175 | + /** |
|---|
| 176 | + * getExpirationDateFromPack |
|---|
| 177 | + * <p> |
|---|
| 178 | + * Computes license expiration date depending on action type: |
|---|
| 179 | + * - Pre-activation: {@code preactivationValidPeriod} days from now. |
|---|
| 180 | + * - Renew/Activation: min(renewValidPeriod days, pack end date - now). |
|---|
| 181 | + * Fails fast if pack end date is already in the past. |
|---|
| 182 | + * |
|---|
| 183 | + * @param pack Pack with policy data. |
|---|
| 184 | + * @param isPreActivation Whether the operation is a pre-activation. |
|---|
| 185 | + * @return Calculated expiration {@link Date}. |
|---|
| 186 | + * @throws CurisRuntimeException if the pack's end date is in the past. |
|---|
| 187 | + */ |
|---|
| 188 | + public Date getExpirationDateFromPack(Pack pack, boolean isPreActivation) { |
|---|
| 189 | + Long validPeriod; |
|---|
| 190 | + if (pack.getEndValidDate().before(new Date())) { |
|---|
| 191 | + throw new CurisRuntimeException("Pack end valid period is reached, no new licenses can be activated."); |
|---|
| 192 | + } |
|---|
| 193 | + if (isPreActivation) { |
|---|
| 194 | + validPeriod = pack.getPreactivationValidPeriod() * MS_PER_DAY; |
|---|
| 195 | + } else { |
|---|
| 196 | + if (pack.getRenewValidPeriod() <= 0) { |
|---|
| 197 | + return pack.getEndValidDate(); |
|---|
| 198 | + } |
|---|
| 199 | + long renewPeriod = pack.getRenewValidPeriod() * MS_PER_DAY; |
|---|
| 200 | + long expirationPeriod = pack.getEndValidDate().getTime() - new Date().getTime(); |
|---|
| 201 | + validPeriod = renewPeriod < expirationPeriod ? renewPeriod : expirationPeriod; |
|---|
| 202 | + } |
|---|
| 203 | + Date expirationDate = new Date(new Date().getTime() + validPeriod); |
|---|
| 204 | + return expirationDate; |
|---|
| 205 | + } |
|---|
| 142 | 206 | |
|---|
| 143 | | - /** |
|---|
| 144 | | - * Get the next free code suffis for a given Pack |
|---|
| 145 | | - * |
|---|
| 146 | | - * @param packId |
|---|
| 147 | | - * @param em |
|---|
| 148 | | - * @return |
|---|
| 149 | | - */ |
|---|
| 150 | | - public int getNextCodeSuffix(int packId, EntityManager em) { |
|---|
| 151 | | - TypedQuery<Integer> query = em.createNamedQuery("last-code-suffix-used-in-pack", Integer.class); |
|---|
| 152 | | - query.setParameter("packId", packId); |
|---|
| 153 | | - Integer lastCodeSuffix = query.getSingleResult(); |
|---|
| 154 | | - return lastCodeSuffix == null ? 1 : lastCodeSuffix + 1; |
|---|
| 155 | | - } |
|---|
| 156 | | - |
|---|
| 207 | + /** |
|---|
| 208 | + * getNextCodeSuffix |
|---|
| 209 | + * <p> |
|---|
| 210 | + * Retrieves the last used code suffix for a given pack and returns the next one. |
|---|
| 211 | + * If none found, returns 1. |
|---|
| 212 | + * |
|---|
| 213 | + * @param packId Pack identifier. |
|---|
| 214 | + * @param em EntityManager to query the DB. |
|---|
| 215 | + * @return Next sequential suffix (>= 1). |
|---|
| 216 | + */ |
|---|
| 217 | + public int getNextCodeSuffix(int packId, EntityManager em) { |
|---|
| 218 | + TypedQuery<Integer> query = em.createNamedQuery("last-code-suffix-used-in-pack", Integer.class); |
|---|
| 219 | + query.setParameter("packId", packId); |
|---|
| 220 | + Integer lastCodeSuffix = query.getSingleResult(); |
|---|
| 221 | + return lastCodeSuffix == null ? 1 : lastCodeSuffix + 1; |
|---|
| 222 | + } |
|---|
| 157 | 223 | } |
|---|
| 224 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services.helpers; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.Collection; |
|---|
| .. | .. |
|---|
| 24 | 27 | import net.curisit.securis.db.PackMetadata; |
|---|
| 25 | 28 | import net.curisit.securis.db.common.Metadata; |
|---|
| 26 | 29 | |
|---|
| 30 | +/** |
|---|
| 31 | + * MetadataHelper |
|---|
| 32 | + * <p> |
|---|
| 33 | + * Utilities to compare, merge and propagate metadata across the hierarchy: |
|---|
| 34 | + * Application -> LicenseType -> Pack -> (marks License as metadata-obsolete) |
|---|
| 35 | + * <p> |
|---|
| 36 | + * Provides: |
|---|
| 37 | + * - Equality checks on metadata sets. |
|---|
| 38 | + * - Merge semantics: remove keys not present, update changed values/mandatory flags. |
|---|
| 39 | + * - Propagation from Application down to LicenseType and from LicenseType down to Packs. |
|---|
| 40 | + * - Marking existing licenses as "metadataObsolete" when pack metadata changes and |
|---|
| 41 | + * the license is in a state where consumers could depend on metadata snapshot. |
|---|
| 42 | + * |
|---|
| 43 | + * Thread-safety: ApplicationScoped, stateless. |
|---|
| 44 | + * |
|---|
| 45 | + * @author JRA |
|---|
| 46 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 47 | + */ |
|---|
| 27 | 48 | @ApplicationScoped |
|---|
| 28 | 49 | public class MetadataHelper { |
|---|
| 29 | 50 | |
|---|
| 30 | | - private static final Logger log = LogManager.getLogger(MetadataHelper.class); |
|---|
| 51 | + private static final Logger log = LogManager.getLogger(MetadataHelper.class); |
|---|
| 31 | 52 | |
|---|
| 32 | | - public <T extends Metadata> boolean match(T m1, T m2) { |
|---|
| 33 | | - if (m1 == null || m2 == null) { |
|---|
| 34 | | - return false; |
|---|
| 35 | | - } |
|---|
| 36 | | - return Objects.equals(m1.getKey(), m2.getKey()) && Objects.equals(m1.getValue(), m2.getValue()) && m1.isMandatory() == m2.isMandatory(); |
|---|
| 37 | | - } |
|---|
| 53 | + /** |
|---|
| 54 | + * match |
|---|
| 55 | + * <p> |
|---|
| 56 | + * Compare two metadata entries (key, value, mandatory). |
|---|
| 57 | + * |
|---|
| 58 | + * @param m1 First metadata. |
|---|
| 59 | + * @param m2 Second metadata. |
|---|
| 60 | + * @param <T> Metadata subtype. |
|---|
| 61 | + * @return true if equal in key/value/mandatory, false otherwise or if any is null. |
|---|
| 62 | + */ |
|---|
| 63 | + public <T extends Metadata> boolean match(T m1, T m2) { |
|---|
| 64 | + if (m1 == null || m2 == null) { |
|---|
| 65 | + return false; |
|---|
| 66 | + } |
|---|
| 67 | + return Objects.equals(m1.getKey(), m2.getKey()) && Objects.equals(m1.getValue(), m2.getValue()) && m1.isMandatory() == m2.isMandatory(); |
|---|
| 68 | + } |
|---|
| 38 | 69 | |
|---|
| 39 | | - public <T extends Metadata> Metadata findByKey(String key, Collection<T> listMd) { |
|---|
| 40 | | - return listMd.parallelStream().filter(m -> Objects.equals(key, m.getKey())).findAny().orElse(null); |
|---|
| 41 | | - } |
|---|
| 70 | + /** |
|---|
| 71 | + * findByKey |
|---|
| 72 | + * <p> |
|---|
| 73 | + * Find a metadata by key in a collection. |
|---|
| 74 | + * |
|---|
| 75 | + * @param key Metadata key to search. |
|---|
| 76 | + * @param listMd Collection of metadata. |
|---|
| 77 | + * @param <T> Metadata subtype. |
|---|
| 78 | + * @return The first matching metadata or null. |
|---|
| 79 | + */ |
|---|
| 80 | + public <T extends Metadata> Metadata findByKey(String key, Collection<T> listMd) { |
|---|
| 81 | + return listMd.parallelStream().filter(m -> Objects.equals(key, m.getKey())).findAny().orElse(null); |
|---|
| 82 | + } |
|---|
| 42 | 83 | |
|---|
| 43 | | - public <T extends Metadata> boolean match(Set<T> listMd1, Set<T> listMd2) { |
|---|
| 44 | | - if (listMd1.size() != listMd2.size()) { |
|---|
| 45 | | - return false; |
|---|
| 46 | | - } |
|---|
| 47 | | - return listMd1.parallelStream().allMatch(m -> this.match(m, findByKey(m.getKey(), listMd2))); |
|---|
| 48 | | - } |
|---|
| 84 | + /** |
|---|
| 85 | + * match |
|---|
| 86 | + * <p> |
|---|
| 87 | + * Compare two sets of metadata for equality (size + all entries match). |
|---|
| 88 | + * |
|---|
| 89 | + * @param listMd1 First set. |
|---|
| 90 | + * @param listMd2 Second set. |
|---|
| 91 | + * @param <T> Metadata subtype. |
|---|
| 92 | + * @return true if both sets match element-wise, false otherwise. |
|---|
| 93 | + */ |
|---|
| 94 | + public <T extends Metadata> boolean match(Set<T> listMd1, Set<T> listMd2) { |
|---|
| 95 | + if (listMd1.size() != listMd2.size()) { |
|---|
| 96 | + return false; |
|---|
| 97 | + } |
|---|
| 98 | + return listMd1.parallelStream().allMatch(m -> this.match(m, findByKey(m.getKey(), listMd2))); |
|---|
| 99 | + } |
|---|
| 49 | 100 | |
|---|
| 50 | | - public <T extends Metadata, K extends Metadata> void mergeMetadata(EntityManager em, Set<T> srcListMd, Set<K> tgtListMd, Set<String> keys) { |
|---|
| 101 | + /** |
|---|
| 102 | + * mergeMetadata |
|---|
| 103 | + * <p> |
|---|
| 104 | + * Merge metadata from a source set (truth) into a target set. |
|---|
| 105 | + * - Removes entries in target whose keys are not in {@code keys}. |
|---|
| 106 | + * - Updates entries in target whose value/mandatory differ from source. |
|---|
| 107 | + * - Does NOT create new entries; caller is expected to persist new ones separately. |
|---|
| 108 | + * |
|---|
| 109 | + * @param em EntityManager to remove/merge. |
|---|
| 110 | + * @param srcListMd Source metadata set (truth). |
|---|
| 111 | + * @param tgtListMd Target metadata set to update. |
|---|
| 112 | + * @param keys Keys present in source. |
|---|
| 113 | + * @param <T> Source metadata type. |
|---|
| 114 | + * @param <K> Target metadata type. |
|---|
| 115 | + */ |
|---|
| 116 | + public <T extends Metadata, K extends Metadata> void mergeMetadata(EntityManager em, Set<T> srcListMd, Set<K> tgtListMd, Set<String> keys) { |
|---|
| 51 | 117 | |
|---|
| 52 | | - Set<K> mdToRemove = tgtListMd.parallelStream() // |
|---|
| 53 | | - .filter(md -> !keys.contains(md.getKey())) // |
|---|
| 54 | | - .collect(Collectors.toSet()); |
|---|
| 55 | | - for (K tgtMd : mdToRemove) { |
|---|
| 56 | | - log.info("MD key to remove: {} - {}", tgtMd.getKey(), tgtMd); |
|---|
| 57 | | - if (tgtMd instanceof LicenseTypeMetadata) { |
|---|
| 58 | | - log.info("LT: {}, tx: {}, contans: {}", LicenseTypeMetadata.class.cast(tgtMd).getLicenseType(), em.isJoinedToTransaction(), em.contains(tgtMd)); |
|---|
| 59 | | - } |
|---|
| 60 | | - em.remove(tgtMd); |
|---|
| 61 | | - } |
|---|
| 62 | | - Set<K> keysToUpdate = tgtListMd.parallelStream() // |
|---|
| 63 | | - .filter(md -> keys.contains(md.getKey())) // |
|---|
| 64 | | - .collect(Collectors.toSet()); |
|---|
| 65 | | - for (K tgtMd : keysToUpdate) { |
|---|
| 66 | | - Metadata md = this.findByKey(tgtMd.getKey(), srcListMd); |
|---|
| 67 | | - if (md.isMandatory() != tgtMd.isMandatory() || !Objects.equals(md.getValue(), tgtMd.getValue())) { |
|---|
| 68 | | - tgtMd.setMandatory(md.isMandatory()); |
|---|
| 69 | | - tgtMd.setValue(md.getValue()); |
|---|
| 70 | | - log.info("MD key to update: {}", tgtMd.getKey()); |
|---|
| 71 | | - em.merge(tgtMd); |
|---|
| 72 | | - } |
|---|
| 73 | | - } |
|---|
| 74 | | - } |
|---|
| 118 | + // Remove missing keys |
|---|
| 119 | + Set<K> mdToRemove = tgtListMd.parallelStream() |
|---|
| 120 | + .filter(md -> !keys.contains(md.getKey())) |
|---|
| 121 | + .collect(Collectors.toSet()); |
|---|
| 122 | + for (K tgtMd : mdToRemove) { |
|---|
| 123 | + log.info("MD key to remove: {} - {}", tgtMd.getKey(), tgtMd); |
|---|
| 124 | + if (tgtMd instanceof LicenseTypeMetadata) { |
|---|
| 125 | + log.info("LT: {}, tx: {}, contans: {}", LicenseTypeMetadata.class.cast(tgtMd).getLicenseType(), em.isJoinedToTransaction(), em.contains(tgtMd)); |
|---|
| 126 | + } |
|---|
| 127 | + em.remove(tgtMd); |
|---|
| 128 | + } |
|---|
| 75 | 129 | |
|---|
| 76 | | - private Set<LicenseTypeMetadata> createNewMetadata(Set<ApplicationMetadata> appMd, Set<LicenseTypeMetadata> existingMd, LicenseType licenseType) { |
|---|
| 77 | | - Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet()); |
|---|
| 78 | | - return appMd.parallelStream() // |
|---|
| 79 | | - .filter(md -> !oldKeys.contains(md.getKey())) // |
|---|
| 80 | | - .map(appmd -> { |
|---|
| 81 | | - LicenseTypeMetadata ltmd = new LicenseTypeMetadata(); |
|---|
| 82 | | - ltmd.setLicenseType(licenseType); |
|---|
| 83 | | - ltmd.setKey(appmd.getKey()); |
|---|
| 84 | | - ltmd.setValue(appmd.getValue()); |
|---|
| 85 | | - ltmd.setMandatory(appmd.isMandatory()); |
|---|
| 86 | | - return ltmd; |
|---|
| 87 | | - }).collect(Collectors.toSet()); |
|---|
| 88 | | - } |
|---|
| 130 | + // Update changed keys |
|---|
| 131 | + Set<K> keysToUpdate = tgtListMd.parallelStream() |
|---|
| 132 | + .filter(md -> keys.contains(md.getKey())) |
|---|
| 133 | + .collect(Collectors.toSet()); |
|---|
| 134 | + for (K tgtMd : keysToUpdate) { |
|---|
| 135 | + Metadata md = this.findByKey(tgtMd.getKey(), srcListMd); |
|---|
| 136 | + if (md.isMandatory() != tgtMd.isMandatory() || !Objects.equals(md.getValue(), tgtMd.getValue())) { |
|---|
| 137 | + tgtMd.setMandatory(md.isMandatory()); |
|---|
| 138 | + tgtMd.setValue(md.getValue()); |
|---|
| 139 | + log.info("MD key to update: {}", tgtMd.getKey()); |
|---|
| 140 | + em.merge(tgtMd); |
|---|
| 141 | + } |
|---|
| 142 | + } |
|---|
| 143 | + } |
|---|
| 89 | 144 | |
|---|
| 90 | | - private Set<PackMetadata> createNewMetadata(Set<LicenseTypeMetadata> ltMd, Set<PackMetadata> existingMd, Pack pack) { |
|---|
| 91 | | - Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet()); |
|---|
| 92 | | - return ltMd.parallelStream() // |
|---|
| 93 | | - .filter(md -> !oldKeys.contains(md.getKey())) // |
|---|
| 94 | | - .map(md -> { |
|---|
| 95 | | - PackMetadata pmd = new PackMetadata(); |
|---|
| 96 | | - pmd.setPack(pack); |
|---|
| 97 | | - pmd.setKey(md.getKey()); |
|---|
| 98 | | - pmd.setValue(md.getValue()); |
|---|
| 99 | | - pmd.setMandatory(md.isMandatory()); |
|---|
| 100 | | - return pmd; |
|---|
| 101 | | - }).collect(Collectors.toSet()); |
|---|
| 102 | | - } |
|---|
| 145 | + // -- Internal helpers to create new metadata rows when propagating |
|---|
| 103 | 146 | |
|---|
| 104 | | - /** |
|---|
| 105 | | - * Copy the modified app metadata to LicenseTypes and Packs |
|---|
| 106 | | - * |
|---|
| 107 | | - * @param em |
|---|
| 108 | | - * @param app |
|---|
| 109 | | - */ |
|---|
| 110 | | - public void propagateMetadata(EntityManager em, Application app) { |
|---|
| 111 | | - Set<ApplicationMetadata> appMd = app.getApplicationMetadata(); |
|---|
| 112 | | - Set<String> keys = appMd.parallelStream().map(md -> md.getKey()).collect(Collectors.toSet()); |
|---|
| 113 | | - for (LicenseType lt : app.getLicenseTypes()) { |
|---|
| 114 | | - log.info("Lic type to update: {}", lt.getCode()); |
|---|
| 115 | | - this.mergeMetadata(em, appMd, lt.getMetadata(), keys); |
|---|
| 116 | | - Set<LicenseTypeMetadata> newMdList = createNewMetadata(appMd, lt.getMetadata(), lt); |
|---|
| 117 | | - for (LicenseTypeMetadata newMetadata : newMdList) { |
|---|
| 118 | | - em.persist(newMetadata); |
|---|
| 119 | | - } |
|---|
| 120 | | - em.detach(lt); |
|---|
| 121 | | - // Probably there is a better way to get the final metadata from JPA... |
|---|
| 122 | | - TypedQuery<LicenseTypeMetadata> updatedMdQuery = em.createNamedQuery("list-licensetype-metadata", LicenseTypeMetadata.class); |
|---|
| 123 | | - updatedMdQuery.setParameter("licenseTypeId", lt.getId()); |
|---|
| 124 | | - Set<LicenseTypeMetadata> updatedMd = new HashSet<>(updatedMdQuery.getResultList()); |
|---|
| 147 | + /** |
|---|
| 148 | + * createNewMetadata<p> |
|---|
| 149 | + * Create new metadata |
|---|
| 150 | + * |
|---|
| 151 | + * @param appMd |
|---|
| 152 | + * @param existingMd |
|---|
| 153 | + * @param licenseType |
|---|
| 154 | + * @return newMetadata |
|---|
| 155 | + */ |
|---|
| 156 | + private Set<LicenseTypeMetadata> createNewMetadata(Set<ApplicationMetadata> appMd, Set<LicenseTypeMetadata> existingMd, LicenseType licenseType) { |
|---|
| 157 | + Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet()); |
|---|
| 158 | + return appMd.parallelStream() |
|---|
| 159 | + .filter(md -> !oldKeys.contains(md.getKey())) |
|---|
| 160 | + .map(appmd -> { |
|---|
| 161 | + LicenseTypeMetadata ltmd = new LicenseTypeMetadata(); |
|---|
| 162 | + ltmd.setLicenseType(licenseType); |
|---|
| 163 | + ltmd.setKey(appmd.getKey()); |
|---|
| 164 | + ltmd.setValue(appmd.getValue()); |
|---|
| 165 | + ltmd.setMandatory(appmd.isMandatory()); |
|---|
| 166 | + return ltmd; |
|---|
| 167 | + }).collect(Collectors.toSet()); |
|---|
| 168 | + } |
|---|
| 125 | 169 | |
|---|
| 126 | | - lt.setMetadata(updatedMd); |
|---|
| 127 | | - propagateMetadata(em, lt, keys); |
|---|
| 128 | | - } |
|---|
| 129 | | - } |
|---|
| 170 | + /** |
|---|
| 171 | + * createNewMetadata<p> |
|---|
| 172 | + * Create the new metadata |
|---|
| 173 | + * |
|---|
| 174 | + * @param ltMd |
|---|
| 175 | + * @param existingMd |
|---|
| 176 | + * @param pack |
|---|
| 177 | + * @return newMetadata |
|---|
| 178 | + */ |
|---|
| 179 | + private Set<PackMetadata> createNewMetadata(Set<LicenseTypeMetadata> ltMd, Set<PackMetadata> existingMd, Pack pack) { |
|---|
| 180 | + Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet()); |
|---|
| 181 | + return ltMd.parallelStream() |
|---|
| 182 | + .filter(md -> !oldKeys.contains(md.getKey())) |
|---|
| 183 | + .map(md -> { |
|---|
| 184 | + PackMetadata pmd = new PackMetadata(); |
|---|
| 185 | + pmd.setPack(pack); |
|---|
| 186 | + pmd.setKey(md.getKey()); |
|---|
| 187 | + pmd.setValue(md.getValue()); |
|---|
| 188 | + pmd.setMandatory(md.isMandatory()); |
|---|
| 189 | + return pmd; |
|---|
| 190 | + }).collect(Collectors.toSet()); |
|---|
| 191 | + } |
|---|
| 130 | 192 | |
|---|
| 131 | | - /** |
|---|
| 132 | | - * Copy the modified licenseType metadata to Packs |
|---|
| 133 | | - * |
|---|
| 134 | | - * @param em |
|---|
| 135 | | - * @param lt |
|---|
| 136 | | - * @param keys |
|---|
| 137 | | - */ |
|---|
| 138 | | - public void propagateMetadata(EntityManager em, LicenseType lt, Set<String> keys) { |
|---|
| 139 | | - Set<LicenseTypeMetadata> ltMd = lt.getMetadata(); |
|---|
| 140 | | - TypedQuery<Pack> packsQuery = em.createNamedQuery("list-packs-by-lic-type", Pack.class); |
|---|
| 141 | | - packsQuery.setParameter("lt_id", lt.getId()); |
|---|
| 142 | | - List<Pack> packs = packsQuery.getResultList(); |
|---|
| 143 | | - log.info("Packs to update the metadata: {}", packs.size()); |
|---|
| 144 | | - for (Pack pack : packs) { |
|---|
| 145 | | - if (pack.isFrozen()) { |
|---|
| 146 | | - log.warn("Metadata in LicenseType {} has changed but the Pack {} is frozen and won't be updated.", lt.getCode(), pack.getCode()); |
|---|
| 147 | | - continue; |
|---|
| 148 | | - } |
|---|
| 149 | | - this.mergeMetadata(em, ltMd, pack.getMetadata(), keys); |
|---|
| 150 | | - Set<PackMetadata> newMdList = createNewMetadata(ltMd, pack.getMetadata(), pack); |
|---|
| 151 | | - for (PackMetadata newMetadata : newMdList) { |
|---|
| 152 | | - em.persist(newMetadata); |
|---|
| 153 | | - } |
|---|
| 154 | | - markObsoleteMetadata(em, pack); |
|---|
| 155 | | - em.detach(pack); |
|---|
| 156 | | - } |
|---|
| 157 | | - } |
|---|
| 193 | + /** |
|---|
| 194 | + * propagateMetadata (Application -> LicenseTypes -> Packs) |
|---|
| 195 | + * <p> |
|---|
| 196 | + * Propagates application metadata changes down to all its license types and packs: |
|---|
| 197 | + * - mergeMetadata on LicenseType |
|---|
| 198 | + * - create new LicenseTypeMetadata for new keys |
|---|
| 199 | + * - re-fetch LT metadata (detached/merged semantics) |
|---|
| 200 | + * - propagateMetadata(LicenseType) to packs |
|---|
| 201 | + * |
|---|
| 202 | + * @param em EntityManager. |
|---|
| 203 | + * @param app Application with updated metadata loaded. |
|---|
| 204 | + */ |
|---|
| 205 | + public void propagateMetadata(EntityManager em, Application app) { |
|---|
| 206 | + Set<ApplicationMetadata> appMd = app.getApplicationMetadata(); |
|---|
| 207 | + Set<String> keys = appMd.parallelStream().map(md -> md.getKey()).collect(Collectors.toSet()); |
|---|
| 208 | + for (LicenseType lt : app.getLicenseTypes()) { |
|---|
| 209 | + log.info("Lic type to update: {}", lt.getCode()); |
|---|
| 210 | + this.mergeMetadata(em, appMd, lt.getMetadata(), keys); |
|---|
| 211 | + Set<LicenseTypeMetadata> newMdList = createNewMetadata(appMd, lt.getMetadata(), lt); |
|---|
| 212 | + for (LicenseTypeMetadata newMetadata : newMdList) { |
|---|
| 213 | + em.persist(newMetadata); |
|---|
| 214 | + } |
|---|
| 215 | + em.detach(lt); |
|---|
| 158 | 216 | |
|---|
| 159 | | - public void markObsoleteMetadata(EntityManager em, Pack pack) { |
|---|
| 160 | | - TypedQuery<License> existingPackLicenses = em.createNamedQuery("list-licenses-by-pack", License.class); |
|---|
| 161 | | - existingPackLicenses.setParameter("packId", pack.getId()); |
|---|
| 162 | | - for (License lic : existingPackLicenses.getResultList()) { |
|---|
| 163 | | - log.info("License from pack: {}, status: {}", lic.getCode(), lic.getStatus()); |
|---|
| 164 | | - if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE || lic.getStatus() == LicenseStatus.CANCELLED) { |
|---|
| 165 | | - lic.setMetadataObsolete(true); |
|---|
| 166 | | - em.merge(lic); |
|---|
| 167 | | - } |
|---|
| 168 | | - } |
|---|
| 169 | | - } |
|---|
| 217 | + // Re-read updated metadata |
|---|
| 218 | + TypedQuery<LicenseTypeMetadata> updatedMdQuery = em.createNamedQuery("list-licensetype-metadata", LicenseTypeMetadata.class); |
|---|
| 219 | + updatedMdQuery.setParameter("licenseTypeId", lt.getId()); |
|---|
| 220 | + Set<LicenseTypeMetadata> updatedMd = new HashSet<>(updatedMdQuery.getResultList()); |
|---|
| 221 | + |
|---|
| 222 | + lt.setMetadata(updatedMd); |
|---|
| 223 | + propagateMetadata(em, lt, keys); |
|---|
| 224 | + } |
|---|
| 225 | + } |
|---|
| 226 | + |
|---|
| 227 | + /** |
|---|
| 228 | + * propagateMetadata (LicenseType -> Packs) |
|---|
| 229 | + * <p> |
|---|
| 230 | + * Propagates license type metadata changes to all its packs: |
|---|
| 231 | + * - mergeMetadata on Pack |
|---|
| 232 | + * - create new PackMetadata for new keys |
|---|
| 233 | + * - markObsoleteMetadata on packs to flag their licenses |
|---|
| 234 | + * |
|---|
| 235 | + * Frozen packs are skipped. |
|---|
| 236 | + * |
|---|
| 237 | + * @param em EntityManager. |
|---|
| 238 | + * @param lt LicenseType with updated metadata set. |
|---|
| 239 | + * @param keys Set of keys present in the source. |
|---|
| 240 | + */ |
|---|
| 241 | + public void propagateMetadata(EntityManager em, LicenseType lt, Set<String> keys) { |
|---|
| 242 | + Set<LicenseTypeMetadata> ltMd = lt.getMetadata(); |
|---|
| 243 | + TypedQuery<Pack> packsQuery = em.createNamedQuery("list-packs-by-lic-type", Pack.class); |
|---|
| 244 | + packsQuery.setParameter("lt_id", lt.getId()); |
|---|
| 245 | + List<Pack> packs = packsQuery.getResultList(); |
|---|
| 246 | + log.info("Packs to update the metadata: {}", packs.size()); |
|---|
| 247 | + for (Pack pack : packs) { |
|---|
| 248 | + if (pack.isFrozen()) { |
|---|
| 249 | + log.warn("Metadata in LicenseType {} has changed but the Pack {} is frozen and won't be updated.", lt.getCode(), pack.getCode()); |
|---|
| 250 | + continue; |
|---|
| 251 | + } |
|---|
| 252 | + this.mergeMetadata(em, ltMd, pack.getMetadata(), keys); |
|---|
| 253 | + Set<PackMetadata> newMdList = createNewMetadata(ltMd, pack.getMetadata(), pack); |
|---|
| 254 | + for (PackMetadata newMetadata : newMdList) { |
|---|
| 255 | + em.persist(newMetadata); |
|---|
| 256 | + } |
|---|
| 257 | + markObsoleteMetadata(em, pack); |
|---|
| 258 | + em.detach(pack); |
|---|
| 259 | + } |
|---|
| 260 | + } |
|---|
| 261 | + |
|---|
| 262 | + /** |
|---|
| 263 | + * markObsoleteMetadata |
|---|
| 264 | + * <p> |
|---|
| 265 | + * For all licenses within the given pack, mark {@code metadataObsolete = true} |
|---|
| 266 | + * if the license is in a relevant state (ACTIVE, PRE_ACTIVE, CANCELLED). |
|---|
| 267 | + * This lets clients know that metadata-dependent artifacts might need refresh. |
|---|
| 268 | + * |
|---|
| 269 | + * @param em EntityManager. |
|---|
| 270 | + * @param pack Pack whose licenses to mark. |
|---|
| 271 | + */ |
|---|
| 272 | + public void markObsoleteMetadata(EntityManager em, Pack pack) { |
|---|
| 273 | + TypedQuery<License> existingPackLicenses = em.createNamedQuery("list-licenses-by-pack", License.class); |
|---|
| 274 | + existingPackLicenses.setParameter("packId", pack.getId()); |
|---|
| 275 | + for (License lic : existingPackLicenses.getResultList()) { |
|---|
| 276 | + log.info("License from pack: {}, status: {}", lic.getCode(), lic.getStatus()); |
|---|
| 277 | + if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE || lic.getStatus() == LicenseStatus.CANCELLED) { |
|---|
| 278 | + lic.setMetadataObsolete(true); |
|---|
| 279 | + em.merge(lic); |
|---|
| 280 | + } |
|---|
| 281 | + } |
|---|
| 282 | + } |
|---|
| 170 | 283 | } |
|---|
| 284 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.services.helpers; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import jakarta.enterprise.context.ApplicationScoped; |
|---|
| .. | .. |
|---|
| 8 | 11 | import net.curisit.securis.security.BasicSecurityContext; |
|---|
| 9 | 12 | import net.curisit.securis.services.exception.SeCurisServiceException; |
|---|
| 10 | 13 | |
|---|
| 14 | +/** |
|---|
| 15 | + * UserHelper |
|---|
| 16 | + * <p> |
|---|
| 17 | + * Small helper to resolve the current user (from security context) or by username. |
|---|
| 18 | + * Throws a typed {@link SeCurisServiceException} if the user cannot be found. |
|---|
| 19 | + * |
|---|
| 20 | + * Thread-safety: ApplicationScoped, stateless. |
|---|
| 21 | + * |
|---|
| 22 | + * @author JRA |
|---|
| 23 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 24 | + */ |
|---|
| 11 | 25 | @ApplicationScoped |
|---|
| 12 | 26 | public class UserHelper { |
|---|
| 13 | 27 | |
|---|
| 28 | + /** |
|---|
| 29 | + * getUser |
|---|
| 30 | + * <p> |
|---|
| 31 | + * Resolve the current authenticated user from {@link BasicSecurityContext}. |
|---|
| 32 | + * |
|---|
| 33 | + * @param bsc Security context containing a principal. |
|---|
| 34 | + * @param em EntityManager to fetch the user. |
|---|
| 35 | + * @return Managed {@link User}. |
|---|
| 36 | + * @throws SeCurisServiceException if the principal is null or not found in DB. |
|---|
| 37 | + */ |
|---|
| 14 | 38 | public User getUser(BasicSecurityContext bsc, EntityManager em) throws SeCurisServiceException { |
|---|
| 15 | 39 | String username = bsc.getUserPrincipal().getName(); |
|---|
| 16 | 40 | return getUser(username, em); |
|---|
| 17 | 41 | } |
|---|
| 18 | 42 | |
|---|
| 43 | + /** |
|---|
| 44 | + * getUser |
|---|
| 45 | + * <p> |
|---|
| 46 | + * Resolve a user by username. |
|---|
| 47 | + * |
|---|
| 48 | + * @param username Username to look up (nullable allowed; returns null). |
|---|
| 49 | + * @param em EntityManager to fetch the user. |
|---|
| 50 | + * @return Managed {@link User} or null if username is null. |
|---|
| 51 | + * @throws SeCurisServiceException if a non-null username does not exist. |
|---|
| 52 | + */ |
|---|
| 19 | 53 | public User getUser(String username, EntityManager em) throws SeCurisServiceException { |
|---|
| 20 | 54 | User user = null; |
|---|
| 21 | 55 | if (username != null) { |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | +* Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | +*/ |
|---|
| 1 | 4 | package net.curisit.securis.utils; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.util.ArrayList; |
|---|
| .. | .. |
|---|
| 5 | 8 | import java.util.HashMap; |
|---|
| 6 | 9 | import java.util.List; |
|---|
| 7 | 10 | import java.util.Map; |
|---|
| 11 | +import java.util.Set; |
|---|
| 8 | 12 | |
|---|
| 9 | 13 | import jakarta.enterprise.context.ApplicationScoped; |
|---|
| 10 | 14 | import jakarta.inject.Inject; |
|---|
| .. | .. |
|---|
| 13 | 17 | import org.apache.logging.log4j.Logger; |
|---|
| 14 | 18 | |
|---|
| 15 | 19 | /** |
|---|
| 16 | | - * Cache implementation with TTL (time To Live) The objects are removed from |
|---|
| 17 | | - * cache when TTL is reached. |
|---|
| 18 | | - * |
|---|
| 19 | | - * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 20 | | - */ |
|---|
| 20 | +* CacheTTL |
|---|
| 21 | +* <p> |
|---|
| 22 | +* Simple in-memory cache with per-entry TTL (time-to-live). A background |
|---|
| 23 | +* cleaning thread periodically removes expired entries. |
|---|
| 24 | +* |
|---|
| 25 | +* <p><b>Type-safety note:</b> Besides generic getters, this cache provides |
|---|
| 26 | +* {@link #getSet(String, Class)} to safely retrieve {@code Set<E>} values |
|---|
| 27 | +* without unchecked warnings at call sites. The method validates that all |
|---|
| 28 | +* elements match the requested {@code elementType}. |
|---|
| 29 | +* |
|---|
| 30 | +* <p><b>Threading:</b> This implementation is lightweight and uses a single |
|---|
| 31 | +* cleaner thread. The internal map is not synchronized beyond the remove loop, |
|---|
| 32 | +* which is acceptable for low-concurrency scenarios. For heavier usage, |
|---|
| 33 | +* consider switching to a {@code ConcurrentHashMap} and/or a scheduled executor. |
|---|
| 34 | +* |
|---|
| 35 | +* @author roberto |
|---|
| 36 | +* Last reviewed by JRA on Oct 5, 2025. |
|---|
| 37 | +*/ |
|---|
| 21 | 38 | @ApplicationScoped |
|---|
| 22 | 39 | public class CacheTTL { |
|---|
| 23 | 40 | |
|---|
| 24 | 41 | private static final Logger LOG = LogManager.getLogger(CacheTTL.class); |
|---|
| 25 | 42 | |
|---|
| 43 | + /** Default TTL (seconds) for entries when not specified. */ |
|---|
| 44 | + private static final int DEFAULT_CACHE_DURATION = 24 * 60 * 60; |
|---|
| 45 | + |
|---|
| 46 | + /** Backing store: key → cached object + expiration. */ |
|---|
| 47 | + private final Map<String, CachedObject> data = new HashMap<>(); |
|---|
| 48 | + |
|---|
| 49 | + /** Background cleaner thread. */ |
|---|
| 50 | + private final Thread cleaningThread; |
|---|
| 51 | + |
|---|
| 26 | 52 | /** |
|---|
| 27 | | - * Period before token expires, set in seconds. |
|---|
| 28 | | - */ |
|---|
| 29 | | - private static int DEFAULT_CACHE_DURATION = 24 * 60 * 60; |
|---|
| 30 | | - |
|---|
| 31 | | - private Map<String, CachedObject> data = new HashMap<>(); |
|---|
| 32 | | - |
|---|
| 33 | | - private Thread cleaningThread = null; |
|---|
| 34 | | - |
|---|
| 53 | + * CacheTTL<p> |
|---|
| 54 | + * Construct a cache and start the background cleaner that removes expired |
|---|
| 55 | + * entries every 60 seconds. |
|---|
| 56 | + */ |
|---|
| 35 | 57 | @Inject |
|---|
| 36 | 58 | public CacheTTL() { |
|---|
| 37 | | - cleaningThread = new Thread(new Runnable() { |
|---|
| 38 | | - |
|---|
| 39 | | - @Override |
|---|
| 40 | | - public void run() { |
|---|
| 41 | | - while (CacheTTL.this.data != null) { |
|---|
| 42 | | - try { |
|---|
| 43 | | - // We check for expired object every 60 seconds |
|---|
| 44 | | - Thread.sleep(60 * 1000); |
|---|
| 45 | | - } catch (InterruptedException e) { |
|---|
| 46 | | - LOG.error("Exiting from Cache Thread"); |
|---|
| 47 | | - data.clear(); |
|---|
| 48 | | - return; |
|---|
| 49 | | - } |
|---|
| 50 | | - Date now = new Date(); |
|---|
| 51 | | - List<String> keysToRemove = new ArrayList<>(); |
|---|
| 52 | | - for (String key : CacheTTL.this.data.keySet()) { |
|---|
| 53 | | - CachedObject co = CacheTTL.this.data.get(key); |
|---|
| 54 | | - if (now.after(co.getExpireAt())) { |
|---|
| 55 | | - keysToRemove.add(key); |
|---|
| 56 | | - } |
|---|
| 57 | | - } |
|---|
| 58 | | - for (String key : keysToRemove) { |
|---|
| 59 | | - // If we try to remove directly in the previous loop an |
|---|
| 60 | | - // exception is thrown |
|---|
| 61 | | - // java.util.ConcurrentModificationException |
|---|
| 62 | | - CacheTTL.this.data.remove(key); |
|---|
| 59 | + cleaningThread = new Thread(() -> { |
|---|
| 60 | + while (true) { |
|---|
| 61 | + try { |
|---|
| 62 | + // Check for expired objects every 60 seconds |
|---|
| 63 | + Thread.sleep(60_000); |
|---|
| 64 | + } catch (InterruptedException e) { |
|---|
| 65 | + LOG.warn("Cache cleaner interrupted. Clearing cache and stopping."); |
|---|
| 66 | + data.clear(); |
|---|
| 67 | + return; |
|---|
| 68 | + } |
|---|
| 69 | + Date now = new Date(); |
|---|
| 70 | + List<String> keysToRemove = new ArrayList<>(); |
|---|
| 71 | + for (String key : data.keySet()) { |
|---|
| 72 | + CachedObject co = data.get(key); |
|---|
| 73 | + if (co != null && now.after(co.getExpireAt())) { |
|---|
| 74 | + keysToRemove.add(key); |
|---|
| 63 | 75 | } |
|---|
| 64 | 76 | } |
|---|
| 77 | + for (String key : keysToRemove) { |
|---|
| 78 | + // Avoid ConcurrentModificationException by removing after iteration |
|---|
| 79 | + data.remove(key); |
|---|
| 80 | + } |
|---|
| 65 | 81 | } |
|---|
| 66 | | - }); |
|---|
| 82 | + }, "CacheTTL-Cleaner"); |
|---|
| 83 | + cleaningThread.setDaemon(true); |
|---|
| 67 | 84 | cleaningThread.start(); |
|---|
| 68 | 85 | } |
|---|
| 69 | 86 | |
|---|
| 87 | + // --------------------------------------------------------------------- |
|---|
| 88 | + // Putters |
|---|
| 89 | + // --------------------------------------------------------------------- |
|---|
| 90 | + |
|---|
| 70 | 91 | /** |
|---|
| 71 | | - * |
|---|
| 72 | | - * @param key |
|---|
| 73 | | - * @param obj |
|---|
| 74 | | - * @param ttl |
|---|
| 75 | | - * Time To Live in seconds |
|---|
| 76 | | - */ |
|---|
| 92 | + * set<p> |
|---|
| 93 | + * Store a value with an explicit TTL. |
|---|
| 94 | + * |
|---|
| 95 | + * @param key cache key |
|---|
| 96 | + * @param obj value to store (may be any object, including collections) |
|---|
| 97 | + * @param ttl TTL in seconds |
|---|
| 98 | + */ |
|---|
| 77 | 99 | public void set(String key, Object obj, int ttl) { |
|---|
| 78 | | - Date expirationDate = new Date(new Date().getTime() + ttl * 1000); |
|---|
| 100 | + Date expirationDate = new Date(System.currentTimeMillis() + (long) ttl * 1000L); |
|---|
| 79 | 101 | data.put(key, new CachedObject(expirationDate, obj)); |
|---|
| 80 | 102 | } |
|---|
| 81 | 103 | |
|---|
| 104 | + /** |
|---|
| 105 | + * set<p> |
|---|
| 106 | + * Store a value with the default TTL. |
|---|
| 107 | + * |
|---|
| 108 | + * @param key cache key |
|---|
| 109 | + * @param obj value to store |
|---|
| 110 | + */ |
|---|
| 82 | 111 | public void set(String key, Object obj) { |
|---|
| 83 | 112 | set(key, obj, DEFAULT_CACHE_DURATION); |
|---|
| 84 | 113 | } |
|---|
| 85 | 114 | |
|---|
| 115 | + // --------------------------------------------------------------------- |
|---|
| 116 | + // Getters |
|---|
| 117 | + // --------------------------------------------------------------------- |
|---|
| 118 | + |
|---|
| 119 | + /** |
|---|
| 120 | + * get<p> |
|---|
| 121 | + * Retrieve a value as {@code Object}. Returns {@code null} if not present |
|---|
| 122 | + * or expired (expired entries are eagerly removed by the cleaner). |
|---|
| 123 | + * |
|---|
| 124 | + * @param key cache key |
|---|
| 125 | + * @return cached value or null |
|---|
| 126 | + */ |
|---|
| 86 | 127 | public Object get(String key) { |
|---|
| 87 | 128 | CachedObject co = data.get(key); |
|---|
| 88 | 129 | return co == null ? null : co.getObject(); |
|---|
| 89 | 130 | } |
|---|
| 90 | 131 | |
|---|
| 132 | + /** |
|---|
| 133 | + * get<p> |
|---|
| 134 | + * Retrieve a value and cast it to the requested type. The cast is unchecked |
|---|
| 135 | + * due to type erasure, but localized within the cache implementation. |
|---|
| 136 | + * |
|---|
| 137 | + * @param key cache key |
|---|
| 138 | + * @param type expected value type |
|---|
| 139 | + * @param <T> generic type |
|---|
| 140 | + * @return cached value typed or null |
|---|
| 141 | + */ |
|---|
| 91 | 142 | public <T> T get(String key, Class<T> type) { |
|---|
| 92 | 143 | CachedObject co = data.get(key); |
|---|
| 93 | 144 | return co == null ? null : co.getObject(type); |
|---|
| 94 | 145 | } |
|---|
| 95 | 146 | |
|---|
| 147 | + /** |
|---|
| 148 | + * getSet<p> |
|---|
| 149 | + * Retrieve a {@code Set<E>} in a type-safe way without unchecked warnings |
|---|
| 150 | + * at the call site. The method validates that the cached value is a |
|---|
| 151 | + * {@code Set} and that <b>all</b> elements are instances of {@code elementType}. |
|---|
| 152 | + * If any element does not match, the method returns {@code null} and logs a warning. |
|---|
| 153 | + * |
|---|
| 154 | + * @param key cache key |
|---|
| 155 | + * @param elementType class of the set elements (e.g., {@code Integer.class}) |
|---|
| 156 | + * @return typed set or null if missing/type-mismatch |
|---|
| 157 | + */ |
|---|
| 158 | + @SuppressWarnings("unchecked") |
|---|
| 159 | + public <E> Set<E> getSet(String key, Class<E> elementType) { |
|---|
| 160 | + Object obj = get(key); |
|---|
| 161 | + if (obj == null) return null; |
|---|
| 162 | + if (!(obj instanceof Set<?> raw)) { |
|---|
| 163 | + LOG.warn("Cache key '{}' expected Set<{}> but found {}", key, elementType.getSimpleName(), obj.getClass().getName()); |
|---|
| 164 | + return null; |
|---|
| 165 | + } |
|---|
| 166 | + // Validate element types to avoid ClassCastException later |
|---|
| 167 | + for (Object el : raw) { |
|---|
| 168 | + if (el != null && !elementType.isInstance(el)) { |
|---|
| 169 | + LOG.warn("Cache key '{}' contains element of type {}, expected {}", key, |
|---|
| 170 | + el.getClass().getName(), elementType.getName()); |
|---|
| 171 | + return null; |
|---|
| 172 | + } |
|---|
| 173 | + } |
|---|
| 174 | + // Safe due to element-wise validation |
|---|
| 175 | + return (Set<E>) raw; |
|---|
| 176 | + } |
|---|
| 177 | + |
|---|
| 178 | + // --------------------------------------------------------------------- |
|---|
| 179 | + // Removers & maintenance |
|---|
| 180 | + // --------------------------------------------------------------------- |
|---|
| 181 | + |
|---|
| 182 | + /** |
|---|
| 183 | + * remove<p> |
|---|
| 184 | + * Remove and return a value typed. |
|---|
| 185 | + * |
|---|
| 186 | + * @param key cache key |
|---|
| 187 | + * @param type expected type |
|---|
| 188 | + * @return removed value or null |
|---|
| 189 | + */ |
|---|
| 96 | 190 | public <T> T remove(String key, Class<T> type) { |
|---|
| 97 | 191 | CachedObject co = data.remove(key); |
|---|
| 98 | 192 | return co == null ? null : co.getObject(type); |
|---|
| 99 | 193 | } |
|---|
| 100 | 194 | |
|---|
| 195 | + /** |
|---|
| 196 | + * remove<p> |
|---|
| 197 | + * Remove and return a value as {@code Object}. |
|---|
| 198 | + * |
|---|
| 199 | + * @param key cache key |
|---|
| 200 | + * @return removed value or null |
|---|
| 201 | + */ |
|---|
| 101 | 202 | public Object remove(String key) { |
|---|
| 102 | 203 | CachedObject co = data.remove(key); |
|---|
| 103 | 204 | return co == null ? null : co.getObject(); |
|---|
| 104 | 205 | } |
|---|
| 105 | 206 | |
|---|
| 207 | + /** |
|---|
| 208 | + * clear<p> |
|---|
| 209 | + * Remove all entries from the cache. |
|---|
| 210 | + */ |
|---|
| 106 | 211 | public void clear() { |
|---|
| 107 | 212 | data.clear(); |
|---|
| 108 | 213 | } |
|---|
| 109 | 214 | |
|---|
| 110 | | - private class CachedObject { |
|---|
| 111 | | - Date expireAt; |
|---|
| 112 | | - Object object; |
|---|
| 215 | + // --------------------------------------------------------------------- |
|---|
| 216 | + // Internal structure |
|---|
| 217 | + // --------------------------------------------------------------------- |
|---|
| 113 | 218 | |
|---|
| 219 | + /** |
|---|
| 220 | + * CachedObject |
|---|
| 221 | + * <p> |
|---|
| 222 | + * Internal wrapper that pairs an arbitrary object with its expiration date. |
|---|
| 223 | + */ |
|---|
| 224 | + private static class CachedObject { |
|---|
| 225 | + private final Date expireAt; |
|---|
| 226 | + private final Object object; |
|---|
| 227 | + |
|---|
| 228 | + /** |
|---|
| 229 | + * Constructor<p> |
|---|
| 230 | + * Set expiration and payload. |
|---|
| 231 | + * |
|---|
| 232 | + * @param date |
|---|
| 233 | + * @param object |
|---|
| 234 | + */ |
|---|
| 114 | 235 | public CachedObject(Date date, Object obj) { |
|---|
| 115 | | - expireAt = date; |
|---|
| 116 | | - object = obj; |
|---|
| 236 | + this.expireAt = date; |
|---|
| 237 | + this.object = obj; |
|---|
| 117 | 238 | } |
|---|
| 118 | 239 | |
|---|
| 240 | + /** |
|---|
| 241 | + * getExpireAt<p> |
|---|
| 242 | + * Return expiration date. |
|---|
| 243 | + * |
|---|
| 244 | + * @return date |
|---|
| 245 | + */ |
|---|
| 119 | 246 | public Date getExpireAt() { |
|---|
| 120 | 247 | return expireAt; |
|---|
| 121 | 248 | } |
|---|
| 122 | 249 | |
|---|
| 250 | + /** |
|---|
| 251 | + * getObject<p> |
|---|
| 252 | + * Return payload as {@code Object}. |
|---|
| 253 | + * |
|---|
| 254 | + * @return object |
|---|
| 255 | + */ |
|---|
| 123 | 256 | public Object getObject() { |
|---|
| 124 | 257 | return object; |
|---|
| 125 | 258 | } |
|---|
| 126 | 259 | |
|---|
| 260 | + /** |
|---|
| 261 | + * getObject<p> |
|---|
| 262 | + * Return payload cast to the requested type. Cast is localized here. |
|---|
| 263 | + * |
|---|
| 264 | + * @param type requested type |
|---|
| 265 | + * @param <T> generic type |
|---|
| 266 | + * @return typed payload |
|---|
| 267 | + */ |
|---|
| 127 | 268 | @SuppressWarnings("unchecked") |
|---|
| 128 | 269 | public <T> T getObject(Class<T> type) { |
|---|
| 129 | 270 | return (T) object; |
|---|
| 130 | 271 | } |
|---|
| 131 | | - |
|---|
| 132 | 272 | } |
|---|
| 133 | | - |
|---|
| 134 | 273 | } |
|---|
| 274 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.utils; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.IOException; |
|---|
| .. | .. |
|---|
| 11 | 14 | import org.apache.logging.log4j.Logger; |
|---|
| 12 | 15 | |
|---|
| 13 | 16 | /** |
|---|
| 14 | | - * Class that loads and serves global config parameters. |
|---|
| 17 | + * Config |
|---|
| 18 | + * <p> |
|---|
| 19 | + * Class that loads and serves global config parameters from a classpath properties file |
|---|
| 20 | + * and, as a fallback, from environment variables. |
|---|
| 21 | + * |
|---|
| 22 | + * Initialization: |
|---|
| 23 | + * - Static initializer loads {@link #KEY_CONFIG_FILE} from classpath (fails hard if missing). |
|---|
| 24 | + * |
|---|
| 25 | + * Accessors: |
|---|
| 26 | + * - {@link #get(String)} / {@link #get(String, String)} |
|---|
| 27 | + * - Integer variants: {@link #getInt(String)} / {@link #getInt(String, int)} |
|---|
| 28 | + * - Namespaced helpers: by prefix/domain, and sequential lists via {@link #getListByPrefix(String)}. |
|---|
| 29 | + * |
|---|
| 30 | + * Thread-safety: static-only utility; internal state is read-only after init. |
|---|
| 15 | 31 | * |
|---|
| 16 | | - * @author rsanchez |
|---|
| 32 | + * @author JRA |
|---|
| 33 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 17 | 34 | */ |
|---|
| 18 | 35 | public class Config { |
|---|
| 19 | 36 | |
|---|
| 20 | 37 | private static final Logger LOG = LogManager.getLogger(Config.class); |
|---|
| 21 | 38 | |
|---|
| 22 | 39 | /** |
|---|
| 23 | | - * Key used to store config file resource location. In a web application, |
|---|
| 24 | | - * can be set as initial parameter in a servlet loaded on startup |
|---|
| 40 | + * Resource path of the application properties file (in classpath). |
|---|
| 41 | + * E.g. "/securis-server.properties". |
|---|
| 25 | 42 | */ |
|---|
| 26 | 43 | public static final String KEY_CONFIG_FILE = "/securis-server.properties"; |
|---|
| 27 | 44 | |
|---|
| .. | .. |
|---|
| 37 | 54 | } |
|---|
| 38 | 55 | |
|---|
| 39 | 56 | /** |
|---|
| 40 | | - * Loads application global parameters from a classpath resource |
|---|
| 41 | | - * |
|---|
| 42 | | - * @param resource |
|---|
| 43 | | - * : Resource location in classpath, i.e: |
|---|
| 44 | | - * "/resource/cp-securis.conf" |
|---|
| 45 | | - * @throws IOException |
|---|
| 57 | + * loadParameters |
|---|
| 58 | + * <p> |
|---|
| 59 | + * Loads application global parameters from a classpath resource. |
|---|
| 60 | + * |
|---|
| 61 | + * @param resource Classpath location (e.g. "/resource/cp-securis.conf"). |
|---|
| 62 | + * @throws IOException If the resource cannot be found or read. |
|---|
| 46 | 63 | */ |
|---|
| 47 | 64 | public static void loadParameters(String resource) throws IOException { |
|---|
| 48 | 65 | |
|---|
| .. | .. |
|---|
| 51 | 68 | |
|---|
| 52 | 69 | params = new Properties(); |
|---|
| 53 | 70 | try { |
|---|
| 54 | | - |
|---|
| 55 | 71 | params.load(fileis); |
|---|
| 56 | 72 | LOG.debug("Params loaded OK from {}", resource); |
|---|
| 57 | 73 | } catch (IOException e) { |
|---|
| .. | .. |
|---|
| 59 | 75 | params = null; |
|---|
| 60 | 76 | throw e; |
|---|
| 61 | 77 | } |
|---|
| 62 | | - |
|---|
| 63 | 78 | } |
|---|
| 64 | 79 | |
|---|
| 80 | + /** |
|---|
| 81 | + * getByDomain |
|---|
| 82 | + * <p> |
|---|
| 83 | + * Convenience accessor for domain-suffixed parameters (param.domain). |
|---|
| 84 | + * |
|---|
| 85 | + * @param domain Domain suffix. |
|---|
| 86 | + * @param paramname Base parameter name. |
|---|
| 87 | + * @return String value or null if not present. |
|---|
| 88 | + */ |
|---|
| 65 | 89 | public static String getByDomain(String domain, String paramname) { |
|---|
| 66 | 90 | return getByDomain(domain, paramname, null); |
|---|
| 67 | 91 | } |
|---|
| 68 | 92 | |
|---|
| 93 | + /** |
|---|
| 94 | + * getByPrefix |
|---|
| 95 | + * <p> |
|---|
| 96 | + * Returns parameter value from "{prefix}.{param}" or falls back to the plain "{param}". |
|---|
| 97 | + * |
|---|
| 98 | + * @param prefix Namespace prefix. |
|---|
| 99 | + * @param paramname Parameter name. |
|---|
| 100 | + * @return Resolved value or null. |
|---|
| 101 | + */ |
|---|
| 69 | 102 | public static String getByPrefix(String prefix, String paramname) { |
|---|
| 70 | 103 | return get(prefix + "." + paramname, get(paramname)); |
|---|
| 71 | 104 | } |
|---|
| 72 | 105 | |
|---|
| 106 | + /** |
|---|
| 107 | + * getByPrefix |
|---|
| 108 | + * <p> |
|---|
| 109 | + * Returns parameter value from "{prefix}.{param}" or provided default (which itself |
|---|
| 110 | + * falls back to "{param}" or its default). |
|---|
| 111 | + * |
|---|
| 112 | + * @param prefix Namespace prefix. |
|---|
| 113 | + * @param paramname Parameter name. |
|---|
| 114 | + * @param defaultVal Default value if none resolved. |
|---|
| 115 | + * @return Resolved value. |
|---|
| 116 | + */ |
|---|
| 73 | 117 | public static String getByPrefix(String prefix, String paramname, String defaultVal) { |
|---|
| 74 | 118 | return get(prefix + "." + paramname, get(paramname, defaultVal)); |
|---|
| 75 | 119 | } |
|---|
| 76 | 120 | |
|---|
| 121 | + /** |
|---|
| 122 | + * getByDomain |
|---|
| 123 | + * <p> |
|---|
| 124 | + * Returns value from "{param}.{domain}" or provided default. |
|---|
| 125 | + * |
|---|
| 126 | + * @param domain domain suffix. |
|---|
| 127 | + * @param paramname base name. |
|---|
| 128 | + * @param defaultval fallback if not found. |
|---|
| 129 | + * @return resolved string. |
|---|
| 130 | + */ |
|---|
| 77 | 131 | public static String getByDomain(String domain, String paramname, String defaultval) { |
|---|
| 78 | 132 | return get(paramname + "." + domain, defaultval); |
|---|
| 79 | 133 | } |
|---|
| 80 | 134 | |
|---|
| 135 | + /** |
|---|
| 136 | + * getIntByDomain |
|---|
| 137 | + * <p> |
|---|
| 138 | + * Integer variant of {@link #getByDomain(String, String)} with fallback to plain param. |
|---|
| 139 | + */ |
|---|
| 81 | 140 | public static int getIntByDomain(String domain, String paramname) { |
|---|
| 82 | 141 | return getInt(paramname + "." + domain, getInt(paramname)); |
|---|
| 83 | 142 | } |
|---|
| 84 | 143 | |
|---|
| 144 | + /** |
|---|
| 145 | + * getIntByDomain |
|---|
| 146 | + * <p> |
|---|
| 147 | + * Integer variant returning provided default when missing. |
|---|
| 148 | + */ |
|---|
| 85 | 149 | public static int getIntByDomain(String domain, String paramname, int defaultval) { |
|---|
| 86 | 150 | return getInt(paramname + "." + domain, defaultval); |
|---|
| 87 | 151 | } |
|---|
| 88 | 152 | |
|---|
| 89 | 153 | /** |
|---|
| 90 | | - * Gets a List with all values of properties that begins with |
|---|
| 91 | | - * <code>prefix</code> It reads sequentially. For example: |
|---|
| 92 | | - * |
|---|
| 93 | | - * <pre> |
|---|
| 94 | | - * securis.sort.comparator.0: net.cp.securis.comparators.ComparePttidVsPtn |
|---|
| 95 | | - * securis.sort.comparator.1: net.cp.securis.comparators.CompareFrequency |
|---|
| 96 | | - * securis.sort.comparator.2: net.cp.securis.comparators.CompareOutgoingVsIncomming |
|---|
| 97 | | - * securis.sort.comparator.3: net.cp.securis.comparators.CompareDuration |
|---|
| 98 | | - * securis.sort.comparator.4: net.cp.securis.comparators.CompareCallVsSms |
|---|
| 99 | | - * </pre> |
|---|
| 100 | | - * |
|---|
| 101 | | - * That config (for prefix: "securis.sort.comparator" ) will return a |
|---|
| 102 | | - * List<String> with values: |
|---|
| 103 | | - * |
|---|
| 104 | | - * <pre> |
|---|
| 105 | | - * "net.cp.securis.comparators.ComparePttidVsPtn", |
|---|
| 106 | | - * "net.cp.securis.comparators.CompareFrequency", |
|---|
| 107 | | - * "net.cp.securis.comparators.CompareOutgoingVsIncomming", |
|---|
| 108 | | - * "net.cp.securis.comparators.CompareDuration", |
|---|
| 109 | | - * "net.cp.securis.comparators.CompareCallVsSms" |
|---|
| 110 | | - * </pre> |
|---|
| 111 | | - * |
|---|
| 112 | | - * Note: If there is a gap between suffixes process will stop, that is, only |
|---|
| 113 | | - * will be returned properties found before gap. |
|---|
| 114 | | - * |
|---|
| 115 | | - * @param prefix |
|---|
| 116 | | - * @return |
|---|
| 154 | + * getListByPrefix |
|---|
| 155 | + * <p> |
|---|
| 156 | + * Reads sequential properties using numeric suffixes starting from 0 and stops on first gap. |
|---|
| 157 | + * Example: |
|---|
| 158 | + * securis.sort.comparator.0=... |
|---|
| 159 | + * securis.sort.comparator.1=... |
|---|
| 160 | + * ... |
|---|
| 161 | + * |
|---|
| 162 | + * @param prefix Base prefix (e.g. "securis.sort.comparator"). |
|---|
| 163 | + * @return Ordered list of values until first missing index. |
|---|
| 117 | 164 | */ |
|---|
| 118 | 165 | public static List<String> getListByPrefix(String prefix) { |
|---|
| 119 | 166 | List<String> list = new ArrayList<String>(); |
|---|
| 120 | | - |
|---|
| 121 | 167 | String tpl = prefix + ".{0}"; |
|---|
| 122 | | - |
|---|
| 123 | 168 | int i = 0; |
|---|
| 124 | 169 | String value = get(MessageFormat.format(tpl, i++)); |
|---|
| 125 | 170 | while (value != null) { |
|---|
| 126 | 171 | list.add(value); |
|---|
| 127 | 172 | value = get(MessageFormat.format(tpl, i++)); |
|---|
| 128 | 173 | } |
|---|
| 129 | | - |
|---|
| 130 | 174 | return list; |
|---|
| 131 | 175 | } |
|---|
| 132 | 176 | |
|---|
| 133 | 177 | /** |
|---|
| 134 | | - * Gets param value in config file or environment variables |
|---|
| 135 | | - * |
|---|
| 136 | | - * @param paramname |
|---|
| 137 | | - * Global parameter's name |
|---|
| 138 | | - * @return Value of paramname or null if paramname is not found neither in |
|---|
| 139 | | - * config file nor in environment variables |
|---|
| 178 | + * get |
|---|
| 179 | + * <p> |
|---|
| 180 | + * Get a parameter value from the loaded properties or environment variables. |
|---|
| 181 | + * |
|---|
| 182 | + * @param paramname Parameter key. |
|---|
| 183 | + * @return Value or null if not found anywhere. |
|---|
| 140 | 184 | */ |
|---|
| 141 | 185 | public static String get(String paramname) { |
|---|
| 142 | 186 | |
|---|
| .. | .. |
|---|
| 149 | 193 | } |
|---|
| 150 | 194 | |
|---|
| 151 | 195 | /** |
|---|
| 152 | | - * Gets param value from config file or environment variables |
|---|
| 153 | | - * |
|---|
| 154 | | - * @param paramname |
|---|
| 155 | | - * Global parameter's name |
|---|
| 156 | | - * @param defaultval |
|---|
| 157 | | - * @return Value of paramname or defaultval if paramname is not found |
|---|
| 196 | + * get |
|---|
| 197 | + * <p> |
|---|
| 198 | + * Returns parameter value or default if missing. |
|---|
| 199 | + * |
|---|
| 200 | + * @param paramname Key. |
|---|
| 201 | + * @param defaultval Default fallback. |
|---|
| 202 | + * @return value or default. |
|---|
| 158 | 203 | */ |
|---|
| 159 | 204 | public static String get(String paramname, String defaultval) { |
|---|
| 160 | 205 | String value = get(paramname); |
|---|
| .. | .. |
|---|
| 162 | 207 | } |
|---|
| 163 | 208 | |
|---|
| 164 | 209 | /** |
|---|
| 165 | | - * Gets param value in config file or environment variables |
|---|
| 166 | | - * |
|---|
| 167 | | - * @param paramname |
|---|
| 168 | | - * Global parameter's name |
|---|
| 169 | | - * @return Integer value of paramname or -1 if paramname is not found |
|---|
| 170 | | - * neither in config file nor in environment variables |
|---|
| 210 | + * getInt |
|---|
| 211 | + * <p> |
|---|
| 212 | + * Integer accessor, returns -1 if missing. |
|---|
| 213 | + * |
|---|
| 214 | + * @param paramname Key. |
|---|
| 215 | + * @return Parsed integer or -1. |
|---|
| 171 | 216 | */ |
|---|
| 172 | 217 | public static int getInt(String paramname) { |
|---|
| 173 | 218 | String value = get(paramname); |
|---|
| .. | .. |
|---|
| 175 | 220 | } |
|---|
| 176 | 221 | |
|---|
| 177 | 222 | /** |
|---|
| 178 | | - * Gets param value from config file or environment variables |
|---|
| 179 | | - * |
|---|
| 180 | | - * @param paramname |
|---|
| 181 | | - * Global parameter's name |
|---|
| 182 | | - * @param defaultval |
|---|
| 183 | | - * @return Integer value of paramname or defaultval if paramname is not |
|---|
| 184 | | - * found |
|---|
| 223 | + * getInt |
|---|
| 224 | + * <p> |
|---|
| 225 | + * Integer accessor, returns provided default when missing. |
|---|
| 226 | + * |
|---|
| 227 | + * @param paramname Key. |
|---|
| 228 | + * @param defaultval Default fallback. |
|---|
| 229 | + * @return Parsed integer or default. |
|---|
| 185 | 230 | */ |
|---|
| 186 | 231 | public static int getInt(String paramname, int defaultval) { |
|---|
| 187 | 232 | String value = get(paramname); |
|---|
| 188 | 233 | return (value == null ? defaultval : Integer.parseInt(value)); |
|---|
| 189 | 234 | } |
|---|
| 190 | 235 | |
|---|
| 236 | + /** |
|---|
| 237 | + * KEYS |
|---|
| 238 | + * <p> |
|---|
| 239 | + * Strongly-typed keys used across the application. |
|---|
| 240 | + */ |
|---|
| 191 | 241 | public static class KEYS { |
|---|
| 192 | 242 | |
|---|
| 193 | 243 | public static final String SERVER_HOSTNAME = "license.server.hostname"; |
|---|
| .. | .. |
|---|
| 205 | 255 | public static final String EMAIL_FROM_ADDRESS = "email.from.address"; |
|---|
| 206 | 256 | public static final String EMAIL_LIC_DEFAULT_SUBJECT = "email.lic.default.subject"; |
|---|
| 207 | 257 | } |
|---|
| 208 | | - |
|---|
| 209 | 258 | } |
|---|
| 259 | + |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.utils; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.File; |
|---|
| .. | .. |
|---|
| 40 | 43 | import org.apache.logging.log4j.Logger; |
|---|
| 41 | 44 | |
|---|
| 42 | 45 | /** |
|---|
| 43 | | - * Component that send emails using Mailgun API: |
|---|
| 44 | | - * http://documentation.mailgun.com/user_manual.html#sending-messages |
|---|
| 45 | | - * |
|---|
| 46 | | - * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 46 | + * EmailManager |
|---|
| 47 | + * <p> |
|---|
| 48 | + * Small utility to send plain-text emails (optionally with one attachment) |
|---|
| 49 | + * using the <b>Mailgun</b> API over HTTPS. |
|---|
| 50 | + * <p> |
|---|
| 51 | + * Design notes: |
|---|
| 52 | + * <ul> |
|---|
| 53 | + * <li>Reads Mailgun credentials and the "from" address from {@link Config}.</li> |
|---|
| 54 | + * <li>Builds a preconfigured {@link HttpClientBuilder} with basic auth and a permissive SSL socket factory.</li> |
|---|
| 55 | + * <li>Exposes synchronous (blocking) and asynchronous (non-blocking) send methods.</li> |
|---|
| 56 | + * <li>Scope is {@code @ApplicationScoped}; the underlying builder is created once per container.</li> |
|---|
| 57 | + * </ul> |
|---|
| 58 | + * |
|---|
| 59 | + * Thread-safety: |
|---|
| 60 | + * <p> |
|---|
| 61 | + * The class is effectively stateless after construction; using a shared {@link HttpClientBuilder} |
|---|
| 62 | + * is safe as a new {@link HttpClient} is built per request. |
|---|
| 63 | + * |
|---|
| 64 | + * Configuration keys (see {@link Config.KEYS}): |
|---|
| 65 | + * <ul> |
|---|
| 66 | + * <li>{@code mailgun.domain}</li> |
|---|
| 67 | + * <li>{@code mailgun.api.key}</li> |
|---|
| 68 | + * <li>{@code email.from.address}</li> |
|---|
| 69 | + * </ul> |
|---|
| 70 | + * |
|---|
| 71 | + * Failure handling: |
|---|
| 72 | + * <p> |
|---|
| 73 | + * Network and HTTP errors are surfaced as {@link SeCurisServiceException} with appropriate error codes. |
|---|
| 74 | + * |
|---|
| 75 | + * @author roberto <roberto.sanchez@curisit.net> |
|---|
| 76 | + * Last reviewed by JRA on Oct 6, 2025. |
|---|
| 47 | 77 | */ |
|---|
| 48 | 78 | @ApplicationScoped |
|---|
| 49 | 79 | public class EmailManager { |
|---|
| 50 | 80 | |
|---|
| 81 | + /** Class logger. */ |
|---|
| 51 | 82 | private static final Logger LOG = LogManager.getLogger(EmailManager.class); |
|---|
| 52 | 83 | |
|---|
| 84 | + /** Mailgun endpoint composed from configured domain. */ |
|---|
| 53 | 85 | private final String serverUrl; |
|---|
| 86 | + |
|---|
| 87 | + /** Preconfigured builder that carries SSL and basic-auth configuration. */ |
|---|
| 54 | 88 | private final HttpClientBuilder httpClientBuilder; |
|---|
| 55 | 89 | |
|---|
| 90 | + // --------------------------------------------------------------------- |
|---|
| 91 | + // Constructors |
|---|
| 92 | + // --------------------------------------------------------------------- |
|---|
| 93 | + |
|---|
| 56 | 94 | /** |
|---|
| 57 | | - * |
|---|
| 58 | | - * @throws SeCurisException |
|---|
| 95 | + * EmailManager |
|---|
| 96 | + * <p> |
|---|
| 97 | + * Default constructor that validates required configuration and prepares an |
|---|
| 98 | + * HTTP client builder with Mailgun credentials and SSL settings. |
|---|
| 99 | + * |
|---|
| 100 | + * @throws SeCurisException if mandatory configuration is missing or the SSL builder cannot be created |
|---|
| 59 | 101 | */ |
|---|
| 60 | 102 | public EmailManager() throws SeCurisException { |
|---|
| 61 | 103 | String domain = Config.get(Config.KEYS.MAILGUN_DOMAIN); |
|---|
| .. | .. |
|---|
| 64 | 106 | } |
|---|
| 65 | 107 | serverUrl = String.format("https://api.mailgun.net/v2/%s/messages", domain); |
|---|
| 66 | 108 | httpClientBuilder = createHttpClient(); |
|---|
| 67 | | - |
|---|
| 68 | 109 | } |
|---|
| 69 | 110 | |
|---|
| 111 | + // --------------------------------------------------------------------- |
|---|
| 112 | + // Internal helpers |
|---|
| 113 | + // --------------------------------------------------------------------- |
|---|
| 114 | + |
|---|
| 115 | + /** |
|---|
| 116 | + * createHttpClient |
|---|
| 117 | + * <p> |
|---|
| 118 | + * Builds a {@link HttpClientBuilder} that: |
|---|
| 119 | + * <ul> |
|---|
| 120 | + * <li>Accepts any server certificate (permissive trust strategy).</li> |
|---|
| 121 | + * <li>Applies HTTP Basic Auth using Mailgun's API key as the password and user "api".</li> |
|---|
| 122 | + * </ul> |
|---|
| 123 | + * |
|---|
| 124 | + * @return a preconfigured {@link HttpClientBuilder} ready to build clients |
|---|
| 125 | + * @throws SeCurisException if SSL initialization fails |
|---|
| 126 | + */ |
|---|
| 70 | 127 | private HttpClientBuilder createHttpClient() throws SeCurisException { |
|---|
| 71 | 128 | SSLContextBuilder builder = new SSLContextBuilder(); |
|---|
| 72 | 129 | SSLConnectionSocketFactory sslsf = null; |
|---|
| 73 | 130 | try { |
|---|
| 131 | + // Trust all X509 certificates (relies on HTTPS + Basic Auth; consider hardening in production). |
|---|
| 74 | 132 | builder.loadTrustMaterial((KeyStore) null, new TrustStrategy() { |
|---|
| 75 | 133 | @Override |
|---|
| 76 | 134 | public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { |
|---|
| .. | .. |
|---|
| 82 | 140 | LOG.error(e1); |
|---|
| 83 | 141 | throw new SeCurisException("Error creating SSL socket factory"); |
|---|
| 84 | 142 | } |
|---|
| 143 | + |
|---|
| 144 | + // Configure Basic Auth with Mailgun API key |
|---|
| 85 | 145 | CredentialsProvider provider = new BasicCredentialsProvider(); |
|---|
| 86 | | - UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("api", Config.get(Config.KEYS.MAILGUN_API_KEY)); |
|---|
| 146 | + UsernamePasswordCredentials credentials = |
|---|
| 147 | + new UsernamePasswordCredentials("api", Config.get(Config.KEYS.MAILGUN_API_KEY)); |
|---|
| 87 | 148 | provider.setCredentials(AuthScope.ANY, credentials); |
|---|
| 88 | 149 | |
|---|
| 89 | | - return HttpClientBuilder.create().setDefaultCredentialsProvider(provider).setSSLSocketFactory(sslsf); |
|---|
| 150 | + return HttpClientBuilder.create() |
|---|
| 151 | + .setDefaultCredentialsProvider(provider) |
|---|
| 152 | + .setSSLSocketFactory(sslsf); |
|---|
| 90 | 153 | } |
|---|
| 91 | 154 | |
|---|
| 155 | + // --------------------------------------------------------------------- |
|---|
| 156 | + // Email sending API |
|---|
| 157 | + // --------------------------------------------------------------------- |
|---|
| 158 | + |
|---|
| 92 | 159 | /** |
|---|
| 93 | | - * Basic method to send emails in text mode with attachment. The method is |
|---|
| 94 | | - * synchronous, It waits until server responses. |
|---|
| 95 | | - * |
|---|
| 96 | | - * @param subject |
|---|
| 97 | | - * @param body |
|---|
| 98 | | - * @param to |
|---|
| 99 | | - * @param file |
|---|
| 100 | | - * @throws SeCurisException |
|---|
| 101 | | - * @throws UnsupportedEncodingException |
|---|
| 160 | + * sendEmail |
|---|
| 161 | + * <p> |
|---|
| 162 | + * Sends a plain-text email (UTF-8) via Mailgun. Optionally attaches a single file. |
|---|
| 163 | + * This call is <b>synchronous</b> (blocking) and only returns once the HTTP response is received. |
|---|
| 164 | + * |
|---|
| 165 | + * @param subject Email subject (will be sent as UTF-8). |
|---|
| 166 | + * @param body Email body in plain text (UTF-8). |
|---|
| 167 | + * @param to Recipient address (required). |
|---|
| 168 | + * @param cc Optional CC address, may be {@code null}. |
|---|
| 169 | + * @param file Optional file to attach, may be {@code null}. |
|---|
| 170 | + * |
|---|
| 171 | + * @throws SeCurisServiceException if the HTTP call fails or Mailgun responds with a non-200 status |
|---|
| 172 | + * @throws UnsupportedEncodingException kept for API compatibility (body/subject are forced to UTF-8) |
|---|
| 102 | 173 | */ |
|---|
| 103 | 174 | @SuppressWarnings("deprecation") |
|---|
| 104 | | - public void sendEmail(String subject, String body, String to, String cc, File file) throws SeCurisServiceException, UnsupportedEncodingException { |
|---|
| 175 | + public void sendEmail(String subject, String body, String to, String cc, File file) |
|---|
| 176 | + throws SeCurisServiceException, UnsupportedEncodingException { |
|---|
| 177 | + |
|---|
| 105 | 178 | HttpPost postRequest = new HttpPost(serverUrl); |
|---|
| 106 | 179 | |
|---|
| 180 | + // Build multipart form body compatible with Mailgun |
|---|
| 107 | 181 | MultipartEntityBuilder builder = MultipartEntityBuilder.create(); |
|---|
| 108 | | - |
|---|
| 109 | 182 | builder.setCharset(Charset.forName("utf-8")); |
|---|
| 110 | 183 | builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); |
|---|
| 111 | 184 | builder.addTextBody("from", Config.get(Config.KEYS.EMAIL_FROM_ADDRESS)); |
|---|
| .. | .. |
|---|
| 113 | 186 | if (cc != null) { |
|---|
| 114 | 187 | builder.addTextBody("cc", cc); |
|---|
| 115 | 188 | } |
|---|
| 116 | | - builder.addTextBody("subject", subject, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8"))); |
|---|
| 117 | | - builder.addTextBody("text", body, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8"))); |
|---|
| 118 | | - if (file != null) { |
|---|
| 189 | + builder.addTextBody("subject", |
|---|
| 190 | + subject, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8"))); |
|---|
| 191 | + builder.addTextBody("text", |
|---|
| 192 | + body, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8"))); |
|---|
| 119 | 193 | |
|---|
| 194 | + if (file != null) { |
|---|
| 120 | 195 | LOG.info("File to attach: {}", file.getAbsoluteFile()); |
|---|
| 121 | 196 | builder.addPart("attachment", new FileBody(file)); |
|---|
| 122 | 197 | } |
|---|
| 123 | 198 | |
|---|
| 124 | 199 | postRequest.setEntity(builder.build()); |
|---|
| 200 | + |
|---|
| 201 | + // Execute HTTP request |
|---|
| 125 | 202 | HttpResponse response; |
|---|
| 126 | 203 | HttpClient httpClient = httpClientBuilder.build(); |
|---|
| 127 | 204 | try { |
|---|
| 128 | 205 | response = httpClient.execute(postRequest); |
|---|
| 129 | 206 | |
|---|
| 207 | + // Mailgun returns JSON. We parse it to a Map for logging/validation. |
|---|
| 130 | 208 | String jsonLic = IOUtils.toString(response.getEntity().getContent()); |
|---|
| 131 | 209 | if (response.getStatusLine().getStatusCode() == 200) { |
|---|
| 132 | 210 | LOG.debug("Response content read OK: {}", jsonLic); |
|---|
| 133 | 211 | Map<String, Object> responseBean = JsonUtils.json2map(jsonLic); |
|---|
| 134 | | - |
|---|
| 135 | 212 | LOG.debug("Response mail read OK: {}", responseBean); |
|---|
| 136 | 213 | } else { |
|---|
| 137 | | - throw new SeCurisServiceException(ErrorCodes.UNEXPECTED_ERROR, "Error sending email, response estatus: " + response.getStatusLine()); |
|---|
| 214 | + throw new SeCurisServiceException( |
|---|
| 215 | + ErrorCodes.UNEXPECTED_ERROR, |
|---|
| 216 | + "Error sending email, response estatus: " + response.getStatusLine()); |
|---|
| 138 | 217 | } |
|---|
| 139 | 218 | } catch (IOException e) { |
|---|
| 140 | 219 | LOG.error("Error sending email", e); |
|---|
| .. | .. |
|---|
| 143 | 222 | } |
|---|
| 144 | 223 | |
|---|
| 145 | 224 | /** |
|---|
| 146 | | - * Basic method to send emails in text mode with attachment. The method is |
|---|
| 147 | | - * asynchronous, It returns immediately |
|---|
| 148 | | - * |
|---|
| 149 | | - * @param subject |
|---|
| 150 | | - * @param body |
|---|
| 151 | | - * @param to |
|---|
| 152 | | - * @param file |
|---|
| 153 | | - * @throws SeCurisException |
|---|
| 154 | | - * @throws UnsupportedEncodingException |
|---|
| 225 | + * sendEmailAsync |
|---|
| 226 | + * <p> |
|---|
| 227 | + * Asynchronous variant of {@link #sendEmail(String, String, String, String, File)}. |
|---|
| 228 | + * The call returns immediately and performs the HTTP request in a single-thread executor. |
|---|
| 229 | + * |
|---|
| 230 | + * @param subject Email subject (UTF-8). |
|---|
| 231 | + * @param body Email body in plain text (UTF-8). |
|---|
| 232 | + * @param to Recipient address. |
|---|
| 233 | + * @param cc Optional CC address, may be {@code null}. |
|---|
| 234 | + * @param file Optional attachment, may be {@code null}. |
|---|
| 235 | + * @param callback Non-null callback to be notified on success or failure. |
|---|
| 236 | + * |
|---|
| 237 | + * @throws SeCurisException if there is a configuration or environment problem before dispatch |
|---|
| 238 | + * @throws UnsupportedEncodingException for API compatibility (subject/body are encoded as UTF-8) |
|---|
| 155 | 239 | */ |
|---|
| 156 | | - public void sendEmailAsync(String subject, String body, String to, String cc, File file, EmailCallback callback) throws SeCurisException, |
|---|
| 157 | | - UnsupportedEncodingException { |
|---|
| 240 | + public void sendEmailAsync( |
|---|
| 241 | + String subject, String body, String to, String cc, File file, EmailCallback callback) |
|---|
| 242 | + throws SeCurisException, UnsupportedEncodingException { |
|---|
| 243 | + |
|---|
| 158 | 244 | Executor ex = Executors.newSingleThreadExecutor(); |
|---|
| 159 | 245 | ex.execute(new Runnable() { |
|---|
| 160 | | - |
|---|
| 161 | 246 | @Override |
|---|
| 162 | 247 | public void run() { |
|---|
| 163 | 248 | try { |
|---|
| .. | .. |
|---|
| 168 | 253 | } catch (SeCurisServiceException e) { |
|---|
| 169 | 254 | callback.error(e); |
|---|
| 170 | 255 | } |
|---|
| 171 | | - |
|---|
| 172 | 256 | } |
|---|
| 173 | 257 | }); |
|---|
| 174 | | - |
|---|
| 175 | 258 | } |
|---|
| 176 | 259 | |
|---|
| 260 | + // --------------------------------------------------------------------- |
|---|
| 261 | + // Callback contract |
|---|
| 262 | + // --------------------------------------------------------------------- |
|---|
| 263 | + |
|---|
| 264 | + /** |
|---|
| 265 | + * EmailCallback |
|---|
| 266 | + * <p> |
|---|
| 267 | + * Functional contract to be notified when an async send finishes. |
|---|
| 268 | + */ |
|---|
| 177 | 269 | public static interface EmailCallback { |
|---|
| 270 | + /** |
|---|
| 271 | + * success<p> |
|---|
| 272 | + * Called when the email was sent and Mailgun returned HTTP 200. |
|---|
| 273 | + */ |
|---|
| 178 | 274 | public void success(); |
|---|
| 179 | 275 | |
|---|
| 276 | + /** |
|---|
| 277 | + * error<p> |
|---|
| 278 | + * Called when there was a problem sending the email. |
|---|
| 279 | + * |
|---|
| 280 | + * @param e encapsulates the reason of failure |
|---|
| 281 | + */ |
|---|
| 180 | 282 | public void error(SeCurisServiceException e); |
|---|
| 181 | 283 | } |
|---|
| 182 | 284 | |
|---|
| 285 | + // --------------------------------------------------------------------- |
|---|
| 286 | + // Manual test harness |
|---|
| 287 | + // --------------------------------------------------------------------- |
|---|
| 288 | + |
|---|
| 289 | + /** |
|---|
| 290 | + * main<p> |
|---|
| 291 | + * Simple manual test for the async email flow. Adjust addresses and file path before use. |
|---|
| 292 | + * |
|---|
| 293 | + * @param args program arguments (unused) |
|---|
| 294 | + * @throws SeCurisException if configuration is invalid |
|---|
| 295 | + * @throws UnsupportedEncodingException if UTF-8 encoding fails (unlikely) |
|---|
| 296 | + */ |
|---|
| 183 | 297 | public static void main(String[] args) throws SeCurisException, UnsupportedEncodingException { |
|---|
| 184 | | - // new EmailManager().sendEmail("España así de bien", |
|---|
| 185 | | - // "Me gusta esta prueba\nCon varias líneas\n\n\n--\nNo response", |
|---|
| 186 | | - // "info@r75.es", new File( |
|---|
| 187 | | - // "/Users/rob/Downloads/test.req")); |
|---|
| 188 | | - new EmailManager().sendEmailAsync("España así de bien", "Me gusta esta prueba\nCon varias líneas\n\n\n--\nNo response", "info@r75.es", |
|---|
| 189 | | - "dev@r75.es", new File("/Users/rob/Downloads/test.req"), new EmailCallback() { |
|---|
| 298 | + // Example async call (subject/body contain non-ASCII content to validate UTF-8 handling). |
|---|
| 299 | + new EmailManager().sendEmailAsync("España así de bien", |
|---|
| 300 | + "Me gusta esta prueba\nCon varias líneas\n\n\n--\nNo response", |
|---|
| 301 | + "info@r75.es", |
|---|
| 302 | + "dev@r75.es", |
|---|
| 303 | + new File("/Users/rob/Downloads/test.req"), |
|---|
| 304 | + new EmailCallback() { |
|---|
| 305 | + @Override |
|---|
| 306 | + public void success() { LOG.info("Success!!!"); } |
|---|
| 190 | 307 | |
|---|
| 191 | 308 | @Override |
|---|
| 192 | | - public void success() { |
|---|
| 193 | | - LOG.info("Success!!!"); |
|---|
| 194 | | - } |
|---|
| 195 | | - |
|---|
| 196 | | - @Override |
|---|
| 197 | | - public void error(SeCurisServiceException e) { |
|---|
| 198 | | - LOG.error("Error: {} !!!", e); |
|---|
| 199 | | - } |
|---|
| 309 | + public void error(SeCurisServiceException e) { LOG.error("Error: {} !!!", e); } |
|---|
| 200 | 310 | }); |
|---|
| 201 | 311 | LOG.info("Waiting for email to be sent..."); |
|---|
| 202 | 312 | } |
|---|
| 203 | | - |
|---|
| 204 | 313 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.utils; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.IOException; |
|---|
| .. | .. |
|---|
| 10 | 13 | import jakarta.servlet.http.HttpServletResponse; |
|---|
| 11 | 14 | import jakarta.servlet.http.HttpServletResponseWrapper; |
|---|
| 12 | 15 | |
|---|
| 16 | +/** |
|---|
| 17 | + * GZipServletResponseWrapper |
|---|
| 18 | + * <p> |
|---|
| 19 | + * {@link HttpServletResponseWrapper} that transparently compresses the response |
|---|
| 20 | + * body using GZIP. Intended for use in filters/servlets where the caller wants |
|---|
| 21 | + * to wrap the original response and write compressed bytes to the client. |
|---|
| 22 | + * <p> |
|---|
| 23 | + * How it works: |
|---|
| 24 | + * <ul> |
|---|
| 25 | + * <li>Overrides {@link #getOutputStream()} and {@link #getWriter()} to route output through a {@link GZIPOutputStream}.</li> |
|---|
| 26 | + * <li>Ensures mutual exclusivity between OutputStream and Writer access per Servlet API requirements.</li> |
|---|
| 27 | + * <li>Ignores {@link #setContentLength(int)} since compressed size differs from uncompressed.</li> |
|---|
| 28 | + * </ul> |
|---|
| 29 | + * |
|---|
| 30 | + * Usage: |
|---|
| 31 | + * <pre> |
|---|
| 32 | + * GZipServletResponseWrapper gz = new GZipServletResponseWrapper(resp); |
|---|
| 33 | + * chain.doFilter(request, gz); |
|---|
| 34 | + * gz.close(); // important: finish compression and flush buffers |
|---|
| 35 | + * </pre> |
|---|
| 36 | + * |
|---|
| 37 | + * Thread-safety: |
|---|
| 38 | + * <p> |
|---|
| 39 | + * Instances are per-request and not shared. |
|---|
| 40 | + * |
|---|
| 41 | + * Limitations: |
|---|
| 42 | + * <p> |
|---|
| 43 | + * Caller is responsible for setting "Content-Encoding: gzip" and for avoiding |
|---|
| 44 | + * double-compression scenarios. |
|---|
| 45 | + * |
|---|
| 46 | + * @author JRA |
|---|
| 47 | + * Last reviewed by JRA on Oct 6, 2025. |
|---|
| 48 | + */ |
|---|
| 13 | 49 | public class GZipServletResponseWrapper extends HttpServletResponseWrapper { |
|---|
| 14 | 50 | |
|---|
| 15 | 51 | private GZIPServletOutputStream gzipOutputStream = null; |
|---|
| 16 | 52 | private PrintWriter printWriter = null; |
|---|
| 17 | 53 | |
|---|
| 54 | + // --------------------------------------------------------------------- |
|---|
| 55 | + // Constructors |
|---|
| 56 | + // --------------------------------------------------------------------- |
|---|
| 57 | + |
|---|
| 58 | + /** |
|---|
| 59 | + * GZipServletResponseWrapper |
|---|
| 60 | + * <p> |
|---|
| 61 | + * Wraps the given response. Actual GZIP streams are lazily created on first write. |
|---|
| 62 | + * |
|---|
| 63 | + * @param response the original {@link HttpServletResponse} to wrap |
|---|
| 64 | + * @throws IOException if the underlying response streams cannot be accessed |
|---|
| 65 | + */ |
|---|
| 18 | 66 | public GZipServletResponseWrapper(HttpServletResponse response) throws IOException { |
|---|
| 19 | 67 | super(response); |
|---|
| 20 | 68 | } |
|---|
| 21 | 69 | |
|---|
| 22 | | - public void close() throws IOException { |
|---|
| 70 | + // --------------------------------------------------------------------- |
|---|
| 71 | + // Lifecycle |
|---|
| 72 | + // --------------------------------------------------------------------- |
|---|
| 23 | 73 | |
|---|
| 24 | | - //PrintWriter.close does not throw exceptions. |
|---|
| 25 | | - //Hence no try-catch block. |
|---|
| 74 | + /** |
|---|
| 75 | + * close<p> |
|---|
| 76 | + * Closes any open writer or output stream and finalizes the GZIP stream. |
|---|
| 77 | + * Must be called once all response content has been written. |
|---|
| 78 | + * |
|---|
| 79 | + * @throws IOException if closing the underlying streams fails |
|---|
| 80 | + */ |
|---|
| 81 | + public void close() throws IOException { |
|---|
| 82 | + // PrintWriter.close does not throw exceptions. Hence no try-catch block. |
|---|
| 26 | 83 | if (this.printWriter != null) { |
|---|
| 27 | 84 | this.printWriter.close(); |
|---|
| 28 | 85 | } |
|---|
| 29 | | - |
|---|
| 30 | 86 | if (this.gzipOutputStream != null) { |
|---|
| 31 | 87 | this.gzipOutputStream.close(); |
|---|
| 32 | 88 | } |
|---|
| 33 | 89 | } |
|---|
| 34 | 90 | |
|---|
| 35 | 91 | /** |
|---|
| 36 | | - * Flush OutputStream or PrintWriter |
|---|
| 92 | + * flushBuffer<p> |
|---|
| 93 | + * Flushes the writer and the GZIP output stream, then delegates to the wrapped response. |
|---|
| 94 | + * If multiple exceptions occur, the first encountered is thrown (typical servlet practice). |
|---|
| 37 | 95 | * |
|---|
| 38 | | - * @throws IOException |
|---|
| 96 | + * @throws IOException if flushing any of the streams fails |
|---|
| 39 | 97 | */ |
|---|
| 40 | | - |
|---|
| 41 | 98 | @Override |
|---|
| 42 | 99 | public void flushBuffer() throws IOException { |
|---|
| 43 | 100 | |
|---|
| 44 | | - //PrintWriter.flush() does not throw exception |
|---|
| 101 | + // PrintWriter.flush() does not throw exception |
|---|
| 45 | 102 | if (this.printWriter != null) { |
|---|
| 46 | 103 | this.printWriter.flush(); |
|---|
| 47 | 104 | } |
|---|
| .. | .. |
|---|
| 62 | 119 | exception2 = e; |
|---|
| 63 | 120 | } |
|---|
| 64 | 121 | |
|---|
| 65 | | - if (exception1 != null) |
|---|
| 66 | | - throw exception1; |
|---|
| 67 | | - if (exception2 != null) |
|---|
| 68 | | - throw exception2; |
|---|
| 122 | + if (exception1 != null) throw exception1; |
|---|
| 123 | + if (exception2 != null) throw exception2; |
|---|
| 69 | 124 | } |
|---|
| 70 | 125 | |
|---|
| 126 | + // --------------------------------------------------------------------- |
|---|
| 127 | + // Output acquisition |
|---|
| 128 | + // --------------------------------------------------------------------- |
|---|
| 129 | + |
|---|
| 130 | + /** |
|---|
| 131 | + * getOutputStream<p> |
|---|
| 132 | + * Returns a {@link ServletOutputStream} that writes compressed data. |
|---|
| 133 | + * Mutually exclusive with {@link #getWriter()} as per Servlet API. |
|---|
| 134 | + * |
|---|
| 135 | + * @return compressed {@link ServletOutputStream} |
|---|
| 136 | + * @throws IOException if the underlying output stream cannot be obtained |
|---|
| 137 | + * @throws IllegalStateException if the writer has been already acquired |
|---|
| 138 | + */ |
|---|
| 71 | 139 | @Override |
|---|
| 72 | 140 | public ServletOutputStream getOutputStream() throws IOException { |
|---|
| 73 | 141 | if (this.printWriter != null) { |
|---|
| .. | .. |
|---|
| 79 | 147 | return this.gzipOutputStream; |
|---|
| 80 | 148 | } |
|---|
| 81 | 149 | |
|---|
| 150 | + /** |
|---|
| 151 | + * getWriter<p> |
|---|
| 152 | + * Returns a {@link PrintWriter} that writes compressed data (UTF-8 by default, inherited from response). |
|---|
| 153 | + * Mutually exclusive with {@link #getOutputStream()} as per Servlet API. |
|---|
| 154 | + * |
|---|
| 155 | + * @return compressed {@link PrintWriter} |
|---|
| 156 | + * @throws IOException if streams cannot be allocated |
|---|
| 157 | + * @throws IllegalStateException if the output stream has been already acquired |
|---|
| 158 | + */ |
|---|
| 82 | 159 | @Override |
|---|
| 83 | 160 | public PrintWriter getWriter() throws IOException { |
|---|
| 84 | 161 | if (this.printWriter == null && this.gzipOutputStream != null) { |
|---|
| .. | .. |
|---|
| 91 | 168 | return this.printWriter; |
|---|
| 92 | 169 | } |
|---|
| 93 | 170 | |
|---|
| 171 | + /** |
|---|
| 172 | + * setContentLength<p> |
|---|
| 173 | + * No-op. The content length of the zipped content is not known a priori and |
|---|
| 174 | + * will not match the uncompressed length; therefore we do not set it here. |
|---|
| 175 | + * |
|---|
| 176 | + * @param len ignored |
|---|
| 177 | + */ |
|---|
| 94 | 178 | @Override |
|---|
| 95 | 179 | public void setContentLength(int len) { |
|---|
| 96 | | - //ignore, since content length of zipped content |
|---|
| 97 | | - //does not match content length of unzipped content. |
|---|
| 180 | + // ignore, since content length of zipped content does not match content length of unzipped content. |
|---|
| 98 | 181 | } |
|---|
| 99 | 182 | |
|---|
| 183 | + // --------------------------------------------------------------------- |
|---|
| 184 | + // Inner compressed stream |
|---|
| 185 | + // --------------------------------------------------------------------- |
|---|
| 186 | + |
|---|
| 187 | + /** |
|---|
| 188 | + * GZIPServletOutputStream |
|---|
| 189 | + * <p> |
|---|
| 190 | + * Decorates the original {@link ServletOutputStream} with a {@link GZIPOutputStream}. |
|---|
| 191 | + * Delegates readiness and listener to the underlying (container) stream. |
|---|
| 192 | + * |
|---|
| 193 | + * @author JRA |
|---|
| 194 | + * Last reviewed by JRA on Oct 5, 2025. |
|---|
| 195 | + */ |
|---|
| 100 | 196 | private static class GZIPServletOutputStream extends ServletOutputStream { |
|---|
| 101 | 197 | private final ServletOutputStream servletOutputStream; |
|---|
| 102 | 198 | private final GZIPOutputStream gzipStream; |
|---|
| 103 | 199 | |
|---|
| 200 | + /** |
|---|
| 201 | + * GZIPServletOutputStream<p> |
|---|
| 202 | + * Creates a new compressed stream wrapper. |
|---|
| 203 | + * |
|---|
| 204 | + * @param servletOutputStream underlying (container-provided) output stream |
|---|
| 205 | + * @throws IOException if the GZIP stream cannot be created |
|---|
| 206 | + */ |
|---|
| 104 | 207 | public GZIPServletOutputStream(ServletOutputStream servletOutputStream) throws IOException { |
|---|
| 105 | 208 | this.servletOutputStream = servletOutputStream; |
|---|
| 106 | 209 | this.gzipStream = new GZIPOutputStream(servletOutputStream); |
|---|
| 107 | 210 | } |
|---|
| 108 | 211 | |
|---|
| 212 | + /** |
|---|
| 213 | + * isReady<p> |
|---|
| 214 | + * Check if the output stream is ready |
|---|
| 215 | + * {@inheritDoc} |
|---|
| 216 | + * |
|---|
| 217 | + * @return isReady |
|---|
| 218 | + */ |
|---|
| 109 | 219 | @Override |
|---|
| 110 | 220 | public boolean isReady() { |
|---|
| 111 | 221 | return this.servletOutputStream.isReady(); |
|---|
| 112 | 222 | } |
|---|
| 113 | 223 | |
|---|
| 224 | + /** |
|---|
| 225 | + * setWriteListener<p> |
|---|
| 226 | + * Set the write listener for the output stream |
|---|
| 227 | + * {@inheritDoc} |
|---|
| 228 | + * |
|---|
| 229 | + * @param writeListener |
|---|
| 230 | + */ |
|---|
| 114 | 231 | @Override |
|---|
| 115 | 232 | public void setWriteListener(WriteListener writeListener) { |
|---|
| 116 | 233 | this.servletOutputStream.setWriteListener(writeListener); |
|---|
| 117 | 234 | } |
|---|
| 118 | 235 | |
|---|
| 236 | + /** |
|---|
| 237 | + * write<p> |
|---|
| 238 | + * Write on the gzip stream |
|---|
| 239 | + * {@inheritDoc} |
|---|
| 240 | + * |
|---|
| 241 | + * @param b |
|---|
| 242 | + * @throws IOException |
|---|
| 243 | + */ |
|---|
| 119 | 244 | @Override |
|---|
| 120 | 245 | public void write(int b) throws IOException { |
|---|
| 121 | 246 | this.gzipStream.write(b); |
|---|
| 122 | 247 | } |
|---|
| 123 | 248 | |
|---|
| 249 | + /** |
|---|
| 250 | + * close<p> |
|---|
| 251 | + * Close the gzip stream |
|---|
| 252 | + * {@inheritDoc} |
|---|
| 253 | + * |
|---|
| 254 | + * @throws IOException |
|---|
| 255 | + */ |
|---|
| 124 | 256 | @Override |
|---|
| 125 | 257 | public void close() throws IOException { |
|---|
| 126 | 258 | this.gzipStream.close(); |
|---|
| 127 | 259 | } |
|---|
| 128 | 260 | |
|---|
| 261 | + /** |
|---|
| 262 | + * flush<p> |
|---|
| 263 | + * Flush the gzip stream |
|---|
| 264 | + * {@inheritDoc} |
|---|
| 265 | + * |
|---|
| 266 | + * @throws IOException |
|---|
| 267 | + */ |
|---|
| 129 | 268 | @Override |
|---|
| 130 | 269 | public void flush() throws IOException { |
|---|
| 131 | 270 | this.gzipStream.flush(); |
|---|
| 132 | 271 | } |
|---|
| 133 | 272 | |
|---|
| 273 | + /** |
|---|
| 274 | + * finish<p> |
|---|
| 275 | + * Explicitly finishes writing of the GZIP stream, without closing the underlying stream. |
|---|
| 276 | + * Not used by the wrapper but available for completeness. |
|---|
| 277 | + * |
|---|
| 278 | + * @throws IOException if finishing fails |
|---|
| 279 | + */ |
|---|
| 134 | 280 | @SuppressWarnings("unused") |
|---|
| 135 | 281 | public void finish() throws IOException { |
|---|
| 136 | 282 | this.gzipStream.finish(); |
|---|
| 137 | 283 | } |
|---|
| 138 | 284 | } |
|---|
| 139 | | - |
|---|
| 140 | 285 | } |
|---|
| .. | .. |
|---|
| 1 | +/* |
|---|
| 2 | + * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. |
|---|
| 3 | + */ |
|---|
| 1 | 4 | package net.curisit.securis.utils; |
|---|
| 2 | 5 | |
|---|
| 3 | 6 | import java.io.IOException; |
|---|
| .. | .. |
|---|
| 20 | 23 | import java.util.Base64; |
|---|
| 21 | 24 | import java.nio.charset.StandardCharsets; |
|---|
| 22 | 25 | |
|---|
| 26 | +/** |
|---|
| 27 | + * TokenHelper |
|---|
| 28 | + * <p> |
|---|
| 29 | + * Utility component to generate and validate short-lived authentication tokens |
|---|
| 30 | + * for SeCuris services. Tokens are: |
|---|
| 31 | + * </p> |
|---|
| 32 | + * <ul> |
|---|
| 33 | + * <li>Base64-encoded UTF-8 strings.</li> |
|---|
| 34 | + * <li>Composed as: {@code <secret> <username> <iso8601-timestamp>} (space-separated).</li> |
|---|
| 35 | + * <li>Where {@code secret} is a deterministic SHA-256 HMAC-like hash built from a static seed, |
|---|
| 36 | + * the username, and the issuance timestamp.</li> |
|---|
| 37 | + * </ul> |
|---|
| 38 | + * |
|---|
| 39 | + * <p><b>Lifecycle & scope:</b> {@code @ApplicationScoped}. Stateless and thread-safe.</p> |
|---|
| 40 | + * |
|---|
| 41 | + * <p><b>Security notes:</b> |
|---|
| 42 | + * The {@code seed} acts like a shared secret. Keep it private and rotate if compromised. |
|---|
| 43 | + * Tokens expire after {@link #VALID_TOKEN_PERIOD} hours except a special client token |
|---|
| 44 | + * defined by {@link ApiResource#API_CLIENT_USERNAME} issued at epoch-1 (see {@link #isTokenValid(String)}). |
|---|
| 45 | + * </p> |
|---|
| 46 | + * |
|---|
| 47 | + * <p><b>Format details:</b> |
|---|
| 48 | + * <pre> |
|---|
| 49 | + * token = Base64( secret + " " + user + " " + Utils.toIsoFormat(date) ) |
|---|
| 50 | + * secret = hex(SHA-256(seed || user || isoDate)) |
|---|
| 51 | + * </pre> |
|---|
| 52 | + * </p> |
|---|
| 53 | + * |
|---|
| 54 | + * @author JRA |
|---|
| 55 | + * Last reviewed by JRA on Oct 6, 2025. |
|---|
| 56 | + */ |
|---|
| 23 | 57 | @ApplicationScoped |
|---|
| 24 | 58 | public class TokenHelper { |
|---|
| 25 | 59 | |
|---|
| 26 | 60 | private static final Logger LOG = LogManager.getLogger(TokenHelper.class); |
|---|
| 27 | 61 | |
|---|
| 28 | 62 | /** |
|---|
| 29 | | - * Period before token expires, set in hours. |
|---|
| 63 | + * Validity window for standard tokens, in hours. |
|---|
| 64 | + * <p> |
|---|
| 65 | + * Any token with a creation date older than this window will be rejected |
|---|
| 66 | + * (unless it matches the special API client rule documented in |
|---|
| 67 | + * {@link #isTokenValid(String)}). |
|---|
| 68 | + * </p> |
|---|
| 30 | 69 | */ |
|---|
| 31 | 70 | private static int VALID_TOKEN_PERIOD = 24; |
|---|
| 71 | + |
|---|
| 72 | + /** Standard HTTP header used by SeCuris clients to carry the token. */ |
|---|
| 32 | 73 | public static final String TOKEN_HEADER_PÀRAM = "X-SECURIS-TOKEN"; |
|---|
| 33 | 74 | |
|---|
| 75 | + /** |
|---|
| 76 | + * TokenHelper<p> |
|---|
| 77 | + * CDI no-arg constructor. |
|---|
| 78 | + * <p> |
|---|
| 79 | + * Kept for dependency injection. No initialization logic is required. |
|---|
| 80 | + * </p> |
|---|
| 81 | + */ |
|---|
| 34 | 82 | @Inject |
|---|
| 35 | 83 | public TokenHelper() { |
|---|
| 36 | 84 | } |
|---|
| 37 | 85 | |
|---|
| 86 | + /** |
|---|
| 87 | + * Static secret seed used to derive the token {@code secret} portion. |
|---|
| 88 | + * <p> |
|---|
| 89 | + * Treat this as confidential. Changing it invalidates all outstanding tokens. |
|---|
| 90 | + * </p> |
|---|
| 91 | + */ |
|---|
| 38 | 92 | private static byte[] seed = "S3Cur15S33dForT0k3nG3n3r@tion".getBytes(); |
|---|
| 39 | 93 | |
|---|
| 94 | + // --------------------------------------------------------------------- |
|---|
| 95 | + // Token generation |
|---|
| 96 | + // --------------------------------------------------------------------- |
|---|
| 97 | + |
|---|
| 40 | 98 | /** |
|---|
| 41 | | - * Generate a token encoded in Base64 for user passed as parameter and |
|---|
| 42 | | - * taking the current moment as token timestamp |
|---|
| 43 | | - * |
|---|
| 44 | | - * @param user |
|---|
| 45 | | - * @return |
|---|
| 99 | + * generateToken |
|---|
| 100 | + * <p> |
|---|
| 101 | + * Convenience overload that generates a token for {@code user} using the current |
|---|
| 102 | + * system time as the issuance timestamp. |
|---|
| 103 | + * </p> |
|---|
| 104 | + * |
|---|
| 105 | + * @param user Username to embed in the token (must be non-null/non-empty). |
|---|
| 106 | + * @return Base64-encoded token string, or {@code null} if a cryptographic error occurs. |
|---|
| 46 | 107 | */ |
|---|
| 47 | 108 | public String generateToken(String user) { |
|---|
| 48 | | - |
|---|
| 49 | 109 | return generateToken(user, new Date()); |
|---|
| 50 | 110 | } |
|---|
| 51 | 111 | |
|---|
| 52 | | - ; |
|---|
| 53 | | - |
|---|
| 112 | + /** |
|---|
| 113 | + * generateToken |
|---|
| 114 | + * <p> |
|---|
| 115 | + * Builds a token for a given user and issuance date. The token body is: |
|---|
| 116 | + * </p> |
|---|
| 117 | + * <pre> |
|---|
| 118 | + * secret + " " + user + " " + Utils.toIsoFormat(date) |
|---|
| 119 | + * </pre> |
|---|
| 120 | + * <p> |
|---|
| 121 | + * and then Base64-encoded in UTF-8. |
|---|
| 122 | + * </p> |
|---|
| 123 | + * |
|---|
| 124 | + * @param user Username to embed. |
|---|
| 125 | + * @param date Issuance date to include in the token (affects expiry and secret derivation). |
|---|
| 126 | + * @return Base64 token, or {@code null} upon failure. |
|---|
| 127 | + */ |
|---|
| 54 | 128 | public String generateToken(String user, Date date) { |
|---|
| 55 | 129 | try { |
|---|
| 56 | 130 | String secret = generateSecret(user, date); |
|---|
| .. | .. |
|---|
| 61 | 135 | sb.append(' '); |
|---|
| 62 | 136 | sb.append(Utils.toIsoFormat(date)); |
|---|
| 63 | 137 | |
|---|
| 64 | | - // Codificación estándar con UTF-8 |
|---|
| 138 | + // Standard UTF-8 encoding before Base64 |
|---|
| 65 | 139 | return Base64.getEncoder().encodeToString(sb.toString().getBytes(StandardCharsets.UTF_8)); |
|---|
| 66 | 140 | |
|---|
| 67 | 141 | } catch (NoSuchAlgorithmException e) { |
|---|
| .. | .. |
|---|
| 72 | 146 | return null; |
|---|
| 73 | 147 | } |
|---|
| 74 | 148 | |
|---|
| 149 | + /** |
|---|
| 150 | + * generateSecret |
|---|
| 151 | + * <p> |
|---|
| 152 | + * Derives the deterministic secret (a 64-hex-character SHA-256 digest) used to |
|---|
| 153 | + * authenticate a token. Inputs are concatenated in the following order: |
|---|
| 154 | + * </p> |
|---|
| 155 | + * <ol> |
|---|
| 156 | + * <li>{@link #seed}</li> |
|---|
| 157 | + * <li>{@code user} (UTF-8 bytes)</li> |
|---|
| 158 | + * <li>{@code Utils.toIsoFormat(date)}</li> |
|---|
| 159 | + * </ol> |
|---|
| 160 | + * |
|---|
| 161 | + * @param user Username to mix in the digest. |
|---|
| 162 | + * @param date Token issuance date to mix in the digest. |
|---|
| 163 | + * @return 64-char hex string. |
|---|
| 164 | + * @throws UnsupportedEncodingException If UTF-8 is unavailable (unexpected). |
|---|
| 165 | + * @throws NoSuchAlgorithmException If SHA-256 is unavailable (unexpected). |
|---|
| 166 | + */ |
|---|
| 75 | 167 | private String generateSecret(String user, Date date) throws UnsupportedEncodingException, NoSuchAlgorithmException { |
|---|
| 76 | 168 | MessageDigest mDigest = MessageDigest.getInstance("SHA-256"); |
|---|
| 77 | 169 | mDigest.update(seed, 0, seed.length); |
|---|
| 170 | + |
|---|
| 78 | 171 | byte[] userbytes = user.getBytes("utf-8"); |
|---|
| 79 | 172 | mDigest.update(userbytes, 0, userbytes.length); |
|---|
| 173 | + |
|---|
| 80 | 174 | byte[] isodate = Utils.toIsoFormat(date).getBytes(); |
|---|
| 81 | 175 | mDigest.update(isodate, 0, isodate.length); |
|---|
| 176 | + |
|---|
| 82 | 177 | BigInteger i = new BigInteger(1, mDigest.digest()); |
|---|
| 83 | 178 | String secret = String.format("%1$064x", i); |
|---|
| 84 | 179 | return secret; |
|---|
| 85 | 180 | } |
|---|
| 86 | 181 | |
|---|
| 182 | + // --------------------------------------------------------------------- |
|---|
| 183 | + // Token validation & parsing |
|---|
| 184 | + // --------------------------------------------------------------------- |
|---|
| 185 | + |
|---|
| 87 | 186 | /** |
|---|
| 88 | | - * Check if passed token is still valid, It use to check if token is expired |
|---|
| 89 | | - * the attribute VALID_TOKEN_PERIOD (in hours) |
|---|
| 90 | | - * |
|---|
| 91 | | - * @param token |
|---|
| 92 | | - * @return |
|---|
| 187 | + * isTokenValid |
|---|
| 188 | + * <p> |
|---|
| 189 | + * Validates the structure, signature and expiry of the given token. |
|---|
| 190 | + * Steps performed: |
|---|
| 191 | + * </p> |
|---|
| 192 | + * <ol> |
|---|
| 193 | + * <li>Base64-decode the token into {@code "secret user isoDate"}.</li> |
|---|
| 194 | + * <li>Parse {@code user} and {@code isoDate}; recompute the expected secret via |
|---|
| 195 | + * {@link #generateSecret(String, Date)} and compare with the provided one.</li> |
|---|
| 196 | + * <li>Check expiry: if the token's timestamp is older than |
|---|
| 197 | + * {@link #VALID_TOKEN_PERIOD} hours, it's invalid.</li> |
|---|
| 198 | + * <li>Special-case: if {@code user} equals {@link ApiResource#API_CLIENT_USERNAME} |
|---|
| 199 | + * and the date returns a non-positive epoch time (e.g., created with {@code new Date(-1)}), |
|---|
| 200 | + * the expiry check is skipped (client integration token).</li> |
|---|
| 201 | + * </ol> |
|---|
| 202 | + * |
|---|
| 203 | + * @param token Base64 token string. |
|---|
| 204 | + * @return {@code true} if valid and not expired; {@code false} otherwise. |
|---|
| 93 | 205 | */ |
|---|
| 94 | 206 | public boolean isTokenValid(String token) { |
|---|
| 95 | 207 | try { |
|---|
| 96 | | - String tokenDecoded = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8); |
|---|
| 208 | + String tokenDecoded = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8); |
|---|
| 97 | 209 | String[] parts = StringUtils.split(tokenDecoded, ' '); |
|---|
| 98 | 210 | if (parts == null || parts.length < 3) { |
|---|
| 99 | 211 | return false; |
|---|
| 100 | 212 | } |
|---|
| 213 | + |
|---|
| 101 | 214 | String secret = parts[0]; |
|---|
| 102 | 215 | String user = parts[1]; |
|---|
| 103 | 216 | Date date = Utils.toDateFromIso(parts[2]); |
|---|
| 217 | + |
|---|
| 218 | + // Expiry check (unless special client token rule applies) |
|---|
| 104 | 219 | if (date.getTime() > 0 || !user.equals(ApiResource.API_CLIENT_USERNAME)) { |
|---|
| 105 | 220 | if (new Date().after(new Date(date.getTime() + VALID_TOKEN_PERIOD * 60 * 60 * 1000))) { |
|---|
| 106 | 221 | return false; |
|---|
| 107 | 222 | } |
|---|
| 108 | | - } // else: It's a securis-client API call |
|---|
| 223 | + } |
|---|
| 224 | + |
|---|
| 225 | + // Signature check |
|---|
| 109 | 226 | String newSecret = generateSecret(user, date); |
|---|
| 110 | 227 | return newSecret.equals(secret); |
|---|
| 228 | + |
|---|
| 111 | 229 | } catch (IOException e) { |
|---|
| 112 | 230 | LOG.error("Error decoding Base64 token", e); |
|---|
| 113 | 231 | } catch (NoSuchAlgorithmException e) { |
|---|
| .. | .. |
|---|
| 116 | 234 | return false; |
|---|
| 117 | 235 | } |
|---|
| 118 | 236 | |
|---|
| 237 | + /** |
|---|
| 238 | + * extractUserFromToken |
|---|
| 239 | + * <p> |
|---|
| 240 | + * Extracts the username portion from a validly structured token. |
|---|
| 241 | + * </p> |
|---|
| 242 | + * |
|---|
| 243 | + * @param token Base64 token string (may be {@code null}). |
|---|
| 244 | + * @return Username if the token has at least three space-separated fields after decoding; |
|---|
| 245 | + * {@code null} on error or malformed input. |
|---|
| 246 | + */ |
|---|
| 119 | 247 | public String extractUserFromToken(String token) { |
|---|
| 120 | 248 | try { |
|---|
| 121 | 249 | if (token == null) { |
|---|
| .. | .. |
|---|
| 134 | 262 | return null; |
|---|
| 135 | 263 | } |
|---|
| 136 | 264 | |
|---|
| 265 | + /** |
|---|
| 266 | + * extractDateCreationFromToken |
|---|
| 267 | + * <p> |
|---|
| 268 | + * Parses and returns the issuance {@link Date} embedded in the token, without |
|---|
| 269 | + * performing validation or expiry checks. |
|---|
| 270 | + * </p> |
|---|
| 271 | + * |
|---|
| 272 | + * @param token Base64 token string. |
|---|
| 273 | + * @return Issuance {@link Date}, or {@code null} if the token is malformed or cannot be decoded. |
|---|
| 274 | + */ |
|---|
| 137 | 275 | public Date extractDateCreationFromToken(String token) { |
|---|
| 138 | 276 | try { |
|---|
| 139 | 277 | String tokenDecoded = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8); |
|---|
| .. | .. |
|---|
| 149 | 287 | return null; |
|---|
| 150 | 288 | } |
|---|
| 151 | 289 | |
|---|
| 290 | + // --------------------------------------------------------------------- |
|---|
| 291 | + // Demo / manual test |
|---|
| 292 | + // --------------------------------------------------------------------- |
|---|
| 293 | + |
|---|
| 294 | + /** |
|---|
| 295 | + * main |
|---|
| 296 | + * <p> |
|---|
| 297 | + * Simple manual test demonstrating generation and validation of a special |
|---|
| 298 | + * "_client" token that bypasses expiry via a negative epoch date. |
|---|
| 299 | + * </p> |
|---|
| 300 | + * |
|---|
| 301 | + * @param args CLI args (unused). |
|---|
| 302 | + * @throws IOException If something goes wrong while encoding/decoding Base64 (unlikely). |
|---|
| 303 | + */ |
|---|
| 152 | 304 | public static void main(String[] args) throws IOException { |
|---|
| 153 | 305 | // client token: |
|---|
| 154 | 306 | // OTk3ODRiMzY5NzQ5MWI5NmYyZGQyODRiYjY2ZTU2YzdmMTZjYzM3YTY3N2ExM2M3ODI2MjU5ZTMzOTIyYjUzNSBfY2xpZW50IDE5NzAtMDEtMDFUMDA6NTk6NTkuOTk5KzAxMDA= |
|---|
| .. | .. |
|---|
| 160 | 312 | System.out.println("is valid client token: " + new TokenHelper().isTokenValid(t)); |
|---|
| 161 | 313 | } |
|---|
| 162 | 314 | } |
|---|
| 315 | + |
|---|
| .. | .. |
|---|
| 19 | 19 | </properties> |
|---|
| 20 | 20 | |
|---|
| 21 | 21 | </persistence-unit> |
|---|
| 22 | | -</persistence> |
|---|
| 22 | +</persistence> |
|---|