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;
}
}