package org.zstack.tag; import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.orm.jpa.JpaSystemException; import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.annotation.Transactional; import org.zstack.core.Platform; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SimpleQuery; import org.zstack.core.db.SimpleQuery.Op; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.tag.*; import org.zstack.utils.DebugUtils; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; import javax.persistence.Tuple; import javax.persistence.TypedQuery; import java.util.*; import static java.util.Arrays.asList; /** */ @Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) public class SystemTag { private static final CLogger logger = Utils.getLogger(SystemTag.class); @Autowired protected DatabaseFacade dbf; // TagManager must be explicitly set. use @Autowired will cause circular dependency protected TagManagerImpl tagMgr; protected String tagFormat; protected Class resourceClass; protected List<SystemTagValidator> validators = new ArrayList<>(); protected List<SystemTagLifeCycleListener> lifeCycleListeners = new ArrayList<>(); protected List<SystemTagOperationJudger> judgers = new ArrayList<>(); public SystemTag(String tagFormat, Class resourceClass) { this.tagFormat = tagFormat; this.resourceClass = resourceClass; } public enum SystemTagOperation { CREATE, UPDATE, DELETE } public SystemTag installLifeCycleListener(SystemTagLifeCycleListener listener) { lifeCycleListeners.add(listener); return this; } public SystemTag installJudger(SystemTagOperationJudger judger) { judgers.add(judger); return this; } void callCreatedJudger(SystemTagInventory tag) { for (SystemTagOperationJudger j : judgers) { j.tagPreCreated(tag); } } void callTagCreatedListener(SystemTagInventory tag) { for (SystemTagLifeCycleListener ext : lifeCycleListeners) { ext.tagCreated(tag); } } void callDeletedJudger(SystemTagInventory tag) { for (SystemTagOperationJudger j : judgers) { j.tagPreDeleted(tag); } } void callTagDeletedListener(SystemTagInventory tag) { for (SystemTagLifeCycleListener ext : lifeCycleListeners) { ext.tagDeleted(tag); } } void callUpdatedJudger(SystemTagInventory old, SystemTagInventory newTag) { for (SystemTagOperationJudger j : judgers) { j.tagPreUpdated(old, newTag); } } void callTagUpdatedListener(SystemTagInventory old, SystemTagInventory newTag) { for (SystemTagLifeCycleListener ext : lifeCycleListeners) { ext.tagUpdated(old, newTag); } } protected String useTagFormat() { return tagFormat; } protected Op useOp() { return Op.EQ; } public boolean hasTag(String resourceUuid) { return hasTag(resourceUuid, resourceClass); } public boolean hasTag(String resourceUuid, Class resourceClass) { SimpleQuery<SystemTagVO> q = dbf.createQuery(SystemTagVO.class); q.add(SystemTagVO_.resourceType, Op.EQ, resourceClass.getSimpleName()); q.add(SystemTagVO_.resourceUuid, Op.EQ, resourceUuid); q.add(SystemTagVO_.tag, useOp(), useTagFormat()); return q.isExists(); } public void copy(String srcUuid, Class srcClass, String dstUuid, Class dstClass) { SimpleQuery<SystemTagVO> q = dbf.createQuery(SystemTagVO.class); q.add(SystemTagVO_.resourceType, Op.EQ, srcClass.getSimpleName()); q.add(SystemTagVO_.resourceUuid, Op.EQ, srcUuid); q.add(SystemTagVO_.tag, useOp(), useTagFormat()); List<SystemTagVO> tags = q.list(); for (SystemTagVO tag : tags) { if (tag.isInherent()) { deleteInherentTag(dstUuid, dstClass); tagMgr.createInherentSystemTag(dstUuid, tag.getTag(), dstClass.getSimpleName()); } else { delete(dstUuid, dstClass); tagMgr.createNonInherentSystemTag(dstUuid, tag.getTag(), dstClass.getSimpleName()); } } } public List<String> getTags(String resourceUuid, Class resourceClass) { SimpleQuery<SystemTagVO> q = dbf.createQuery(SystemTagVO.class); q.select(SystemTagVO_.tag); q.add(SystemTagVO_.resourceType, Op.EQ, resourceClass.getSimpleName()); q.add(SystemTagVO_.resourceUuid, Op.EQ, resourceUuid); q.add(SystemTagVO_.tag, useOp(), useTagFormat()); return q.listValue(); } public List<String> getTags(String resourceUuid) { return getTags(resourceUuid, resourceClass); } public String getTag(String resourceUuid, Class resourceClass) { List<String> tags = getTags(resourceUuid, resourceClass); if (tags.isEmpty()) { return null; } return tags.get(0); } public String getTag(String resourceUuid) { return getTag(resourceUuid, resourceClass); } public Map<String, List<String>> getTags(List<String> resourceUuids, Class resourceClass) { SimpleQuery<SystemTagVO> q = dbf.createQuery(SystemTagVO.class); q.select(SystemTagVO_.tag, SystemTagVO_.resourceUuid); q.add(SystemTagVO_.resourceType, Op.EQ, resourceClass.getSimpleName()); q.add(SystemTagVO_.resourceUuid, Op.IN, resourceUuids); q.add(SystemTagVO_.tag, useOp(), useTagFormat()); List<Tuple> ts = q.listTuple(); Map<String, List<String>> ret = new HashMap<>(); for (Tuple t : ts) { String uuid = t.get(1, String.class); List<String> tags = ret.get(uuid); if (tags == null) { tags = new ArrayList<>(); ret.put(uuid, tags); } tags.add(t.get(0, String.class)); } return ret; } public Map<String, List<String>> getTags(List<String> resourceUuids) { DebugUtils.Assert(!resourceUuids.isEmpty(), "how can you pass an empty resourceUuids"); return getTags(resourceUuids, resourceClass); } public String getTagFormat() { return tagFormat; } public Class getResourceClass() { return resourceClass; } public void delete(String resourceUuid, Class resourceClass) { tagMgr.deleteSystemTag(tagFormat, resourceUuid, resourceClass.getSimpleName(), false); } public void delete(String resourceUuid) { tagMgr.deleteSystemTag(tagFormat, resourceUuid, resourceClass.getSimpleName(), false); } public void deleteInherentTag(String resourceUuid) { tagMgr.deleteSystemTag(tagFormat, resourceUuid, resourceClass.getSimpleName(), true); } public void deleteInherentTag(String resourceUuid, Class resourceClass) { tagMgr.deleteSystemTag(tagFormat, resourceUuid, resourceClass.getSimpleName(), true); } public String instantiateTag(Map tokens) { return tagFormat; } public SystemTagCreator newSystemTagCreator(String resUuid) { SystemTag self = this; return new SystemTagCreator() { private boolean exceptionCanBeIgnored; @Override public SystemTagInventory create() { try { return doCreate(); } catch (TransactionSystemException e) { if (exceptionCanBeIgnored) { return null; } else { throw e; } } } @Override public void setTagByTokens(Map tokens) { tag = instantiateTag(tokens); } @Transactional private SystemTagInventory doCreate() { build(); tagMgr.validateSystemTag(resourceUuid, resourceClass.getSimpleName(), tag); if (recreate) { String sql; if (useOp() == Op.LIKE) { sql = "select t from SystemTagVO t where t.tag like :tag" + " and t.resourceUuid = :resUuid and t.inherent = :inherent" + " and t.resourceType = :resType"; } else { sql = "select t from SystemTagVO t where t.tag = :tag" + " and t.resourceUuid = :resUuid and t.inherent = :inherent" + " and t.resourceType = :resType"; } TypedQuery<SystemTagVO> q = dbf.getEntityManager().createQuery(sql, SystemTagVO.class); q.setParameter("resUuid", resourceUuid); q.setParameter("tag", useTagFormat()); q.setParameter("inherent", inherent); q.setParameter("resType", resourceClass.getSimpleName()); List<SystemTagVO> vos = q.getResultList(); List<SystemTagInventory> invs = SystemTagInventory.valueOf(vos); for (SystemTagInventory inv : invs) { tagMgr.preTagDeleted(inv); } for (SystemTagVO vo : vos) { dbf.getEntityManager().remove(vo); } dbf.getEntityManager().flush(); tagMgr.fireTagDeleted(invs); } String uuid; if (unique) { String key = String.format("%s-%s-%s", resourceUuid, resourceClass.getSimpleName(), tag); uuid = UUID.nameUUIDFromBytes(key.getBytes()).toString().replaceAll("-", ""); } else { uuid = Platform.getUuid(); } /** * fix issues 1970 * * Check whether the data exists, if there is no longer insert (another recreate for true will not touch this logic, it will not change the original code behavior), * This reduces the vast majority of Hibernate redundant error logs. * * Background: If we insert duplicate data, hibernate will produce an error log, resulting in a large number of redundant error log. */ String sql = "select count(t) from SystemTagVO t where t.uuid = :uuid"; TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class); q.setParameter("uuid", uuid); if(q.getSingleResult() > 0){ // tag exists if (!ignoreIfExisting) { throw new CloudRuntimeException(String.format("duplicate system tag[uuid: %s]", uuid)); } else { exceptionCanBeIgnored = true; return null; } } SystemTagVO vo = new SystemTagVO(); vo.setResourceType(resourceClass.getSimpleName()); vo.setUuid(uuid); vo.setResourceUuid(resourceUuid); vo.setInherent(inherent); vo.setTag(tag); vo.setType(TagType.System); tagMgr.preTagCreated(SystemTagInventory.valueOf(vo)); try { // If execution fails, hibernate will generate an error log dbf.getEntityManager().persist(vo); dbf.getEntityManager().flush(); dbf.getEntityManager().refresh(vo); } catch (JpaSystemException e) { if (e.getRootCause() instanceof MySQLIntegrityConstraintViolationException && e.getRootCause().getMessage().contains("Duplicate entry")) { // tag exists if (!ignoreIfExisting) { throw new CloudRuntimeException(String.format("duplicate system tag[resourceUuid: %s," + "resourceType: %s, tag: %s", resourceUuid, resourceClass.getSimpleName(), tag), e); } else { logger.debug(String.format("please ignore this error log: Duplicate entry '%s' for key 'PRIMARY'", uuid)); exceptionCanBeIgnored = true; return null; } } } SystemTagInventory inv = SystemTagInventory.valueOf(vo); tagMgr.fireTagCreated(asList(inv)); return inv; } private void build() { resourceUuid = resUuid; if (resourceClass == null) { resourceClass = self.resourceClass; } if (tag == null) { tag = getTagFormat(); } } }; } public SystemTag installValidator(SystemTagValidator validator) { validators.add(validator); return this; } public boolean isMatch(String tag) { return tagFormat.equals(tag); } void validate(String resourceUuid, Class resourceType, String tag) { for (SystemTagValidator validator : validators) { validator.validateSystemTag(resourceUuid, resourceType, tag); } } public SystemTagInventory updateByTagUuid(String tagUuid, String newTag) { return tagMgr.updateSystemTag(tagUuid, newTag); } public SystemTagInventory update(String resourceUuid, String newTag) { SimpleQuery<SystemTagVO> q = dbf.createQuery(SystemTagVO.class); q.select(SystemTagVO_.uuid); q.add(SystemTagVO_.resourceType, Op.EQ, resourceClass.getSimpleName()); q.add(SystemTagVO_.resourceUuid, Op.EQ, resourceUuid); q.add(SystemTagVO_.tag, useOp(), useTagFormat()); String tagUuid = q.findValue(); if (tagUuid == null) { return null; } return tagMgr.updateSystemTag(tagUuid, newTag); } void setTagMgr(TagManager tagMgr) { this.tagMgr = (TagManagerImpl) tagMgr; } public List<SystemTagValidator> getValidators() { return validators; } public void setValidators(List<SystemTagValidator> validators) { this.validators = validators; } }