Java tutorial
/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.server.resource.group.definition; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.core.domain.criteria.ResourceGroupDefinitionCriteria; import org.rhq.core.domain.resource.group.GroupDefinition; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.core.domain.util.PageControl; import org.rhq.core.domain.util.PageList; import org.rhq.core.server.PersistenceUtility; import org.rhq.core.util.collection.ArrayUtils; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.auth.SubjectManagerLocal; import org.rhq.enterprise.server.authz.AuthorizationManagerLocal; import org.rhq.enterprise.server.authz.PermissionException; import org.rhq.enterprise.server.authz.RequiredPermission; import org.rhq.enterprise.server.resource.ResourceManagerLocal; import org.rhq.enterprise.server.resource.group.RecursivityChangeType; import org.rhq.enterprise.server.resource.group.ResourceGroupDeleteException; import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal; import org.rhq.enterprise.server.resource.group.ResourceGroupUpdateException; import org.rhq.enterprise.server.resource.group.definition.exception.GroupDefinitionAlreadyExistsException; import org.rhq.enterprise.server.resource.group.definition.exception.GroupDefinitionCreateException; import org.rhq.enterprise.server.resource.group.definition.exception.GroupDefinitionDeleteException; import org.rhq.enterprise.server.resource.group.definition.exception.GroupDefinitionException; import org.rhq.enterprise.server.resource.group.definition.exception.GroupDefinitionNotFoundException; import org.rhq.enterprise.server.resource.group.definition.exception.GroupDefinitionUpdateException; import org.rhq.enterprise.server.resource.group.definition.framework.ExpressionEvaluator; import org.rhq.enterprise.server.resource.group.definition.framework.InvalidExpressionException; import org.rhq.enterprise.server.resource.group.definition.mbean.GroupDefinitionRecalculationThreadMonitor; import org.rhq.enterprise.server.resource.group.definition.mbean.GroupDefinitionRecalculationThreadMonitorMBean; import org.rhq.enterprise.server.util.CriteriaQueryGenerator; import org.rhq.enterprise.server.util.CriteriaQueryRunner; @Stateless public class GroupDefinitionManagerBean implements GroupDefinitionManagerLocal { private final Log log = LogFactory.getLog(GroupDefinitionManagerBean.class); @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @EJB private GroupDefinitionManagerLocal groupDefinitionManager; // self, for xactional purposes @EJB private ResourceGroupManagerLocal resourceGroupManager; @EJB private ResourceManagerLocal resourceManager; @EJB private SubjectManagerLocal subjectManager; @EJB private AuthorizationManagerLocal authorizationManager; @SuppressWarnings("unchecked") @RequiredPermission(Permission.MANAGE_INVENTORY) public void recalculateDynaGroups(Subject subject) { Query recalculationFinderQuery = entityManager .createNamedQuery(GroupDefinition.QUERY_FIND_IDS_FOR_RECALCULATION); recalculationFinderQuery.setParameter("now", System.currentTimeMillis()); List<Integer> groupDefinitionIdsToRecalculate = recalculationFinderQuery.getResultList(); if (groupDefinitionIdsToRecalculate.size() == 0) { return; // this will skip the info logging, so we only log when this method does something meaningful } GroupDefinitionRecalculationThreadMonitorMBean monitor = GroupDefinitionRecalculationThreadMonitor .getMBean(); long totalStart = System.currentTimeMillis(); for (Integer groupDefinitionId : groupDefinitionIdsToRecalculate) { long singleStart = System.currentTimeMillis(); boolean success = false; try { groupDefinitionManager.calculateGroupMembership(subject, groupDefinitionId); success = true; } catch (Throwable t) { /* * be paranoid about capturing any and all kinds of errors, to give a chances for * all recalculations to complete in this (heart)beat of the recalculation thread */ log.error("Error recalculating DynaGroups for GroupDefinition[id=" + groupDefinitionId + "]", t); } long singleEnd = System.currentTimeMillis(); try { GroupDefinition groupDefinition = getById(groupDefinitionId); int size = getManagedResourceGroupSizeForGroupDefinition(groupDefinitionId); monitor.updateStatistic(groupDefinition.getName(), size, success, singleEnd - singleStart); } catch (Throwable t) { log.error("Error updating DynaGroup statistics GroupDefinition[id=" + groupDefinitionId + "]", t); // ignore error during statistic update } } long totalEnd = System.currentTimeMillis(); monitor.updateAutoRecalculationThreadTime(totalEnd - totalStart); } public GroupDefinition getById(int groupDefinitionId) throws GroupDefinitionNotFoundException { GroupDefinition groupDefinition = entityManager.find(GroupDefinition.class, groupDefinitionId); if (groupDefinition == null) { throw new GroupDefinitionNotFoundException("Group definition with specified id does not exist"); } return groupDefinition; } @RequiredPermission(Permission.MANAGE_INVENTORY) public GroupDefinition createGroupDefinition(Subject subject, GroupDefinition newGroupDefinition) throws GroupDefinitionAlreadyExistsException, GroupDefinitionCreateException { try { validate(newGroupDefinition, null); } catch (GroupDefinitionException gde) { throw new GroupDefinitionCreateException(gde.getMessage()); } try { entityManager.persist(newGroupDefinition); } catch (Exception e) { throw new GroupDefinitionCreateException(e); } return newGroupDefinition; } @RequiredPermission(Permission.MANAGE_INVENTORY) public GroupDefinition updateGroupDefinition(Subject subject, GroupDefinition groupDefinition) throws GroupDefinitionAlreadyExistsException, GroupDefinitionUpdateException, InvalidExpressionException, ResourceGroupUpdateException { boolean nameChanged = false; try { nameChanged = validate(groupDefinition, groupDefinition.getId()); } catch (GroupDefinitionException gde) { throw new GroupDefinitionUpdateException(gde.getMessage()); } ExpressionEvaluator evaluator = new ExpressionEvaluator(); for (String expression : groupDefinition.getExpressionAsList()) { evaluator.addExpression(expression); } RecursivityChangeType changeType = RecursivityChangeType.None; GroupDefinition attachedGroupDefinition = null; try { attachedGroupDefinition = getById(groupDefinition.getId()); } catch (GroupDefinitionNotFoundException gdnfe) { throw new GroupDefinitionUpdateException(gdnfe.getMessage()); } if (groupDefinition.isRecursive() == true && attachedGroupDefinition.isRecursive() == false) { // making a recursive group into a "normal" group changeType = RecursivityChangeType.AddedRecursion; } else if (groupDefinition.isRecursive() == false && attachedGroupDefinition.isRecursive() == true) { // making a "normal" group into a recursive group changeType = RecursivityChangeType.RemovedRecursion; } else { // recursive bit didn't change } if (nameChanged || changeType != RecursivityChangeType.None) { String oldGroupDefinitionName = attachedGroupDefinition.getName(); Subject overlord = subjectManager.getOverlord(); for (ResourceGroup dynaGroup : attachedGroupDefinition.getManagedResourceGroups()) { String dynaGroupName = dynaGroup.getName(); String newDynaGroupName = updateDynaGroupName(oldGroupDefinitionName, groupDefinition.getName(), dynaGroupName); dynaGroup.setName(newDynaGroupName); // do not set recursive bit here // the update method will figure out whether to flip it by inspecting its managing GroupDefinition //dynaGroup.setRecursive(groupDefinition.isRecursive()); resourceGroupManager.updateResourceGroup(overlord, dynaGroup, changeType); } } // do not call entityManager.merge, it could overwrite managed fields // merge fields explicitly to control precisely which fields get updated attachedGroupDefinition.setName(groupDefinition.getName()); attachedGroupDefinition.setDescription(groupDefinition.getDescription()); attachedGroupDefinition.setRecursive(groupDefinition.isRecursive()); attachedGroupDefinition.setExpression(groupDefinition.getExpression()); attachedGroupDefinition.setRecalculationInterval(groupDefinition.getRecalculationInterval()); return attachedGroupDefinition; } // return boolean indicating whether the name of this group definition is changing private boolean validate(GroupDefinition definition, Integer id) throws GroupDefinitionException, GroupDefinitionAlreadyExistsException { String name = (definition.getName() == null ? "" : definition.getName().trim()); String description = (definition.getDescription() == null ? "" : definition.getDescription().trim()); if (name.equals("")) { throw new GroupDefinitionException("Name is a required property"); } if (name.length() > 100) { throw new GroupDefinitionException("Name is limited to 100 characters"); } if (description.length() > 100) { throw new GroupDefinitionException("Description is limited to 100 characters"); } if (name.contains("<") || name.contains("$") || name.contains("'") || name.contains("{") || name.contains("[")) { throw new GroupDefinitionException("Name must not contain <,$,',[,{ characters"); } Query query = entityManager.createNamedQuery(GroupDefinition.QUERY_FIND_BY_NAME); query.setParameter("name", name); try { GroupDefinition found = (GroupDefinition) query.getSingleResult(); if ((id == null) // null == id means creating new def - so if query has results, it's a dup || (found.getId() != id)) // found != id means updating def - so if query has result, only dup if ids don't match { throw new GroupDefinitionAlreadyExistsException( "GroupDefinition with name " + name + " already exists"); } } catch (NoResultException e) { // user is changing the name of the group, this is OK return true; } return false; } @RequiredPermission(Permission.MANAGE_INVENTORY) @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) // required for the recalculation thread public void calculateGroupMembership(Subject subject, int groupDefinitionId) throws ResourceGroupDeleteException, GroupDefinitionDeleteException, GroupDefinitionNotFoundException, InvalidExpressionException, ResourceGroupUpdateException { /* * even though this method declares to throw it, it should never generate an InvalidExpressionException because * the definition's expression set was validated before it was persisted. conceivably, if the group definition * is persisted without being passed to the validating updateGroupDefinition( GroupDefinition ) method, then it * could throw a validation exception. * * so, callers to this method should catch InvalidExpressionException just in case, and give the user a chance to * correct the expression set before attempting to calculate the effective group again. */ long startTime = System.currentTimeMillis(); GroupDefinition groupDefinition = getById(groupDefinitionId); groupDefinition.setLastCalculationTime(System.currentTimeMillis()); // we're calculating now ExpressionEvaluator evaluator = new ExpressionEvaluator(); for (String expression : groupDefinition.getExpressionAsList()) { evaluator.addExpression(expression); } Collection<Integer> doomedResourceGroupIds = new ArrayList<Integer>(); for (Integer managedGroupId : getManagedResourceGroupIdsForGroupDefinition(groupDefinitionId)) { doomedResourceGroupIds.add(managedGroupId); } for (ExpressionEvaluator.Result result : evaluator) { if (result == null) { /* * skip null result elements, which represent queries that returned some null element -- this could be * remedied by supporting "IS NULL" for parameter-replacement aside from just "= :bindArg" syntax */ continue; } /* * do one group at a time, to help prevent xaction timeouts * * note: we don't need to pass the overlord here because all group definition / dynagroup functionality * is hidden behind the MANAGE_INVENTORY permission, which is sufficient for all operations on a * resource group including creation, deletion, and membership changes */ Integer nextResourceGroupId = groupDefinitionManager.calculateGroupMembership_helper(subject, groupDefinitionId, result); /* * as a result of recalculation, the membership may have changed such that a group which was previously * marked as compatible now becomes a mixed group. if that happens, then the GroupCategory needs to be * updated and any compatible group constructs need to be removed from this group. the following method * will achieve both of those goals */ resourceGroupManager.setResourceType(nextResourceGroupId); /* * remove all ids returned from the helper. by the time we're done looping over all * ExpressionEvaluator.Result objects, the remaining objects in managedResourceGroupIds should represent * groups that no longer managed by this definition (either due to an inventory or expression change), and * are thus doomed */ doomedResourceGroupIds.remove(nextResourceGroupId); } /* * and ids that are left over are doomed, but since deleting a resource group is related to the size of the * group, remove each group in it's own transaction * * note: we don't need to pass the overlord here because all group definition / dynagroup functionality * is hidden behind the MANAGE_INVENTORY permission, which is sufficient for all operations on a * resource group including creation, deletion, and membership changes */ for (Integer doomedGroupId : doomedResourceGroupIds) { groupDefinitionManager.removeManagedResource_helper(subject, groupDefinitionId, doomedGroupId); } long endTime = System.currentTimeMillis(); log.debug("calculateGroupMembership took " + (endTime - startTime) + " millis"); } @RequiredPermission(Permission.MANAGE_INVENTORY) @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public Integer calculateGroupMembership_helper(Subject overlord, int groupDefinitionId, ExpressionEvaluator.Result result) throws ResourceGroupDeleteException, GroupDefinitionNotFoundException, GroupDefinitionNotFoundException { long startTime = System.currentTimeMillis(); GroupDefinition groupDefinition = getById(groupDefinitionId); String groupByClause = result.getGroupByClause(); ResourceGroup resourceGroup = resourceGroupManager .getByGroupDefinitionAndGroupByClause(groupDefinition.getId(), groupByClause); int resourceGroupId = 0; if (resourceGroup == null) { String newDynamicGroupName = getDynamicGroupName(groupDefinition.getName(), groupByClause); resourceGroup = new ResourceGroup(newDynamicGroupName); resourceGroupId = resourceGroupManager.createResourceGroup(overlord, resourceGroup).getId(); resourceGroup.setRecursive(groupDefinition.isRecursive()); resourceGroup.setGroupByClause(groupByClause); groupDefinition.addResourceGroup(resourceGroup); } else { resourceGroupId = resourceGroup.getId(); } /* * group additions/deletions are actions made to the explicit group, the implicit group is modified (based on * the recursive bit) by the existing code in the resourceGroupManager * * use resourceManager.getExplicitResourceIdsByResourceGroup instead of resourceGroup.getExplicitResources to keep * the data we need to pull across the line from the database as small as possible */ Collection<Integer> existingResourceIds = resourceManager .findExplicitResourceIdsByResourceGroup(resourceGroup.getId()); Set<Integer> idsToAdd = new HashSet<Integer>(result.getData()); idsToAdd.removeAll(existingResourceIds); Set<Integer> idsToRemove = new HashSet<Integer>(existingResourceIds); idsToRemove.removeAll(result.getData()); resourceGroupManager.addResourcesToGroup(overlord, resourceGroupId, ArrayUtils.unwrapCollection(idsToAdd)); resourceGroupManager.removeResourcesFromGroup(overlord, resourceGroupId, ArrayUtils.unwrapCollection(idsToRemove)); long endTime = System.currentTimeMillis(); log.debug("calculateGroupMembership_helper took " + (endTime - startTime) + " millis"); return resourceGroupId; } @SuppressWarnings({ "unchecked" }) public PageList<GroupDefinition> getGroupDefinitions(Subject subject, PageControl pc) { pc.initDefaultOrderingField("gd.name"); if (authorizationManager.isInventoryManager(subject)) { Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, GroupDefinition.QUERY_FIND_ALL, pc); List<GroupDefinition> results = query.getResultList(); int count = getGroupDefinitionCount(subject); return new PageList<GroupDefinition>(results, count, pc); } else { return new PageList<GroupDefinition>(pc); } } public PageList<GroupDefinition> findGroupDefinitionsByCriteria(Subject subject, ResourceGroupDefinitionCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); ; if (authorizationManager.isInventoryManager(subject) == false) { if (criteria.isInventoryManagerRequired()) { throw new PermissionException("Subject [" + subject.getName() + "] requires InventoryManager permission for requested query criteria."); } } CriteriaQueryRunner<GroupDefinition> queryRunner = new CriteriaQueryRunner<GroupDefinition>(criteria, generator, entityManager); return queryRunner.execute(); } public int getGroupDefinitionCount(Subject subject) { if (authorizationManager.isInventoryManager(subject)) { Query queryCount = PersistenceUtility.createCountQuery(entityManager, GroupDefinition.QUERY_FIND_ALL); long count = (Long) queryCount.getSingleResult(); return (int) count; } else { // instead of throwing an authorization exception, gracefully return 0 return 0; } } @RequiredPermission(Permission.MANAGE_INVENTORY) public int getAutoRecalculationGroupDefinitionCount(Subject subject) { Query queryCount = PersistenceUtility.createCountQuery(entityManager, GroupDefinition.QUERY_FIND_ALL_RECALCULATING); long count = (Long) queryCount.getSingleResult(); return (int) count; } @RequiredPermission(Permission.MANAGE_INVENTORY) public int getDynaGroupCount(Subject subject) { Query queryCount = PersistenceUtility.createCountQuery(entityManager, GroupDefinition.QUERY_FIND_ALL_MEMBERS); long count = (Long) queryCount.getSingleResult(); return (int) count; } @RequiredPermission(Permission.MANAGE_INVENTORY) @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void removeGroupDefinition(Subject subject, Integer groupDefinitionId) throws GroupDefinitionNotFoundException, GroupDefinitionDeleteException { Collection<Integer> managedGroupIds = getManagedResourceGroupIdsForGroupDefinition(groupDefinitionId); Subject overlord = subjectManager.getOverlord(); for (Integer managedGroupId : managedGroupIds) { removeManagedResource_helper(overlord, groupDefinitionId, managedGroupId); } GroupDefinition groupDefinition = getById(groupDefinitionId); try { entityManager.remove(groupDefinition); } catch (Exception e) { throw new GroupDefinitionDeleteException( "Error deleting groupDefinition '" + groupDefinition.getName() + "': ", e); } } @RequiredPermission(Permission.MANAGE_INVENTORY) @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void removeManagedResource_helper(Subject overlord, int groupDefinitionId, Integer doomedGroupId) throws GroupDefinitionDeleteException, GroupDefinitionNotFoundException { GroupDefinition groupDefinition = getById(groupDefinitionId); ResourceGroup doomedGroup = entityManager.getReference(ResourceGroup.class, doomedGroupId); groupDefinition.removeResourceGroup(doomedGroup); try { /* * using the group manager's delete method ensures that auditing data, * such as completed operations, is correctly removed */ resourceGroupManager.deleteResourceGroup(subjectManager.getOverlord(), doomedGroupId); } catch (Exception e) { throw new GroupDefinitionDeleteException("Error removing managedGroup '" + doomedGroup.getName() + "' " + "from groupDefinition '" + groupDefinition.getName() + "': ", e); } } @SuppressWarnings("unchecked") private List<Integer> getManagedResourceGroupIdsForGroupDefinition(int groupDefinitionId) { Query query = entityManager.createNamedQuery(GroupDefinition.QUERY_FIND_MANAGED_RESOURCE_GROUP_IDS_ADMIN); query.setParameter("groupDefinitionId", groupDefinitionId); List<Integer> results = query.getResultList(); return results; } private int getManagedResourceGroupSizeForGroupDefinition(int groupDefinitionId) { Query query = entityManager.createNamedQuery(GroupDefinition.QUERY_FIND_MANAGED_RESOURCE_GROUP_SIZE_ADMIN); query.setParameter("groupDefinitionId", groupDefinitionId); Number result = (Number) query.getSingleResult(); return result.intValue(); } private String getDynamicGroupName(String groupDefinitionName, String groupByClause) { String newDynamicGroupName = "DynaGroup - " + groupDefinitionName + (groupByClause.equals("") ? "" : (" ( " + groupByClause + " )")); return newDynamicGroupName; } private String updateDynaGroupName(String oldGroupDefinitionName, String updatedGroupDefinitionName, String dynaGroupName) throws GroupDefinitionUpdateException { String newGroupDefinitionName = updatedGroupDefinitionName; int oldGroupNameIndexStart = 12; // length of 'DynaGroup - ' prefix int oldGroupNameLength = oldGroupDefinitionName.length(); return dynaGroupName.substring(0, oldGroupNameIndexStart) + // newGroupDefinitionName + // dynaGroupName.substring(oldGroupNameIndexStart + oldGroupNameLength); } }