From 146a0fb8b0e90f9196e569152f649baf60d6cc8f Mon Sep 17 00:00:00 2001
From: Joaquín Reñé <jrene@curisit.net>
Date: Tue, 07 Oct 2025 14:52:57 +0000
Subject: [PATCH] #4410 - Comments on classes
---
securis/src/main/java/net/curisit/securis/services/helpers/MetadataHelper.java | 372 ++++++++++++++++++++++++++++++++++------------------
1 files changed, 243 insertions(+), 129 deletions(-)
diff --git a/securis/src/main/java/net/curisit/securis/services/helpers/MetadataHelper.java b/securis/src/main/java/net/curisit/securis/services/helpers/MetadataHelper.java
index ac70711..4fb1e3e 100644
--- a/securis/src/main/java/net/curisit/securis/services/helpers/MetadataHelper.java
+++ b/securis/src/main/java/net/curisit/securis/services/helpers/MetadataHelper.java
@@ -1,3 +1,6 @@
+/*
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
+ */
package net.curisit.securis.services.helpers;
import java.util.Collection;
@@ -24,147 +27,258 @@
import net.curisit.securis.db.PackMetadata;
import net.curisit.securis.db.common.Metadata;
+/**
+ * MetadataHelper
+ * <p>
+ * Utilities to compare, merge and propagate metadata across the hierarchy:
+ * Application -> LicenseType -> Pack -> (marks License as metadata-obsolete)
+ * <p>
+ * Provides:
+ * - Equality checks on metadata sets.
+ * - Merge semantics: remove keys not present, update changed values/mandatory flags.
+ * - Propagation from Application down to LicenseType and from LicenseType down to Packs.
+ * - Marking existing licenses as "metadataObsolete" when pack metadata changes and
+ * the license is in a state where consumers could depend on metadata snapshot.
+ *
+ * Thread-safety: ApplicationScoped, stateless.
+ *
+ * @author JRA
+ * Last reviewed by JRA on Oct 5, 2025.
+ */
@ApplicationScoped
public class MetadataHelper {
- private static final Logger log = LogManager.getLogger(MetadataHelper.class);
+ private static final Logger log = LogManager.getLogger(MetadataHelper.class);
- public <T extends Metadata> boolean match(T m1, T m2) {
- if (m1 == null || m2 == null) {
- return false;
- }
- return Objects.equals(m1.getKey(), m2.getKey()) && Objects.equals(m1.getValue(), m2.getValue()) && m1.isMandatory() == m2.isMandatory();
- }
+ /**
+ * match
+ * <p>
+ * Compare two metadata entries (key, value, mandatory).
+ *
+ * @param m1 First metadata.
+ * @param m2 Second metadata.
+ * @param <T> Metadata subtype.
+ * @return true if equal in key/value/mandatory, false otherwise or if any is null.
+ */
+ public <T extends Metadata> boolean match(T m1, T m2) {
+ if (m1 == null || m2 == null) {
+ return false;
+ }
+ return Objects.equals(m1.getKey(), m2.getKey()) && Objects.equals(m1.getValue(), m2.getValue()) && m1.isMandatory() == m2.isMandatory();
+ }
- public <T extends Metadata> Metadata findByKey(String key, Collection<T> listMd) {
- return listMd.parallelStream().filter(m -> Objects.equals(key, m.getKey())).findAny().orElse(null);
- }
+ /**
+ * findByKey
+ * <p>
+ * Find a metadata by key in a collection.
+ *
+ * @param key Metadata key to search.
+ * @param listMd Collection of metadata.
+ * @param <T> Metadata subtype.
+ * @return The first matching metadata or null.
+ */
+ public <T extends Metadata> Metadata findByKey(String key, Collection<T> listMd) {
+ return listMd.parallelStream().filter(m -> Objects.equals(key, m.getKey())).findAny().orElse(null);
+ }
- public <T extends Metadata> boolean match(Set<T> listMd1, Set<T> listMd2) {
- if (listMd1.size() != listMd2.size()) {
- return false;
- }
- return listMd1.parallelStream().allMatch(m -> this.match(m, findByKey(m.getKey(), listMd2)));
- }
+ /**
+ * match
+ * <p>
+ * Compare two sets of metadata for equality (size + all entries match).
+ *
+ * @param listMd1 First set.
+ * @param listMd2 Second set.
+ * @param <T> Metadata subtype.
+ * @return true if both sets match element-wise, false otherwise.
+ */
+ public <T extends Metadata> boolean match(Set<T> listMd1, Set<T> listMd2) {
+ if (listMd1.size() != listMd2.size()) {
+ return false;
+ }
+ return listMd1.parallelStream().allMatch(m -> this.match(m, findByKey(m.getKey(), listMd2)));
+ }
- public <T extends Metadata, K extends Metadata> void mergeMetadata(EntityManager em, Set<T> srcListMd, Set<K> tgtListMd, Set<String> keys) {
+ /**
+ * mergeMetadata
+ * <p>
+ * Merge metadata from a source set (truth) into a target set.
+ * - Removes entries in target whose keys are not in {@code keys}.
+ * - Updates entries in target whose value/mandatory differ from source.
+ * - Does NOT create new entries; caller is expected to persist new ones separately.
+ *
+ * @param em EntityManager to remove/merge.
+ * @param srcListMd Source metadata set (truth).
+ * @param tgtListMd Target metadata set to update.
+ * @param keys Keys present in source.
+ * @param <T> Source metadata type.
+ * @param <K> Target metadata type.
+ */
+ public <T extends Metadata, K extends Metadata> void mergeMetadata(EntityManager em, Set<T> srcListMd, Set<K> tgtListMd, Set<String> keys) {
- Set<K> mdToRemove = tgtListMd.parallelStream() //
- .filter(md -> !keys.contains(md.getKey())) //
- .collect(Collectors.toSet());
- for (K tgtMd : mdToRemove) {
- log.info("MD key to remove: {} - {}", tgtMd.getKey(), tgtMd);
- if (tgtMd instanceof LicenseTypeMetadata) {
- log.info("LT: {}, tx: {}, contans: {}", LicenseTypeMetadata.class.cast(tgtMd).getLicenseType(), em.isJoinedToTransaction(), em.contains(tgtMd));
- }
- em.remove(tgtMd);
- }
- Set<K> keysToUpdate = tgtListMd.parallelStream() //
- .filter(md -> keys.contains(md.getKey())) //
- .collect(Collectors.toSet());
- for (K tgtMd : keysToUpdate) {
- Metadata md = this.findByKey(tgtMd.getKey(), srcListMd);
- if (md.isMandatory() != tgtMd.isMandatory() || !Objects.equals(md.getValue(), tgtMd.getValue())) {
- tgtMd.setMandatory(md.isMandatory());
- tgtMd.setValue(md.getValue());
- log.info("MD key to update: {}", tgtMd.getKey());
- em.merge(tgtMd);
- }
- }
- }
+ // Remove missing keys
+ Set<K> mdToRemove = tgtListMd.parallelStream()
+ .filter(md -> !keys.contains(md.getKey()))
+ .collect(Collectors.toSet());
+ for (K tgtMd : mdToRemove) {
+ log.info("MD key to remove: {} - {}", tgtMd.getKey(), tgtMd);
+ if (tgtMd instanceof LicenseTypeMetadata) {
+ log.info("LT: {}, tx: {}, contans: {}", LicenseTypeMetadata.class.cast(tgtMd).getLicenseType(), em.isJoinedToTransaction(), em.contains(tgtMd));
+ }
+ em.remove(tgtMd);
+ }
- private Set<LicenseTypeMetadata> createNewMetadata(Set<ApplicationMetadata> appMd, Set<LicenseTypeMetadata> existingMd, LicenseType licenseType) {
- Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet());
- return appMd.parallelStream() //
- .filter(md -> !oldKeys.contains(md.getKey())) //
- .map(appmd -> {
- LicenseTypeMetadata ltmd = new LicenseTypeMetadata();
- ltmd.setLicenseType(licenseType);
- ltmd.setKey(appmd.getKey());
- ltmd.setValue(appmd.getValue());
- ltmd.setMandatory(appmd.isMandatory());
- return ltmd;
- }).collect(Collectors.toSet());
- }
+ // Update changed keys
+ Set<K> keysToUpdate = tgtListMd.parallelStream()
+ .filter(md -> keys.contains(md.getKey()))
+ .collect(Collectors.toSet());
+ for (K tgtMd : keysToUpdate) {
+ Metadata md = this.findByKey(tgtMd.getKey(), srcListMd);
+ if (md.isMandatory() != tgtMd.isMandatory() || !Objects.equals(md.getValue(), tgtMd.getValue())) {
+ tgtMd.setMandatory(md.isMandatory());
+ tgtMd.setValue(md.getValue());
+ log.info("MD key to update: {}", tgtMd.getKey());
+ em.merge(tgtMd);
+ }
+ }
+ }
- private Set<PackMetadata> createNewMetadata(Set<LicenseTypeMetadata> ltMd, Set<PackMetadata> existingMd, Pack pack) {
- Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet());
- return ltMd.parallelStream() //
- .filter(md -> !oldKeys.contains(md.getKey())) //
- .map(md -> {
- PackMetadata pmd = new PackMetadata();
- pmd.setPack(pack);
- pmd.setKey(md.getKey());
- pmd.setValue(md.getValue());
- pmd.setMandatory(md.isMandatory());
- return pmd;
- }).collect(Collectors.toSet());
- }
+ // -- Internal helpers to create new metadata rows when propagating
- /**
- * Copy the modified app metadata to LicenseTypes and Packs
- *
- * @param em
- * @param app
- */
- public void propagateMetadata(EntityManager em, Application app) {
- Set<ApplicationMetadata> appMd = app.getApplicationMetadata();
- Set<String> keys = appMd.parallelStream().map(md -> md.getKey()).collect(Collectors.toSet());
- for (LicenseType lt : app.getLicenseTypes()) {
- log.info("Lic type to update: {}", lt.getCode());
- this.mergeMetadata(em, appMd, lt.getMetadata(), keys);
- Set<LicenseTypeMetadata> newMdList = createNewMetadata(appMd, lt.getMetadata(), lt);
- for (LicenseTypeMetadata newMetadata : newMdList) {
- em.persist(newMetadata);
- }
- em.detach(lt);
- // Probably there is a better way to get the final metadata from JPA...
- TypedQuery<LicenseTypeMetadata> updatedMdQuery = em.createNamedQuery("list-licensetype-metadata", LicenseTypeMetadata.class);
- updatedMdQuery.setParameter("licenseTypeId", lt.getId());
- Set<LicenseTypeMetadata> updatedMd = new HashSet<>(updatedMdQuery.getResultList());
+ /**
+ * createNewMetadata<p>
+ * Create new metadata
+ *
+ * @param appMd
+ * @param existingMd
+ * @param licenseType
+ * @return newMetadata
+ */
+ private Set<LicenseTypeMetadata> createNewMetadata(Set<ApplicationMetadata> appMd, Set<LicenseTypeMetadata> existingMd, LicenseType licenseType) {
+ Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet());
+ return appMd.parallelStream()
+ .filter(md -> !oldKeys.contains(md.getKey()))
+ .map(appmd -> {
+ LicenseTypeMetadata ltmd = new LicenseTypeMetadata();
+ ltmd.setLicenseType(licenseType);
+ ltmd.setKey(appmd.getKey());
+ ltmd.setValue(appmd.getValue());
+ ltmd.setMandatory(appmd.isMandatory());
+ return ltmd;
+ }).collect(Collectors.toSet());
+ }
- lt.setMetadata(updatedMd);
- propagateMetadata(em, lt, keys);
- }
- }
+ /**
+ * createNewMetadata<p>
+ * Create the new metadata
+ *
+ * @param ltMd
+ * @param existingMd
+ * @param pack
+ * @return newMetadata
+ */
+ private Set<PackMetadata> createNewMetadata(Set<LicenseTypeMetadata> ltMd, Set<PackMetadata> existingMd, Pack pack) {
+ Set<String> oldKeys = existingMd.stream().map(md -> md.getKey()).collect(Collectors.toSet());
+ return ltMd.parallelStream()
+ .filter(md -> !oldKeys.contains(md.getKey()))
+ .map(md -> {
+ PackMetadata pmd = new PackMetadata();
+ pmd.setPack(pack);
+ pmd.setKey(md.getKey());
+ pmd.setValue(md.getValue());
+ pmd.setMandatory(md.isMandatory());
+ return pmd;
+ }).collect(Collectors.toSet());
+ }
- /**
- * Copy the modified licenseType metadata to Packs
- *
- * @param em
- * @param lt
- * @param keys
- */
- public void propagateMetadata(EntityManager em, LicenseType lt, Set<String> keys) {
- Set<LicenseTypeMetadata> ltMd = lt.getMetadata();
- TypedQuery<Pack> packsQuery = em.createNamedQuery("list-packs-by-lic-type", Pack.class);
- packsQuery.setParameter("lt_id", lt.getId());
- List<Pack> packs = packsQuery.getResultList();
- log.info("Packs to update the metadata: {}", packs.size());
- for (Pack pack : packs) {
- if (pack.isFrozen()) {
- log.warn("Metadata in LicenseType {} has changed but the Pack {} is frozen and won't be updated.", lt.getCode(), pack.getCode());
- continue;
- }
- this.mergeMetadata(em, ltMd, pack.getMetadata(), keys);
- Set<PackMetadata> newMdList = createNewMetadata(ltMd, pack.getMetadata(), pack);
- for (PackMetadata newMetadata : newMdList) {
- em.persist(newMetadata);
- }
- markObsoleteMetadata(em, pack);
- em.detach(pack);
- }
- }
+ /**
+ * propagateMetadata (Application -> LicenseTypes -> Packs)
+ * <p>
+ * Propagates application metadata changes down to all its license types and packs:
+ * - mergeMetadata on LicenseType
+ * - create new LicenseTypeMetadata for new keys
+ * - re-fetch LT metadata (detached/merged semantics)
+ * - propagateMetadata(LicenseType) to packs
+ *
+ * @param em EntityManager.
+ * @param app Application with updated metadata loaded.
+ */
+ public void propagateMetadata(EntityManager em, Application app) {
+ Set<ApplicationMetadata> appMd = app.getApplicationMetadata();
+ Set<String> keys = appMd.parallelStream().map(md -> md.getKey()).collect(Collectors.toSet());
+ for (LicenseType lt : app.getLicenseTypes()) {
+ log.info("Lic type to update: {}", lt.getCode());
+ this.mergeMetadata(em, appMd, lt.getMetadata(), keys);
+ Set<LicenseTypeMetadata> newMdList = createNewMetadata(appMd, lt.getMetadata(), lt);
+ for (LicenseTypeMetadata newMetadata : newMdList) {
+ em.persist(newMetadata);
+ }
+ em.detach(lt);
- public void markObsoleteMetadata(EntityManager em, Pack pack) {
- TypedQuery<License> existingPackLicenses = em.createNamedQuery("list-licenses-by-pack", License.class);
- existingPackLicenses.setParameter("packId", pack.getId());
- for (License lic : existingPackLicenses.getResultList()) {
- log.info("License from pack: {}, status: {}", lic.getCode(), lic.getStatus());
- if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE || lic.getStatus() == LicenseStatus.CANCELLED) {
- lic.setMetadataObsolete(true);
- em.merge(lic);
- }
- }
- }
+ // Re-read updated metadata
+ TypedQuery<LicenseTypeMetadata> updatedMdQuery = em.createNamedQuery("list-licensetype-metadata", LicenseTypeMetadata.class);
+ updatedMdQuery.setParameter("licenseTypeId", lt.getId());
+ Set<LicenseTypeMetadata> updatedMd = new HashSet<>(updatedMdQuery.getResultList());
+
+ lt.setMetadata(updatedMd);
+ propagateMetadata(em, lt, keys);
+ }
+ }
+
+ /**
+ * propagateMetadata (LicenseType -> Packs)
+ * <p>
+ * Propagates license type metadata changes to all its packs:
+ * - mergeMetadata on Pack
+ * - create new PackMetadata for new keys
+ * - markObsoleteMetadata on packs to flag their licenses
+ *
+ * Frozen packs are skipped.
+ *
+ * @param em EntityManager.
+ * @param lt LicenseType with updated metadata set.
+ * @param keys Set of keys present in the source.
+ */
+ public void propagateMetadata(EntityManager em, LicenseType lt, Set<String> keys) {
+ Set<LicenseTypeMetadata> ltMd = lt.getMetadata();
+ TypedQuery<Pack> packsQuery = em.createNamedQuery("list-packs-by-lic-type", Pack.class);
+ packsQuery.setParameter("lt_id", lt.getId());
+ List<Pack> packs = packsQuery.getResultList();
+ log.info("Packs to update the metadata: {}", packs.size());
+ for (Pack pack : packs) {
+ if (pack.isFrozen()) {
+ log.warn("Metadata in LicenseType {} has changed but the Pack {} is frozen and won't be updated.", lt.getCode(), pack.getCode());
+ continue;
+ }
+ this.mergeMetadata(em, ltMd, pack.getMetadata(), keys);
+ Set<PackMetadata> newMdList = createNewMetadata(ltMd, pack.getMetadata(), pack);
+ for (PackMetadata newMetadata : newMdList) {
+ em.persist(newMetadata);
+ }
+ markObsoleteMetadata(em, pack);
+ em.detach(pack);
+ }
+ }
+
+ /**
+ * markObsoleteMetadata
+ * <p>
+ * For all licenses within the given pack, mark {@code metadataObsolete = true}
+ * if the license is in a relevant state (ACTIVE, PRE_ACTIVE, CANCELLED).
+ * This lets clients know that metadata-dependent artifacts might need refresh.
+ *
+ * @param em EntityManager.
+ * @param pack Pack whose licenses to mark.
+ */
+ public void markObsoleteMetadata(EntityManager em, Pack pack) {
+ TypedQuery<License> existingPackLicenses = em.createNamedQuery("list-licenses-by-pack", License.class);
+ existingPackLicenses.setParameter("packId", pack.getId());
+ for (License lic : existingPackLicenses.getResultList()) {
+ log.info("License from pack: {}, status: {}", lic.getCode(), lic.getStatus());
+ if (lic.getStatus() == LicenseStatus.ACTIVE || lic.getStatus() == LicenseStatus.PRE_ACTIVE || lic.getStatus() == LicenseStatus.CANCELLED) {
+ lic.setMetadataObsolete(true);
+ em.merge(lic);
+ }
+ }
+ }
}
+
--
Gitblit v1.3.2