/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2009], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.authz.server.session; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hyperic.hibernate.PageInfo; import org.hyperic.hq.appdef.shared.AppdefEntityConstants; import org.hyperic.hq.authz.server.session.ResourceGroup.ResourceGroupCreateInfo; import org.hyperic.hq.authz.shared.AuthzConstants; import org.hyperic.hq.authz.shared.GroupCreationException; import org.hyperic.hq.authz.shared.PermissionManager; import org.hyperic.hq.authz.shared.PermissionManagerFactory; import org.hyperic.hq.common.SystemException; import org.hyperic.hq.dao.HibernateDAO; import org.hyperic.util.Transformer; import org.hyperic.util.pager.PageList; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @Repository public class ResourceGroupDAO extends HibernateDAO<ResourceGroup> { private static final Log _log = LogFactory.getLog(ResourceGroupDAO.class.getName()); private static final Integer rootResourceGroupId = AuthzConstants.rootResourceGroupId; @Autowired private ResourceDAO rDao; @Autowired private ResourceTypeDAO resourceTypeDAO; @Autowired public ResourceGroupDAO(SessionFactory sessionFactory) { super(ResourceGroup.class, sessionFactory); } private void assertNameConstraints(String name) throws GroupCreationException { if ((name == null) || (name.length() == 0) || (name.length() > 100)) { throw new GroupCreationException("Group name must be between " + "1 and 100 characters in length"); } } private void assertDescriptionConstraints(String desc) throws GroupCreationException { if ((desc != null) && (desc.length() > 100)) { throw new GroupCreationException("Group description must be " + "between 1 and 100 characters in length"); } } private void assertLocationConstraints(String loc) throws GroupCreationException { if ((loc != null) && (loc.length() > 100)) { throw new GroupCreationException("Group location must be " + "between 1 and 100 characters in length"); } } /** * @param groupResource - Typically this param is null and the behavior is that a resource is create of authzGroup * type. Only used when associating a group with an existing resource. */ ResourceGroup create(AuthzSubject creator, ResourceGroupCreateInfo cInfo, Collection<Resource> resources, Collection<Role> roles, Resource groupResource) throws GroupCreationException { if (groupResource == null) { // name is not persisted if the groupResource != null assertNameConstraints(cInfo.getName()); } assertDescriptionConstraints(cInfo.getDescription()); assertLocationConstraints(cInfo.getLocation()); switch (cInfo.getGroupType()) { case AppdefEntityConstants.APPDEF_TYPE_GROUP_ADHOC_APP: case AppdefEntityConstants.APPDEF_TYPE_GROUP_ADHOC_GRP: case AppdefEntityConstants.APPDEF_TYPE_GROUP_ADHOC_PSS: if (cInfo.getResourcePrototype() != null) { throw new GroupCreationException("Cannot specify a prototype for mixed groups"); } break; case AppdefEntityConstants.APPDEF_TYPE_GROUP_COMPAT_PS: case AppdefEntityConstants.APPDEF_TYPE_GROUP_COMPAT_SVC: if (cInfo.getResourcePrototype() == null) { throw new GroupCreationException("Compatable groups must specify a prototype"); } break; } ResourceGroup resGrp = cInfo.getResourceGroup(creator); ResourceType resType = resourceTypeDAO.findById(AuthzConstants.authzGroup); assert resType != null; final Resource proto = rDao.findById(AuthzConstants.rootResourceId); Resource r = null; if (groupResource == null) { r = cInfo.isPrivateGroup() ? rDao.createPrivate(resType, proto, cInfo.getName(), creator, resGrp.getId(), cInfo.isSystem()) : rDao.create(resType, proto, cInfo.getName(), creator, resGrp.getId(), cInfo.isSystem()); } else { r = groupResource; } resGrp.setResource(r); save(resGrp); /* * The following oddity is needed because the above rDao.create() * flushes the session. If we don't refresh the object, then changing * the instanceId here doens't seem to do anything. This is definitely a * hacky workaround for a Hibernate issue. */ r = rDao.findById(r.getId()); getSession().refresh(r); if (groupResource == null) { r.setInstanceId(resGrp.getId()); } getSession().saveOrUpdate(r); flushSession(); setMembers(resGrp, new HashSet<Resource>(resources)); resGrp.setRoles(new HashSet<Role>(roles)); return resGrp; } ResourceGroup findResourceGroup(Resource resource) { final String hql = "from ResourceGroup where resource = :resource"; return (ResourceGroup) createQuery(hql).setParameter("resource", resource).uniqueResult(); } void removeAllMembers(ResourceGroup group) { // Don't want to mark the Root Resource Group dirty to avoid optimistic // locking issues. Since the root group is associated with all // resources, transactions which involve creating/deleting resources // are not self-contained and therefore any changes to this object // would make these types of transactions potentially fail. if (!group.getId().equals(rootResourceGroupId)) { group.markDirty(); } createQuery("delete from GroupMember g " + "where g.group = :group").setParameter("group", group).executeUpdate(); } boolean isMember(ResourceGroup group, Resource resource) { GroupMember gm = (GroupMember) createQuery( "from GroupMember g where g.group = :group " + " and g.resource = :resource") .setParameter("group", group).setParameter("resource", resource).uniqueResult(); return gm != null; } @SuppressWarnings("unchecked") void removeMembers(ResourceGroup group, Collection<Resource> members) { if (group == null || members == null || members.isEmpty()) { return; } // Don't want to mark the Root Resource Group dirty to avoid optimistic // locking issues. Since the root group is associated with all // resources, transactions which involve creating/deleting resources // are not self-contained and therefore any changes to this object // would make these types of transactions potentially fail. if (!group.getId().equals(rootResourceGroupId)) { group.markDirty(); } final List<Integer> memberIds = new Transformer<Resource, Integer>() { @Override public Integer transform(Resource r) { return r.getId(); } }.transform(members); final Query query = createQuery("from GroupMember where group = :group and resource.id in (:members)"); final int size = memberIds.size(); final List<GroupMember> toDelete = new ArrayList<GroupMember>(); for (int i=0; i<size; i+=BATCH_SIZE) { final int end = Math.min(i+BATCH_SIZE, size); final List<Integer> list = memberIds.subList(i, end); toDelete.addAll(query.setParameter("group", group) .setParameterList("members", list) .list()); } for (final GroupMember member : toDelete) { getSession().delete(member); } } void addMember(ResourceGroup group, Resource resource) { addMembers(group, Collections.singleton(resource)); } void addMembers(ResourceGroup group, Collection<Resource> resources) { Session sess = getSession(); // Don't want to mark the Root Resource Group dirty to avoid optimistic // locking issues. Since the root group is associated with all // resources, transactions which involve creating/deleting resources // are not self-contained and therefore any changes to this object // would make these types of transactions potentially fail. if (!group.getId().equals(rootResourceGroupId)) { group.markDirty(); } for (Resource r : resources) { GroupMember m = new GroupMember(group, r); sess.save(m); } } void setMembers(ResourceGroup group, Collection<Resource> resources) { removeAllMembers(group); addMembers(group, resources); } /** * Get groups that a resource belongs to via the persistence mechanism (i.e. * mapping table) * * @return {@link ResourceGroup}s */ @SuppressWarnings("unchecked") Collection<ResourceGroup> getGroups(Resource r) { return createQuery( "select g.group from GroupMember g " + "where g.resource = :resource").setParameter( "resource", r).list(); } /** * Get resources belonging to a group via the persistence mechanism. * * @return {@link Resource}s */ int getNumMembers(ResourceGroup g) { String hql = "select count(g.resource) from GroupMember g " + "where g.group = :group and g.resource.resourceType is not null"; return ((Number) createQuery(hql).setParameter("group", g).uniqueResult()).intValue(); } /** * Get resources belonging to a group via the persistence mechanism. * * @return {@link Resource}s */ @SuppressWarnings("unchecked") List<Resource> getMembers(ResourceGroup g) { String hql = "select g.resource from GroupMember g " + "where g.group = :group and g.resource.resourceType is not null order by g.resource.name"; return createQuery(hql).setParameter("group", g).list(); } @SuppressWarnings("unchecked") List<Resource> getMembers(Collection<ResourceGroup> groups) { if ((groups == null) || groups.isEmpty()) { return Collections.emptyList(); } final String hql = "select g.resource.id from GroupMember g where g.group in (:groups)"; final List<Integer> resourceIds = createQuery(hql).setParameterList("groups", groups).list(); final List<Resource> rtn = new ArrayList<Resource>(resourceIds.size()); for (final Integer resourceId : resourceIds) { final Resource resource = rDao.get(resourceId); if ((resource == null) || resource.isInAsyncDeleteState()) { continue; } rtn.add(resource); } return rtn; } /** * Get counts of resources mapped by type name. * * @return {@link Resource}s */ @SuppressWarnings("unchecked") Map<String, Number> getMemberTypes(ResourceGroup g) { List<Object[]> counts = createQuery( "select p.name, count(r) from GroupMember g " + "join g.resource r " + "join r.prototype p " + "where g.group = :group group by p.name").setParameter( "group", g).list(); Map<String, Number> types = new HashMap<String, Number>(); for (Object[] objs : counts) { types.put((String) objs[0], (Number) objs[1]); } return types; } @Override public void remove(ResourceGroup entity) { remove(entity, false); } /** * @param removeResource true when the group is associated with a resource that belongs to another first class * entity */ void remove(ResourceGroup entity, boolean removeResource) { // remove all roles entity.getRoles().clear(); removeAllMembers(entity); Resource res = entity.getResource(); // remove this resourceGroup itself super.remove(entity); flushSession(); if (removeResource) { rDao.remove(res); } } public ResourceGroup findRootGroup() { ResourceGroup res = findByName(AuthzConstants.rootResourceGroupName); if (res == null) { throw new SystemException("Root group should exist"); } return res; } public ResourceGroup findByName(String name) { String sql = "from ResourceGroup g where lower(g.resource.name) = lower(?) " + "AND g.resource.resourceType.id = :groupType"; return (ResourceGroup) getSession().createQuery(sql) .setString(0, name).setCacheable(true) .setInteger("groupType", AuthzConstants.authzGroup) .setCacheable(true) .setCacheRegion("ResourceGroup.findByName") .uniqueResult(); } @SuppressWarnings("unchecked") public Collection<ResourceGroup> findByRoleIdAndSystem_orderName(Integer roleId, boolean system, boolean asc) { String sql = "select g from ResourceGroup g join g.roles r " + "where r.id = ? and g.system = ? " + "order by g.resource.sortName " + (asc ? "asc" : "desc"); return getSession().createQuery(sql).setInteger(0, roleId.intValue()).setBoolean(1, system).list(); } @SuppressWarnings("unchecked") public Collection<ResourceGroup> findByRolesAndGroupTypeAndSystem(Collection<Role> roles, int groupType, boolean system) { if ((roles==null) || roles.isEmpty()){ return Collections.emptyList(); } String sql = "select g from ResourceGroup g join g.roles r " + "where r in (:roles) and g.groupType = :type and g.system = :system "; return getSession().createQuery(sql) .setParameterList("roles", roles) .setInteger("type", groupType) .setBoolean("system", system).list(); } @SuppressWarnings("unchecked") public Collection<ResourceGroup> findWithNoRoles_orderName(boolean asc) { String sql = "from ResourceGroup g " + "where g.roles.size = 0 and g.system = false " + "order by g.resource.sortName " + (asc ? "asc" : "desc"); return getSession().createQuery(sql).list(); } @SuppressWarnings("unchecked") public Collection<ResourceGroup> findByNotRoleId_orderName(Integer roleId, boolean asc) { return getSession().createQuery( "from ResourceGroup g " + "where ? not in (select id from g.roles) and " + "g.system = false order by g.resource.sortName " + (asc ? "asc" : "desc")) .setInteger(0, roleId.intValue()).list(); } @SuppressWarnings("unchecked") public Collection<ResourceGroup> findCompatible(Resource proto) { String sql = "from ResourceGroup g " + "where g.resourcePrototype = ? and " + "(g.groupType = ? or g.groupType = ?)"; return getSession().createQuery(sql).setParameter(0, proto) .setInteger(1, AppdefEntityConstants.APPDEF_TYPE_GROUP_COMPAT_PS).setInteger(2, AppdefEntityConstants.APPDEF_TYPE_GROUP_COMPAT_SVC).list(); } @SuppressWarnings("unchecked") PageList<ResourceGroup> findGroupsClusionary(AuthzSubject subject, Resource member, Resource prototype, Collection<ResourceGroup> excludeGroups, PageInfo pInfo, boolean inclusive, boolean includeDynamicGroup) { ResourceGroupSortField sort = (ResourceGroupSortField) pInfo.getSort(); String hql = "from ResourceGroup g where g.system = false and "; if (prototype != null) { hql += " (g.resourcePrototype = :proto "; // Mixed groups, too Integer protoType = prototype.getResourceType().getId(); if (protoType.equals(AuthzConstants.authzPlatformProto) || protoType.equals(AuthzConstants.authzServerProto) || protoType.equals(AuthzConstants.authzServiceProto)) { hql += " or g.groupType = " + AppdefEntityConstants.APPDEF_TYPE_GROUP_ADHOC_PSS; } else if (protoType.equals(AuthzConstants.authzApplicationProto)) { hql += " or g.groupType = " + AppdefEntityConstants.APPDEF_TYPE_GROUP_ADHOC_APP; } hql += ") and "; } List<Integer> excludes = new ArrayList<Integer>(excludeGroups.size()); for (ResourceGroup g : excludeGroups) { excludes.add(g.getId()); } if (!excludes.isEmpty()) { hql += " g.id not in (:excludes) and "; } String inclusionStr = ""; if (!inclusive) { inclusionStr = " not "; } PermissionManager pm = PermissionManagerFactory.getInstance(); hql += inclusionStr + " exists ( " + " select m.id from GroupMember m " + " where m.resource = :resource and m.group = g " + ") "; String pmql = pm.getOperableGroupsHQL(subject, "g", inclusive ? AuthzConstants.groupOpViewResourceGroup : AuthzConstants.groupOpModifyResourceGroup); if (pmql.length() > 0) { hql += pmql; } if (!includeDynamicGroup) { hql += " and not g.groupType = " + AppdefEntityConstants.APPDEF_TYPE_GROUP_DYNAMIC; } String countHql = "select count(g.id) " + hql; String actualHql = "select g " + hql + " order by " + sort.getSortString("g"); Query q = getSession().createQuery(countHql).setParameter("resource", member); if (!excludes.isEmpty()) { q.setParameterList("excludes", excludes); } if (prototype != null) { q.setParameter("proto", prototype); } if (pmql.length() > 0) { q.setInteger("subjId", subject.getId().intValue()); } int total = ((Number) (q.uniqueResult())).intValue(); q = getSession().createQuery(actualHql).setParameter("resource", member); if (prototype != null) { q.setParameter("proto", prototype); } if (!excludes.isEmpty()) { q.setParameterList("excludes", excludes); } if (pmql.length() > 0) { q.setInteger("subjId", subject.getId().intValue()); } List<ResourceGroup> vals = pInfo.pageResults(q).list(); return new PageList<ResourceGroup>(vals, total); } @SuppressWarnings("unchecked") public Collection<ResourceGroup> findByGroupType(int groupType) { String sql = "from ResourceGroup g where g.groupType = :type"; return getSession().createQuery(sql).setInteger("type", groupType).list(); } @SuppressWarnings("unchecked") public Collection<ResourceGroup> findByGroupType_orderName(boolean isAscending, int groupType) { String sql = "from ResourceGroup g where g.groupType = :type" + " ORDER BY g.resource.name " + ((isAscending) ? "asc" : "desc"); return getSession().createQuery(sql).setInteger("type", groupType).list(); } @SuppressWarnings("unchecked") public Collection<ResourceGroup> findDeletedGroups() { String hql = "from ResourceGroup where resource.resourceType = null"; return createQuery(hql).list(); } @SuppressWarnings("unchecked") List<ResourceGroup> getGroupsByType(int groupType) { String hql = "from ResourceGroup where groupType = :type"; return createQuery(hql).setInteger("type", groupType).list(); } @SuppressWarnings("unchecked") public List<ResourceGroup> getGroupsByTypeAndOwners(int groupType, Collection<AuthzSubject> owners) { if ((owners==null) || owners.isEmpty()){ return Collections.emptyList(); } String hql = "select g from ResourceGroup g join g.resource r " + " where groupType = :type and r.owner in (:owners)"; return createQuery(hql) .setInteger("type", groupType) .setParameterList("owners", owners).list(); } @SuppressWarnings("unchecked") Collection<GroupMember> getOrphanedResourceGroupMembers() { Collection<GroupMember> collection = getOrphanedResourceGroupMembers1(); collection.addAll(getOrphanedResourceGroupMembers2()); collection.addAll(getOrphanedResourceGroupMembers3()); return collection; } @SuppressWarnings("unchecked") Collection<GroupMember> getOrphanedResourceGroupMembers1() { String hql = new StringBuilder(512) .append("from GroupMember g where exists (") .append("SELECT 1 FROM Resource r WHERE r.resourceType.id = :platformType AND g.resource != r ") .append("AND r.instanceId not in (select p.id from Platform p)") .append(")") .toString(); return createQuery(hql) .setInteger("platformType", AuthzConstants.authzPlatform) .list(); } @SuppressWarnings("unchecked") Collection<GroupMember> getOrphanedResourceGroupMembers2() { String hql = new StringBuilder(512) .append("from GroupMember g where exists (") .append("SELECT 1 FROM Resource r WHERE r.resourceType.id = :serverType AND g.resource != r ") .append("AND r.instanceId not in (select s.id from Server s)") .append(")") .toString(); return createQuery(hql) .setInteger("serverType", AuthzConstants.authzServer) .list(); } @SuppressWarnings("unchecked") Collection<GroupMember> getOrphanedResourceGroupMembers3() { String hql = new StringBuilder(512) .append("from GroupMember g where exists (") .append("SELECT 1 FROM Resource r WHERE r.resourceType.id = :serviceType AND g.resource != r ") .append("AND r.instanceId not in (select s.id from Service s)") .append(")") .toString(); return createQuery(hql) .setInteger("serviceType", AuthzConstants.authzService) .list(); } }