diff --git a/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java b/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java index 1380350f3bcf176c1c8a67000b502fd678680e0b..442a0f66d775df4b3bcda72cdec066f4e6c7aae5 100644 --- a/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java +++ b/src/main/java/org/olat/modules/qpool/manager/QuestionPoolServiceImpl.java @@ -847,11 +847,13 @@ public class QuestionPoolServiceImpl implements QPoolService { @Override public TaxonomyLevel updateTaxonomyLevel(TaxonomyLevel level, String identifier, String displayName) { - return null;//taxonomy taxonomyLevelDao.updateTaxonomyLevel(level).update(newField, level); + level.setIdentifier(identifier); + level.setDisplayName(displayName); + return taxonomyLevelDao.updateTaxonomyLevel(level); } @Override public boolean deleteTaxonomyLevel(TaxonomyLevel level) { - return false;//TODO taxonomy taxonomyLevelDao.delete(level); + return taxonomyLevelDao.delete(level); } } \ No newline at end of file diff --git a/src/main/java/org/olat/modules/taxonomy/TaxonomyService.java b/src/main/java/org/olat/modules/taxonomy/TaxonomyService.java index 059ba632c64e84227f73d959f1d2aece51f48efe..b3939505f83094c118c4394f5c317df520d36546 100644 --- a/src/main/java/org/olat/modules/taxonomy/TaxonomyService.java +++ b/src/main/java/org/olat/modules/taxonomy/TaxonomyService.java @@ -53,23 +53,55 @@ public interface TaxonomyService { public TaxonomyLevel createTaxonomyLevel(String identifier, String displayName, String description, String externalId, TaxonomyLevelManagedFlag[] flags, TaxonomyLevel parent, Taxonomy taxonomy); + /** + * + * @return true if the level can be deleted + */ + public boolean deleteTaxonomyLevel(TaxonomyLevelRef taxonomyLevel); + /** * @param ref The root taxonomy (optional) * @return A list of levels */ public List<TaxonomyLevel> getTaxonomyLevels(TaxonomyRef ref); + /** + * Load a taxonomy level by is reference. + * + * @param ref The taxonomy level key + * @return The freshly loaded taxonomy level + */ public TaxonomyLevel getTaxonomyLevel(TaxonomyLevelRef ref); + public List<TaxonomyLevel> getTaxonomyLevelParentLine(TaxonomyLevel taxonomyLevel, Taxonomy taxonomy); public TaxonomyLevel updateTaxonomyLevel(TaxonomyLevel level); - + /** + * Get the documents directory for the specified taxonomy level. + * + * @param level The taxonomy level + * @return A directory + */ public VFSContainer getDocumentsLibrary(TaxonomyLevel level); + /** + * Get the documents library for the specified taxonomy. + * + * @param taxonomy The taxonomy + * @return A directory + */ public VFSContainer getDocumentsLibrary(Taxonomy taxonomy); + /** + * The directory reserved to the information page of the + * taxonomy. The info page itself is normally "index.html" + * and the directory can be used to save some images. + * + * @param taxonomy The taxonomy + * @return A directory + */ public VFSContainer getTaxonomyInfoPageContainer(Taxonomy taxonomy); @@ -181,6 +213,13 @@ public interface TaxonomyService { TaxonomyRef taxonomy, TaxonomyCompetence competence, IdentityRef assessedIdentity, IdentityRef author); + /** + * Standardized conversion from object to XML used by + * the audit log. + * + * @param competence The competence + * @return A XML representation of the competence. + */ public String toAuditXml(TaxonomyCompetence competence); } diff --git a/src/main/java/org/olat/modules/taxonomy/manager/TaxonomyLevelDAO.java b/src/main/java/org/olat/modules/taxonomy/manager/TaxonomyLevelDAO.java index fc10109c651e088e635d4ecfb4c4ee0fa81b6c47..37a76920bb4cc4e869a31812289f50ca3fddbf0b 100644 --- a/src/main/java/org/olat/modules/taxonomy/manager/TaxonomyLevelDAO.java +++ b/src/main/java/org/olat/modules/taxonomy/manager/TaxonomyLevelDAO.java @@ -36,6 +36,7 @@ import org.olat.core.util.vfs.VFSContainer; import org.olat.modules.taxonomy.Taxonomy; import org.olat.modules.taxonomy.TaxonomyLevel; import org.olat.modules.taxonomy.TaxonomyLevelManagedFlag; +import org.olat.modules.taxonomy.TaxonomyLevelRef; import org.olat.modules.taxonomy.TaxonomyLevelType; import org.olat.modules.taxonomy.TaxonomyRef; import org.olat.modules.taxonomy.model.TaxonomyLevelImpl; @@ -92,28 +93,39 @@ public class TaxonomyLevelDAO implements InitializingBean { String storage = createLevelStorage(taxonomy, level); level.setDirectoryPath(storage); + String identifiersPath = getMaterializedPathIdentifiers(parent, level); + String keysPath = getMaterializedPathKeys(parent, level); + level.setParent(parent); + level.setMaterializedPathKeys(keysPath); + level.setMaterializedPathIdentifiers(identifiersPath); + + level = dbInstance.getCurrentEntityManager().merge(level); + level.getTaxonomy(); + return level; + } + + private String getMaterializedPathIdentifiers(TaxonomyLevel parent, TaxonomyLevel level) { if(parent != null) { - level.setParent(parent); - - String parentPathOfKeys = ((TaxonomyLevelImpl)parent).getMaterializedPathKeys(); + String parentPathOfIdentifiers = parent.getMaterializedPathIdentifiers(); + if(parentPathOfIdentifiers == null || "/".equals(parentPathOfIdentifiers)) { + parentPathOfIdentifiers = "/"; + } + return parentPathOfIdentifiers + level.getIdentifier() + "/"; + } + return "/" + level.getIdentifier() + "/"; + } + + private String getMaterializedPathKeys(TaxonomyLevel parent, TaxonomyLevel level) { + if(parent != null) { + + String parentPathOfKeys = parent.getMaterializedPathKeys(); if(parentPathOfKeys == null || "/".equals(parentPathOfKeys)) { parentPathOfKeys = ""; } - String parentPathOfIdentifiers = ((TaxonomyLevelImpl)parent).getMaterializedPathIdentifiers(); - if(parentPathOfIdentifiers == null || "/".equals(parentPathOfIdentifiers)) { - parentPathOfIdentifiers = ""; - } - level.setMaterializedPathKeys(parentPathOfKeys + level.getKey() + "/"); - level.setMaterializedPathIdentifiers(parentPathOfIdentifiers + level.getIdentifier() + "/"); - } else { - level.setMaterializedPathKeys("/" + level.getKey() + "/"); - level.setMaterializedPathIdentifiers("/" + level.getIdentifier() + "/"); + return parentPathOfKeys + level.getKey() + "/"; } - - level = dbInstance.getCurrentEntityManager().merge(level); - level.getTaxonomy(); - return level; + return "/" + level.getKey() + "/"; } public TaxonomyLevel loadByKey(Long key) { @@ -171,8 +183,19 @@ public class TaxonomyLevelDAO implements InitializingBean { .getResultList(); } + public TaxonomyLevel getParent(TaxonomyLevelRef taxonomyLevel) { + StringBuilder sb = new StringBuilder(256); + sb.append("select level.parent from ctaxonomylevel as level") + .append(" where level.key=:taxonomyLevelKey"); + List<TaxonomyLevel> levels = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), TaxonomyLevel.class) + .setParameter("taxonomyLevelKey", taxonomyLevel.getKey()) + .getResultList(); + return levels == null || levels.isEmpty() ? null : levels.get(0); + } + // Perhaps replace it with a select in ( materializedPathKeys.split("[/]") ) would be better - public List<TaxonomyLevel> getParentLine(TaxonomyLevel taxonomyLevel, Taxonomy taxonomy) { + public List<TaxonomyLevel> getParentLine(TaxonomyLevel taxonomyLevel, TaxonomyRef taxonomy) { StringBuilder sb = new StringBuilder(256); sb.append("select level from ctaxonomylevel as level") .append(" left join fetch level.parent as parent") @@ -189,9 +212,99 @@ public class TaxonomyLevelDAO implements InitializingBean { return levels; } + public List<TaxonomyLevel> getDescendants(TaxonomyLevel taxonomyLevel, TaxonomyRef taxonomy) { + StringBuilder sb = new StringBuilder(256); + sb.append("select level from ctaxonomylevel as level") + .append(" left join fetch level.parent as parent") + .append(" left join fetch level.type as type") + .append(" where level.taxonomy.key=:taxonomyKey") + .append(" and level.key!=:levelKey and level.materializedPathKeys like :materializedPath"); + + List<TaxonomyLevel> levels = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), TaxonomyLevel.class) + .setParameter("materializedPath", taxonomyLevel.getMaterializedPathKeys() + "%") + .setParameter("levelKey", taxonomyLevel.getKey()) + .setParameter("taxonomyKey", taxonomy.getKey()) + .getResultList(); + Collections.sort(levels, new PathMaterializedPathLengthComparator()); + return levels; + } + public TaxonomyLevel updateTaxonomyLevel(TaxonomyLevel level) { + boolean updatePath = false; + + String path = level.getMaterializedPathIdentifiers(); + String newPath = null; + + TaxonomyLevel parentLevel = getParent(level); + if(parentLevel != null) { + newPath = getMaterializedPathIdentifiers(parentLevel, level); + updatePath = !newPath.equals(path); + if(updatePath) { + ((TaxonomyLevelImpl)level).setMaterializedPathIdentifiers(newPath); + } + } + ((TaxonomyLevelImpl)level).setLastModified(new Date()); - return dbInstance.getCurrentEntityManager().merge(level); + TaxonomyLevel mergedLevel = dbInstance.getCurrentEntityManager().merge(level); + + if(updatePath) { + List<TaxonomyLevel> descendants = getDescendants(mergedLevel, mergedLevel.getTaxonomy()); + for(TaxonomyLevel descendant:descendants) { + String descendantPath = descendant.getMaterializedPathIdentifiers(); + if(descendantPath.indexOf(path) == 0) { + String end = descendantPath.substring(path.length(), descendantPath.length()); + String updatedPath = newPath + end; + ((TaxonomyLevelImpl)descendant).setMaterializedPathIdentifiers(updatedPath); + } + dbInstance.getCurrentEntityManager().merge(descendant); + } + } + return mergedLevel; + } + + public boolean delete(TaxonomyLevelRef taxonomyLevel) { + if(!hasChildren(taxonomyLevel) && !hasItemUsing(taxonomyLevel) &&!hasCompetenceUsing(taxonomyLevel)) { + TaxonomyLevel impl = loadByKey(taxonomyLevel.getKey()); + if(impl != null) { + dbInstance.getCurrentEntityManager().remove(impl); + } + return true; + } + return false; + } + + public boolean hasItemUsing(TaxonomyLevelRef taxonomyLevel) { + String sb = "select item.key from questionitem item where item.taxonomyLevel.key=:taxonomyLevelKey"; + List<Long> items = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Long.class) + .setParameter("taxonomyLevelKey", taxonomyLevel.getKey()) + .setFirstResult(0) + .setMaxResults(1) + .getResultList(); + return items != null && items.size() > 0 && items.get(0) != null && items.get(0).intValue() > 0; + } + + public boolean hasCompetenceUsing(TaxonomyLevelRef taxonomyLevel) { + String sb = "select competence.key from ctaxonomycompetence competence where competence.taxonomyLevel.key=:taxonomyLevelKey"; + List<Long> comptences = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Long.class) + .setParameter("taxonomyLevelKey", taxonomyLevel.getKey()) + .setFirstResult(0) + .setMaxResults(1) + .getResultList(); + return comptences != null && comptences.size() > 0 && comptences.get(0) != null && comptences.get(0).intValue() > 0; + } + + public boolean hasChildren(TaxonomyLevelRef taxonomyLevel) { + String sb = "select level.key from ctaxonomylevel as level where level.parent.key=:taxonomyLevelKey"; + List<Long> children = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Long.class) + .setParameter("taxonomyLevelKey", taxonomyLevel.getKey()) + .setFirstResult(0) + .setMaxResults(1) + .getResultList(); + return children != null && children.size() > 0 && children.get(0) != null && children.get(0).intValue() > 0; } public VFSContainer getDocumentsLibrary(TaxonomyLevel level) { diff --git a/src/main/java/org/olat/modules/taxonomy/manager/TaxonomyServiceImpl.java b/src/main/java/org/olat/modules/taxonomy/manager/TaxonomyServiceImpl.java index e1bd4b0a628c9ffa8c07faef35441e30b3f31d71..e23ebb543b04ba2be4d354df31848ce9b46c7781 100644 --- a/src/main/java/org/olat/modules/taxonomy/manager/TaxonomyServiceImpl.java +++ b/src/main/java/org/olat/modules/taxonomy/manager/TaxonomyServiceImpl.java @@ -120,6 +120,11 @@ public class TaxonomyServiceImpl implements TaxonomyService { return taxonomyLevelDao.updateTaxonomyLevel(level); } + @Override + public boolean deleteTaxonomyLevel(TaxonomyLevelRef taxonomyLevel) { + return taxonomyLevelDao.delete(taxonomyLevel); + } + @Override public VFSContainer getDocumentsLibrary(TaxonomyLevel level) { return taxonomyLevelDao.getDocumentsLibrary(level); diff --git a/src/test/java/org/olat/modules/taxonomy/manager/TaxonomyLevelDAOTest.java b/src/test/java/org/olat/modules/taxonomy/manager/TaxonomyLevelDAOTest.java index c00403a398fbfd39a1cccda356c558423ee631a9..f81f1752d262284a09d8b23d760b3ff3d7719cd5 100644 --- a/src/test/java/org/olat/modules/taxonomy/manager/TaxonomyLevelDAOTest.java +++ b/src/test/java/org/olat/modules/taxonomy/manager/TaxonomyLevelDAOTest.java @@ -153,6 +153,24 @@ public class TaxonomyLevelDAOTest extends OlatTestCase { Assert.assertEquals(level_5, levels.get(4)); } + @Test + public void getDescendants() { + Taxonomy taxonomy = taxonomyDao.createTaxonomy("ID-103b", "Deeply leveled taxonomy", null, null); + TaxonomyLevel level_1 = taxonomyLevelDao.createTaxonomyLevel("L-1", "A level", "A basic level", null, null, null, null, taxonomy); + TaxonomyLevel level_2 = taxonomyLevelDao.createTaxonomyLevel("L-2", "A level", "A basic level", null, null, level_1, null, taxonomy); + TaxonomyLevel level_3 = taxonomyLevelDao.createTaxonomyLevel("L-3", "A level", "A basic level", null, null, level_2, null, taxonomy); + TaxonomyLevel level_4 = taxonomyLevelDao.createTaxonomyLevel("L-4", "A level", "A basic level", null, null, level_3, null, taxonomy); + TaxonomyLevel level_5 = taxonomyLevelDao.createTaxonomyLevel("L-5", "A level", "A basic level", null, null, level_4, null, taxonomy); + dbInstance.commit(); + + List<TaxonomyLevel> levels = taxonomyLevelDao.getDescendants(level_2, taxonomy); + Assert.assertNotNull(levels); + Assert.assertEquals(3, levels.size()); + Assert.assertTrue(levels.contains(level_3)); + Assert.assertTrue(levels.contains(level_4)); + Assert.assertTrue(levels.contains(level_5)); + } + @Test public void getLevelsByExternalId() { Taxonomy taxonomy = taxonomyDao.createTaxonomy("ID-103", "Externalized taxonomy", null, null); @@ -178,4 +196,56 @@ public class TaxonomyLevelDAOTest extends OlatTestCase { Assert.assertEquals(1, levels.size()); Assert.assertEquals(level, levels.get(0)); } + + @Test + public void updateTaxonomyLevel_simple() { + Taxonomy taxonomy = taxonomyDao.createTaxonomy("ID-105", "Updated taxonomy", null, null); + String displayName = UUID.randomUUID().toString(); + TaxonomyLevel level1 = taxonomyLevelDao.createTaxonomyLevel("U-1", displayName, "A basic level", null, null, null, null, taxonomy); + TaxonomyLevel level2 = taxonomyLevelDao.createTaxonomyLevel("U-2", displayName, "A basic level", null, null, level1, null, taxonomy); + dbInstance.commitAndCloseSession(); + + TaxonomyLevel reloadedLevel2 = taxonomyLevelDao.loadByKey(level2.getKey()); + reloadedLevel2.setDisplayName("Updated"); + reloadedLevel2.setIdentifier("UU"); + TaxonomyLevel updatedLevel2 = taxonomyLevelDao.updateTaxonomyLevel(reloadedLevel2); + dbInstance.commitAndCloseSession(); + + String identifiersPath = updatedLevel2.getMaterializedPathIdentifiers(); + Assert.assertEquals("/U-1/UU/", identifiersPath); + } + + @Test + public void updateTaxonomyLevel_withChildren() { + Taxonomy taxonomy = taxonomyDao.createTaxonomy("ID-105", "Updated taxonomy", null, null); + String displayName = UUID.randomUUID().toString(); + TaxonomyLevel level1 = taxonomyLevelDao.createTaxonomyLevel("U-1", displayName, "A basic level", null, null, null, null, taxonomy); + TaxonomyLevel level2 = taxonomyLevelDao.createTaxonomyLevel("U-2", displayName, "A basic level", null, null, level1, null, taxonomy); + TaxonomyLevel level3 = taxonomyLevelDao.createTaxonomyLevel("U-3", displayName, "A basic level", null, null, level2, null, taxonomy); + TaxonomyLevel level4_1 = taxonomyLevelDao.createTaxonomyLevel("U-4-1", displayName, "A basic level", null, null, level3, null, taxonomy); + TaxonomyLevel level4_2 = taxonomyLevelDao.createTaxonomyLevel("U-4-2", displayName, "A basic level", null, null, level3, null, taxonomy); + dbInstance.commitAndCloseSession(); + + TaxonomyLevel reloadedLevel2 = taxonomyLevelDao.loadByKey(level2.getKey()); + reloadedLevel2.setDisplayName("Updated"); + reloadedLevel2.setIdentifier("UBU"); + TaxonomyLevel updatedLevel2 = taxonomyLevelDao.updateTaxonomyLevel(reloadedLevel2); + dbInstance.commitAndCloseSession(); + + //check the different levels + String identifiersPath2 = updatedLevel2.getMaterializedPathIdentifiers(); + Assert.assertEquals("/U-1/UBU/", identifiersPath2); + + TaxonomyLevel updatedLevel3 = taxonomyLevelDao.loadByKey(level3.getKey()); + String identifiersPath3 = updatedLevel3.getMaterializedPathIdentifiers(); + Assert.assertEquals("/U-1/UBU/U-3/", identifiersPath3); + + TaxonomyLevel updatedLevel4_1 = taxonomyLevelDao.loadByKey(level4_1.getKey()); + String identifiersPath4_1 = updatedLevel4_1.getMaterializedPathIdentifiers(); + Assert.assertEquals("/U-1/UBU/U-3/U-4-1/", identifiersPath4_1); + + TaxonomyLevel updatedLevel4_2 = taxonomyLevelDao.loadByKey(level4_2.getKey()); + String identifiersPath4_2 = updatedLevel4_2.getMaterializedPathIdentifiers(); + Assert.assertEquals("/U-1/UBU/U-3/U-4-2/", identifiersPath4_2); + } }