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