/* * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved. */ package net.curisit.securis.services; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.persistence.TypedQuery; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.POST; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import net.curisit.integrity.commons.Utils; import net.curisit.securis.DefaultExceptionHandler; import net.curisit.securis.SeCurisException; import net.curisit.securis.db.Application; import net.curisit.securis.db.LicenseType; import net.curisit.securis.db.LicenseTypeMetadata; import net.curisit.securis.db.User.Rol; import net.curisit.securis.ioc.EnsureTransaction; import net.curisit.securis.security.BasicSecurityContext; import net.curisit.securis.security.Securable; import net.curisit.securis.services.exception.SeCurisServiceException; import net.curisit.securis.services.exception.SeCurisServiceException.ErrorCodes; import net.curisit.securis.services.helpers.MetadataHelper; import net.curisit.securis.utils.TokenHelper; /** * LicenseTypeResource *

* CRUD for license types. Non-admin queries are scoped to the applications * accessible by the caller. Metadata changes are reconciled and, when keys * change, can be propagated to dependent entities via {@link MetadataHelper}. * * @author JRA * Last reviewed by JRA on Oct 5, 2025. */ @Path("/licensetype") public class LicenseTypeResource { private static final Logger LOG = LogManager.getLogger(LicenseTypeResource.class); @Inject TokenHelper tokenHelper; @Inject MetadataHelper metadataHelper; @Context EntityManager em; public LicenseTypeResource() { } /** * index *

* List license types. Non-admin users get only types for their allowed apps. * * @param bsc security context. * @return 200 OK with list (possibly empty). */ @GET @Path("/") @Produces({ MediaType.APPLICATION_JSON }) @Securable public Response index(@Context BasicSecurityContext bsc) { LOG.info("Getting license types list "); em.clear(); TypedQuery q; if (bsc.isUserInRole(BasicSecurityContext.ROL_ADMIN)) { q = em.createNamedQuery("list-license_types", LicenseType.class); } else { if (bsc.getApplicationsIds() == null || bsc.getApplicationsIds().isEmpty()) { return Response.ok().build(); } q = em.createNamedQuery("list-license_types-by_apps-id", LicenseType.class); q.setParameter("list_ids", bsc.getApplicationsIds()); } List list = q.getResultList(); return Response.ok(list).build(); } /** * get *

* Fetch a license type by id. * * @param ltid LicenseType id (string form). * @param token (unused) header token. * @return 200 OK with the entity. * @throws SeCurisServiceException 404 if not found. */ @GET @Path("/{ltid}") @Produces({ MediaType.APPLICATION_JSON }) @Securable public Response get(@PathParam("ltid") String ltid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) throws SeCurisServiceException { LOG.info("Getting license type data for id: {}: ", ltid); if (ltid == null || "".equals(ltid)) { LOG.error("LicenseType ID is mandatory"); return Response.status(Status.NOT_FOUND).build(); } em.clear(); LicenseType lt = em.find(LicenseType.class, Integer.parseInt(ltid)); if (lt == null) { LOG.error("LicenseType with id {} not found in DB", ltid); throw new SeCurisServiceException(ErrorCodes.NOT_FOUND, "LicenseType was not found in DB"); } return Response.ok(lt).build(); } /** * create *

* Create a new license type. Requires ADMIN. Sets application reference, * persists metadata entries and stamps creation time. * * @param lt Payload. * @param token (unused) token header. * @return 200 OK with created entity, or 404 if app missing. */ @POST @Path("/") @Consumes(MediaType.APPLICATION_JSON) @Produces({ MediaType.APPLICATION_JSON }) @EnsureTransaction @Securable(roles = Rol.ADMIN) @RolesAllowed(BasicSecurityContext.ROL_ADMIN) public Response create(LicenseType lt, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { LOG.info("Creating new license type"); try { setApplication(lt, lt.getApplicationId(), em); } catch (SeCurisException e) { return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build(); } if (lt.getApplicationId() == null) { LOG.error("Application is missing for current license type data"); return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "Application is missing for current license type data").build(); } lt.setCreationTimestamp(new Date()); em.persist(lt); Set newMD = lt.getMetadata(); if (newMD != null) { for (LicenseTypeMetadata md : newMD) { md.setLicenseType(lt); em.persist(md); } } lt.setMetadata(newMD); return Response.ok(lt).build(); } /** * modify *

* Update an existing license type. Reconciles metadata: * removes keys not present in the new set; merges existing; persists new ones. * If keys changed, {@link MetadataHelper#propagateMetadata} is invoked. * * @param lt New values. * @param ltid LicenseType id. * @param token (unused) token. * @return 200 OK with updated entity; 404 if not found or app missing. */ @PUT @POST @Path("/{ltid}") @EnsureTransaction @Consumes(MediaType.APPLICATION_JSON) @Produces({ MediaType.APPLICATION_JSON }) @Securable(roles = Rol.ADMIN) @RolesAllowed(BasicSecurityContext.ROL_ADMIN) public Response modify(LicenseType lt, @PathParam("ltid") String ltid, @HeaderParam(TokenHelper.TOKEN_HEADER_PÀRAM) String token) { LOG.info("Modifying license type with id: {}", ltid); LicenseType currentlt = em.find(LicenseType.class, Integer.parseInt(ltid)); if (currentlt == null) { LOG.error("LicenseType with id {} not found in DB", ltid); return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, "License type not found with ID: " + ltid).build(); } try { setApplication(currentlt, lt.getApplicationId(), em); } catch (SeCurisException e) { return Response.status(Status.NOT_FOUND).header(DefaultExceptionHandler.ERROR_MESSAGE_HEADER, e.getMessage()).build(); } currentlt.setCode(lt.getCode()); currentlt.setName(lt.getName()); currentlt.setDescription(lt.getDescription()); Set newMD = lt.getMetadata(); Set oldMD = currentlt.getMetadata(); boolean metadataChanges = !metadataHelper.match(newMD, oldMD); if (metadataChanges) { Set newMdKeys = getMdKeys(newMD); for (LicenseTypeMetadata currentMd : oldMD) { if (!newMdKeys.contains(currentMd.getKey())) { em.remove(currentMd); LOG.info("Removing MD: {}", currentMd); } } if (newMD != null) { Set oldMdKeys = getMdKeys(oldMD); for (LicenseTypeMetadata md : newMD) { if (oldMdKeys.contains(md.getKey())) { em.merge(md); } else { md.setLicenseType(currentlt); em.persist(md); } } } currentlt.setMetadata(newMD); } em.merge(currentlt); if (metadataChanges) { Set keys = newMD.parallelStream().map(md -> md.getKey()).collect(Collectors.toSet()); metadataHelper.propagateMetadata(em, currentlt, keys); } return Response.ok(currentlt).build(); } /** * delete *

* Delete a license type by id. Requires ADMIN. * * @param ltid LicenseType id. * @param req request (unused). * @return 200 OK on success; 404 if not found. */ @DELETE @Path("/{ltid}") @EnsureTransaction @Produces({ MediaType.APPLICATION_JSON }) @Securable(roles = Rol.ADMIN) @RolesAllowed(BasicSecurityContext.ROL_ADMIN) public Response delete(@PathParam("ltid") String ltid, @Context HttpServletRequest req) { LOG.info("Deleting app with id: {}", ltid); LicenseType app = em.find(LicenseType.class, Integer.parseInt(ltid)); if (app == null) { LOG.error("LicenseType with id {} can not be deleted, It was not found in DB", ltid); return Response.status(Status.NOT_FOUND).build(); } em.remove(app); return Response.ok(Utils.createMap("success", true, "id", ltid)).build(); } // --------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------- private Set getMdKeys(Set mds) { Set ids = new HashSet(); if (mds != null) { for (LicenseTypeMetadata md : mds) { ids.add(md.getKey()); } } return ids; } /** * setApplication

* Resolve and set the application of a license type. * * @param licType target LicenseType. * @param applicationId id of the application (nullable). * @param em entity manager. * @throws SeCurisException if id provided but not found. */ private void setApplication(LicenseType licType, Integer applicationId, EntityManager em) throws SeCurisException { Application app = null; if (applicationId != null) { app = em.find(Application.class, applicationId); if (app == null) { LOG.error("LicenseType application with id {} not found in DB", applicationId); throw new SecurityException("License type's app not found with ID: " + applicationId); } } licType.setApplication(app); } }