/* * 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.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; 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.lang.StringUtils; 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.plugin.CannedGroupExpression; import org.rhq.core.domain.resource.group.GroupDefinition; import org.rhq.core.domain.resource.group.InvalidExpressionException; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.core.domain.server.PersistenceUtility; import org.rhq.core.domain.util.PageControl; import org.rhq.core.domain.util.PageList; 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.ResourceGroupAlreadyExistsException; 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.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, GroupDefinitionManagerRemote { 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; } public GroupDefinition updateGroupDefinition(Subject subject, GroupDefinition groupDefinition) throws GroupDefinitionAlreadyExistsException, GroupDefinitionUpdateException, InvalidExpressionException, ResourceGroupUpdateException { // whenever DG is updated from UI or remotely we detach it from cannedExpression return updateGroupDefinition(subject, groupDefinition, true); } @RequiredPermission(Permission.MANAGE_INVENTORY) @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) // required for the recalculation thread (same like calculateGroupMembership) this fixes BZ 976265 private GroupDefinition updateGroupDefinition(Subject subject, GroupDefinition groupDefinition, boolean detachFromCannedExpression) throws GroupDefinitionAlreadyExistsException, GroupDefinitionUpdateException, InvalidExpressionException, ResourceGroupUpdateException { boolean nameChanged = false; try { nameChanged = validate(groupDefinition, groupDefinition.getId()); } catch (GroupDefinitionException gde) { throw new GroupDefinitionUpdateException(gde.getMessage()); } 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()); if (detachFromCannedExpression) { attachedGroupDefinition.setCannedExpression(null); } 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"); } if (definition.getRecalculationInterval() < 0) { throw new GroupDefinitionException("Recalculation interval cannot be negative"); } if (definition.getRecalculationInterval() > 0 && definition.getRecalculationInterval() < 60 * 1000) { throw new GroupDefinitionException( "Recalculation interval cannot be a positive number lower than 1 minute (60000ms)"); } if (definition.getExpression() == null || definition.getExpression().isEmpty()) { throw new GroupDefinitionException("Expression is empty"); } try { ExpressionEvaluator evaluator = new ExpressionEvaluator(); for (String expression : definition.getExpressionAsList()) { evaluator.addExpression(expression); } } catch (InvalidExpressionException e) { throw new GroupDefinitionException("Cannot parse the expression: " + e.getMessage()); } 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); } // BZ 1187680 : In a rare case a groupBy expression can generates multiple groupByClause strings differing // only in case. Group names are [correctly] case insensitive and groupBy [correctly] is not. For example // 'groupby resource.name' can cause this issue with two resources with the same name, ignoring case, like // 'CPU' and 'cpu'. In this situation we need to consolidate results into one dyna-group, otherwise // we'll get naming conflicts in the generated groups. So, merge results where the groupByClause is equal, // ignoring case. Map<String, ExpressionEvaluator.Result> resultMap = new HashMap<String, ExpressionEvaluator.Result>(); for (ExpressionEvaluator.Result result : evaluator) { String groupByClause = result.getGroupByClause(); String equivalentGroupByClauseKey = null; for (String key : resultMap.keySet()) { if (key.equalsIgnoreCase(groupByClause)) { equivalentGroupByClauseKey = key; break; } } if (null != equivalentGroupByClauseKey) { ExpressionEvaluator.Result sameResult = resultMap.get(equivalentGroupByClauseKey); sameResult.getData().addAll(result.getData()); } else { resultMap.put(result.getGroupByClause(), result); } } for (ExpressionEvaluator.Result result : resultMap.values()) { 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.setResourceTypeInNewTx(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(); boolean isNewGroup = false; ResourceGroup resourceGroup = resourceGroupManager.getByGroupDefinitionAndGroupByClause( groupDefinition.getId(), groupByClause); int resourceGroupId = 0; if (resourceGroup == null) { isNewGroup = true; String newDynamicGroupName = getDynamicGroupName(groupDefinition.getName(), groupByClause); resourceGroup = new ResourceGroup(newDynamicGroupName); try { resourceGroupId = resourceGroupManager.createResourceGroup(overlord, resourceGroup).getId(); } catch (ResourceGroupAlreadyExistsException e) { // BZ 1187680: In certain recalculation scenarios we need to lazily remove an existing // resource group with the same name because it no longer has a groubByClause that supports // its existence. Remove the unwanted group and create a new one with the proper name. Query query = entityManager.createNamedQuery(ResourceGroup.QUERY_FIND_BY_NAME_VISIBLE_GROUP); query.setParameter("name", resourceGroup.getName()); List<ResourceGroup> groups = query.getResultList(); // in an unexpected situation, group not found or the name is actually the same, just re-throw if (groups.size() != 1 || resourceGroup.getName().equals(groups.get(0).getName())) { throw e; } entityManager.remove(groups.get(0)); entityManager.flush(); // now retry the create using the correct name 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 = isNewGroup ? Collections.EMPTY_LIST : // 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(); if (log.isDebugEnabled()) { 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); } } @RequiredPermission(Permission.MANAGE_INVENTORY) public PageList<GroupDefinition> findGroupDefinitionsByCriteria(Subject subject, ResourceGroupDefinitionCriteria criteria) { if (authorizationManager.isInventoryManager(subject) == false) { if (criteria.isInventoryManagerRequired()) { throw new PermissionException("Subject [" + subject.getName() + "] requires InventoryManager permission for requested query criteria."); } } CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, 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.find(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); } private void updateGroupProperties(GroupDefinition gd, CannedGroupExpression cge) { gd.setName(cge.getName()); gd.setDescription(cge.getDescription()); gd.setExpression(StringUtils.join(cge.getExpression(), "\n")); gd.setRecalculationInterval(cge.getRecalcInMinutes() * 60 * 1000L); gd.setRecursive(cge.isRecursive()); gd.setCannedExpression(cge.getGroupDefinitionReferenceKey()); } private GroupDefinition getByName(String name) { Query query = entityManager.createNamedQuery(GroupDefinition.QUERY_FIND_BY_NAME); query.setParameter("name", name); try { GroupDefinition found = (GroupDefinition) query.getSingleResult(); return found; } catch (NoResultException e) { return null; } } private void updateDefinitionByCannedExpresssion(CannedGroupExpression cge) { Query query = entityManager.createNamedQuery(GroupDefinition.QUERY_FIND_BY_CANNED_EXPR_NAME); query.setParameter("cannedExpression", cge.getGroupDefinitionReferenceKey()); @SuppressWarnings("unchecked") List<GroupDefinition> result = query.getResultList(); if (cge.isCreateByDefault()) { // let's deploy dyna-group if (result.isEmpty()) { GroupDefinition sameName = getByName(cge.getName()); if (sameName != null) { log.info("Not creating DynaGroup based on [" + cge.getPlugin() + "] " + cge + " DynaGroup with same name already exists"); return; } log.info("Creating dynaGroup based on [" + cge.getPlugin() + "] " + cge); GroupDefinition gd = new GroupDefinition(cge.getName()); updateGroupProperties(gd, cge); try { createGroupDefinition(subjectManager.getOverlord(), gd); } catch (Exception ex) { log.error(ex); } } else { log.info("Updating dynaGroup based on [" + cge.getPlugin() + "] " + cge); GroupDefinition gd = result.get(0); updateGroupProperties(gd, cge); try { updateGroupDefinition(subjectManager.getOverlord(), gd, false); } catch (Exception ex) { log.error(ex); } } } else { if (!result.isEmpty()) { // this might be upgrade // we'd like to delete referenced groupDefinition log.info("Purging " + result.get(0) + " because CannedExpression was upgraded in plugin with createByDefault=false"); try { removeGroupDefinition(subjectManager.getOverlord(), result.get(0).getId()); } catch (Exception ex) { log.error(ex); } } } } public void updateGroupsByCannedExpressions(String plugin, List<CannedGroupExpression> expressions) { if (expressions == null) { expressions = Collections.emptyList(); } // create or update dyna groups based on our expressions for (CannedGroupExpression cge : expressions) { updateDefinitionByCannedExpresssion(cge); } Query query = entityManager.createNamedQuery(GroupDefinition.QUERY_FIND_LIKE_EXPR_NAME); query.setParameter("cannedExpression", plugin + ":%"); // include separator ':' ! @SuppressWarnings("unchecked") List<GroupDefinition> result = query.getResultList(); Map<String, CannedGroupExpression> map = new HashMap<String, CannedGroupExpression>(); for (CannedGroupExpression e : expressions) { map.put(e.getGroupDefinitionReferenceKey(), e); } for (GroupDefinition gd : result) { if (!map.containsKey(gd.getCannedExpression())) { log.info("Purging " + gd + " because base CannedExpression does not exist anymore"); try { removeGroupDefinition(subjectManager.getOverlord(), gd.getId()); } catch (Exception ex) { log.error(ex); } } } } }