package org.ovirt.engine.core.bll; import java.util.List; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.common.action.TagsOperationParameters; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.businessentities.tags; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.LogCompat; import org.ovirt.engine.core.compat.LogFactoryCompat; import org.ovirt.engine.core.compat.Regex; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.dao.TagDAO; import org.ovirt.engine.core.utils.collections.CopyOnAccessMap; enum TagReturnValueIndicator { ID, NAME } /** * This class responsible to In memory Tags handling. On Vdc starting in memory tags tree initialized. All Tags changing * operations go throw this class */ public class TagsDirector { private static LogCompat log = LogFactoryCompat.getLog(TagsDirector.class); protected static final Guid ROOT_TAG_ID = Guid.Empty; /** * In memory nodes cache for quicker access to each node by ID: O(1) instead O(lnN) of tree */ private final java.util.Map<Guid, tags> tagsMapByID = new CopyOnAccessMap<Guid, tags>(new java.util.HashMap<Guid, tags>()); /** * In memory nodes cache for quicker access to each node by name */ private final java.util.Map<String, tags> tagsMapByName = new CopyOnAccessMap<String, tags>(new java.util.HashMap<String, tags>()); private static TagsDirector instance = null; protected TagsDirector() { } /** * In memory tree initialized during initialization */ protected void init() { log.info("TagsDirector initialization"); tags root = new tags("root", null, true, ROOT_TAG_ID, "root"); AddTagToHash(root); AddChildren(root); } private void AddTagToHash(tags tag) { tagsMapByID.put(tag.gettag_id(), tag); tagsMapByName.put(tag.gettag_name(), tag); if (tag.getparent_id() != null) { // If the tag has a parent, the parent should have in its children the added tag instead // of the old version of the tag , if exists tags parentTag = tagsMapByID.get(tag.getparent_id()); if (parentTag == null) { log.error(String.format("Could not obtain tag for guid %1$s", tag.getparent_id())); return; } List<tags> parentChildren = parentTag.getChildren(); replaceTagInChildren(tag, parentChildren); AddTagToHash(parentTag); // replace the parent tag after the modification } } private void replaceTagInChildren(tags tag, List<tags> parentChildren) { for (int counter = 0; counter < parentChildren.size(); counter++) { if (parentChildren.get(counter).gettag_id().equals(tag.gettag_id())) { parentChildren.set(counter, tag); break; } } } private void RemoveTagFromHash(tags tag) { tagsMapByID.remove(tag.gettag_id()); tagsMapByName.remove(tag.gettag_name()); } /** * Recurcive tree initialization call * * @param tag */ private void AddChildren(tags tag) { log.infoFormat("Tag {0} added to tree", tag.gettag_name()); List<tags> children = getTagDAO().getAllForParent(tag.gettag_id()); for (tags child : children) { AddChildren(child); log.infoFormat("Tag {0} added as child to parent {1}", child.gettag_name(), tag.gettag_name()); tag.getChildren().add(child); AddTagToHash(tag); AddTagToHash(child); } } protected TagDAO getTagDAO() { return DbFacade.getInstance().getTagDAO(); } private void RemoveTagAndChildren(tags tag) { for (tags child : tag.getChildren()) { RemoveTagAndChildren(child); } RemoveTagFromHash(tag); } public static TagsDirector getInstance() { // ASSUMPTION: this is not a thread-safe call. We assume first call is run // from Backend.Initialize if (instance == null) { instance = new TagsDirector(); instance.init(); } return instance; } public void AddTag(tags tag) { if (tagsMapByID.containsKey(tag.getparent_id())) { tags parent = tagsMapByID.get(tag.getparent_id()); parent.getChildren().add(tag); AddTagToHash(tag); AddTagToHash(parent); } else { log.errorFormat("Trying to add tag {0}, parent doesn't exist in Data Structure - {1}", tag.gettag_name(), tag.getparent_id()); } } /** * Remove tag operation. For tag with children all tag's children will be removed as well * * @param tagId * tag to remove */ public void RemoveTag(Guid tagId) { if (tagsMapByID.containsKey(tagId)) { tags tag = tagsMapByID.get(tagId); RemoveTagAndChildren(tag); tags parent = tagsMapByID.get(tag.getparent_id()); parent.getChildren().remove(tag); AddTagToHash(parent); } else { log.warnFormat("Trying to remove tag, not exists in Data Structure - {0}", tagId); } } /** * Update tag. We assume that the id doesn't change. * * @param tag */ public void UpdateTag(tags tag) { if (tagsMapByID.containsKey(tag.gettag_id())) { tags tagFromCache = tagsMapByID.get(tag.gettag_id()); String oldName = tagFromCache.gettag_name(); // check if tag name has changed. If it has - modify name dictionary // accordingly: if (!tag.gettag_name().equals(oldName)) { tagsMapByName.remove(oldName); } // Copy the children of the cached tag to keep the object hierarchy consistent. tag.setChildren(tagFromCache.getChildren()); AddTagToHash(tag); } else { log.warnFormat("Trying to update tag, not exists in Data Structure - {0}", tag.gettag_name()); } } public void MoveTag(Guid tagId, Guid newParent) { if (tagsMapByID.containsKey(tagId)) { tags tag = tagsMapByID.get(tagId); if (tagsMapByID.containsKey(newParent)) { if (tagsMapByID.containsKey(tag.getparent_id())) { tags parentTag = tagsMapByID.get(tag.getparent_id()); parentTag.getChildren().remove(tag); AddTagToHash(parentTag); } else { log.warnFormat("Trying to move tag from parent that doesn't exist in Data Structure - {0}", tag.getparent_id()); } tags newParentTag = tagsMapByID.get(newParent); newParentTag.getChildren().add(tag); tag.setparent_id(newParent); AddTagToHash(newParentTag); // Parent got changed, modify it. updateTagInBackend(tag); } else { log.errorFormat("Trying to move tag, to parent not exists in Data Structure - {0}", newParent); } } else { log.errorFormat("Trying to move tag, not exists in Data Structure - {0}", tagId); } } protected void updateTagInBackend(tags tag) { Backend.getInstance().runInternalAction(VdcActionType.UpdateTag, new TagsOperationParameters(tag)); } private String GetTagIdAndParentsIds(tags tag) { StringBuilder builder = new StringBuilder(); builder.append(tag.gettag_id()); Guid tempTagId = new Guid(tag.getparent_id().toString()); while (!tempTagId.equals(Guid.Empty)) { builder.append(String.format(",%1$s", tempTagId)); tag = GetTagById(tempTagId); tempTagId = new Guid(tag.getparent_id().toString()); } return builder.toString(); } /** * This function will return the tag's ID and its parents IDs. * * @param tagId * the tag ID. * @return a comma seperated list of IDs. */ public String GetTagIdAndParentsIds(Guid tagId) { tags tag = GetTagById(tagId); return GetTagIdAndParentsIds(tag); } /** * This function will return the tag's ID and its parents IDs. * * @param tagId * the tag ID. * @return a comma seperated list of IDs. */ public String GetTagIdAndParentsIds(String tagName) { tags tag = GetTagByName(tagName); return GetTagIdAndParentsIds(tag); } /** * This function will return the tag's ID and its children IDs. Its used to determine if a tag is assigned to an * entity. Tag is determined as assigned to an entity if the entity is assigned to the tag or to one of its * children. * * @param tagId * the ID of the 'root' tag. * @return a comma separated list of IDs. */ public String GetTagIdAndChildrenIds(Guid tagId) { tags tag = GetTagById(tagId); if (tag == null) { return StringUtils.EMPTY; } StringBuilder sb = tag.GetTagIdAndChildrenIds(); return sb.toString(); } public String GetTagNameAndChildrenNames(Guid tagId) { tags tag = GetTagById(tagId); StringBuilder sb = tag.GetTagNameAndChildrenNames(); return sb.toString(); } public java.util.HashSet<Guid> GetTagIdAndChildrenIdsAsSet(Guid tagId) { tags tag = GetTagById(tagId); java.util.HashSet<Guid> set = new java.util.HashSet<Guid>(); tag.GetTagIdAndChildrenIdsAsList(set); return set; } /** * This function will return the tag's ID and its children IDs. Its used to determine if a tag is assigned to an * entity. Tag is determined as assigned to an entity if the entity is assigned to the tag or to one of its * children. * * @param tagName * the name of the 'root' tag. * @return a comma separated list of IDs. */ public String GetTagIdAndChildrenIds(String tagName) { tags tag = GetTagByName(tagName); StringBuilder sb = tag.GetTagIdAndChildrenIds(); return sb.toString(); } /** * This function will return the tags IDs of all tags that their names match the specified regular expression and * ALL their children (regardless if the children match the reg-exp or not). Its used to determine if a tag is * assigned to an entity. Tag is determined as assigned to an entity if the entity is assigned to the tag or to one * of its children. * * @param tagName * the name of the 'root' tag. * @return a comma separated list of IDs. */ public String GetTagIdsAndChildrenIdsByRegExp(String tagNameRegExp) { // add RegEx chars or beginning of string ('^') and end of string ('$'): tagNameRegExp = String.format("^%1$s$", tagNameRegExp); // convert to the regular expression format: tagNameRegExp = tagNameRegExp.replace("*", ".*"); StringBuilder sb = new StringBuilder(); RecursiveGetTagsAndChildrenByRegExp(tagNameRegExp, sb, GetRootTag(), TagReturnValueIndicator.ID); return sb.toString(); } public String GetTagNamesAndChildrenNamesByRegExp(String tagNameRegExp) { // add RegEx chars or beginning of string ('^') and end of string ('$'): tagNameRegExp = String.format("^%1$s$", tagNameRegExp); // convert to the regular expression format: tagNameRegExp = tagNameRegExp.replace("*", ".*"); StringBuilder sb = new StringBuilder(); RecursiveGetTagsAndChildrenByRegExp(tagNameRegExp, sb, GetRootTag(), TagReturnValueIndicator.NAME); return sb.toString(); } private void RecursiveGetTagsAndChildrenByRegExp(String tagNameRegExp, StringBuilder sb, tags tag, TagReturnValueIndicator indicator ) { if ((tag.getChildren() != null) && (tag.getChildren().size() > 0)) { for (tags child : tag.getChildren()) { if (Regex.IsMatch(child.gettag_name(), tagNameRegExp)) // the tag matches the regular expression -> add it and all its // children // (we prevent searching a regular expression match on them - // unnecessary). { if (sb.length() == 0) { if (indicator == TagReturnValueIndicator.ID) sb.append(child.GetTagIdAndChildrenIds()); else sb.append(child.GetTagNameAndChildrenNames()); } else { if (indicator == TagReturnValueIndicator.ID) sb.append(String.format(",%1$s", child.GetTagIdAndChildrenIds())); else sb.append(String.format(",%1$s", child.GetTagNameAndChildrenNames())); } } else { RecursiveGetTagsAndChildrenByRegExp(tagNameRegExp, sb, child, indicator); } } } } /** * Get tag from in memory data structure (by ID). This tag will be with all children tree initialized as opposite to * tag from db. * * @param tagId * @return */ public tags GetTagById(Guid tagId) { if (tagsMapByID.containsKey(tagId)) { return tagsMapByID.get(tagId); } else { return null; } } /** * Get tag from in memory data structure (by name). * * @param tagName * @return */ public tags GetTagByName(String tagName) { if (tagsMapByName.containsKey(tagName)) { return tagsMapByName.get(tagName); } else { return null; } } /** * Gets a list of all the tags in the system. * * @return a tags list. */ public java.util.ArrayList<tags> GetAllTags() { java.util.ArrayList<tags> ret = new java.util.ArrayList<tags>(tagsMapByID.values()); // remove the root - it is not a real tag: ret.remove(GetRootTag()); return ret; } /** * Returns the root tag in the system. * * @return the root tag. */ public tags GetRootTag() { return tagsMapByID.get(ROOT_TAG_ID); } public boolean IsTagDescestorOfTag(Guid sourceTagId, Guid potentialDescestorId) { if (sourceTagId.equals(potentialDescestorId)) { return true; } tags tag = GetTagById(sourceTagId); if (tag != null && tag.getChildren() != null) { for (tags childTag : tag.getChildren()) { if (IsTagDescestorOfTag(childTag.gettag_id(), potentialDescestorId)) { return true; } } } return false; } }