/* * RHQ Management Platform * Copyright (C) 2005-2010 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.metadata; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; 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.NonUniqueResultException; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.ejb3.annotation.TransactionTimeout; import org.rhq.core.clientapi.agent.metadata.PluginMetadataManager; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.core.domain.criteria.ResourceCriteria; import org.rhq.core.domain.drift.DriftDefinition; import org.rhq.core.domain.drift.DriftDefinitionComparator; import org.rhq.core.domain.drift.DriftDefinitionComparator.CompareMode; import org.rhq.core.domain.drift.DriftDefinitionTemplate; import org.rhq.core.domain.resource.DeleteResourceHistory; import org.rhq.core.domain.resource.ProcessScan; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.core.domain.util.PageList; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.auth.SubjectManagerLocal; import org.rhq.enterprise.server.authz.RequiredPermission; import org.rhq.enterprise.server.bundle.BundleManagerLocal; import org.rhq.enterprise.server.inventory.InventoryManagerLocal; import org.rhq.enterprise.server.resource.ResourceManagerLocal; import org.rhq.enterprise.server.resource.ResourceTypeManagerLocal; import org.rhq.enterprise.server.resource.group.ResourceGroupDeleteException; import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal; import org.rhq.enterprise.server.scheduler.jobs.AsyncResourceDeleteJob; import org.rhq.enterprise.server.scheduler.jobs.PurgeResourceTypesJob; import org.rhq.enterprise.server.util.CriteriaQuery; import org.rhq.enterprise.server.util.CriteriaQueryExecutor; /** * This class manages the metadata for resources. Plugins are registered against this bean so that their metadata can be * pulled out and stored as necessary. * * @author Greg Hinkle * @author Heiko W. Rupp * @author John Mazzitelli * @author Ian Springer */ @Stateless public class ResourceMetadataManagerBean implements ResourceMetadataManagerLocal { private final Log log = LogFactory.getLog(ResourceMetadataManagerBean.class); @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @EJB private SubjectManagerLocal subjectManager; @EJB private ResourceManagerLocal resourceManager; @EJB private ResourceGroupManagerLocal resourceGroupManager; @EJB private ResourceTypeManagerLocal resourceTypeManager; @EJB private InventoryManagerLocal inventoryManager; @EJB private ResourceMetadataManagerLocal resourceMetadataManager; // self @EJB private ContentMetadataManagerLocal contentMetadataMgr; @EJB private OperationMetadataManagerLocal operationMetadataMgr; @EJB private EventMetdataManagerLocal eventMetadataMgr; @EJB private MeasurementMetadataManagerLocal measurementMetadataMgr; @EJB private AlertMetadataManagerLocal alertMetadataMgr; @EJB private ResourceConfigurationMetadataManagerLocal resourceConfigMetadataMgr; @EJB private PluginConfigurationMetadataManagerLocal pluginConfigMetadataMgr; @EJB private BundleManagerLocal bundleMgr; @TransactionAttribute(TransactionAttributeType.NEVER) public void updateTypes(Set<ResourceType> resourceTypes) throws Exception { // Only process the type if it is a non-runs-inside type (i.e. not a child of some other type X at this same // level in the type hierarchy). runs-inside types which we skip here will get processed at the next level down // when we recursively process type X's children. Set<ResourceType> allChildren = new HashSet<ResourceType>(); Queue<ResourceType> unvisitedChildren = new LinkedList<ResourceType>(); for (ResourceType resourceType : resourceTypes) { unvisitedChildren.addAll(resourceType.getChildResourceTypes()); } while (!unvisitedChildren.isEmpty()) { ResourceType childResourceType = unvisitedChildren.poll(); if (!allChildren.contains(childResourceType)) { allChildren.add(childResourceType); unvisitedChildren.addAll(childResourceType.getChildResourceTypes()); } } Set<ResourceType> nonRunsInsideResourceTypes = new LinkedHashSet<ResourceType>(); for (ResourceType resourceType : resourceTypes) { if (!allChildren.contains(resourceType)) { nonRunsInsideResourceTypes.add(resourceType); } } // Iterate the resource types breadth-first, so all platform types get added before any server types or platform // service types. This way, we'll be able to set all of the platform types as parents of the server types and // platform service types. It's also helpful for other types with multiple "runs-inside" parent types (e.g // Hibernate Entities), since it ensures the parent types will get persisted prior to the child types. if (log.isDebugEnabled()) { log.debug("Processing types: " + nonRunsInsideResourceTypes + "..."); } Set<ResourceType> legitimateChildren = new HashSet<ResourceType>(); for (ResourceType resourceType : nonRunsInsideResourceTypes) { long startTime = System.currentTimeMillis(); resourceType = resourceMetadataManager.updateTypeInNewTx(resourceType); long endTime = System.currentTimeMillis(); log.debug("Updated resource type [" + toConciseString(resourceType) + "] in " + (endTime - startTime) + " ms"); legitimateChildren.addAll(resourceType.getChildResourceTypes()); } // Only recurse if there are actually children - this prevents infinite recursion. if (!legitimateChildren.isEmpty()) { updateTypes(legitimateChildren); } } // NO TRANSACTION SHOULD BE ACTIVE ON ENTRY // Start with no transaction so we can control the transactional boundaries. Obsolete type removal removes // resources of the obsolete type. We need to avoid an umbrella transaction for the type removal because large // inventories of obsolete resources will generate very large transactions. Potentially resulting in timeouts // or other issues. @TransactionAttribute(TransactionAttributeType.NEVER) public void removeObsoleteTypes(Subject subject, String pluginName, PluginMetadataManager metadataCache) { Set<ResourceType> obsoleteTypes = new HashSet<ResourceType>(); Set<ResourceType> legitTypes = new HashSet<ResourceType>(); try { resourceMetadataManager.getPluginTypes(subject, pluginName, legitTypes, obsoleteTypes, metadataCache); if (!obsoleteTypes.isEmpty()) { log.info("Removing " + obsoleteTypes.size() + " obsolete types: " + obsoleteTypes + "..."); // removeResourceTypes(subject, obsoleteTypes, new HashSet<ResourceType>(obsoleteTypes)); // 1) Mark the obsolete types for deletion and uninventory the doomed resources List<Integer> obsoleteTypeIds = new ArrayList<Integer>(obsoleteTypes.size()); for (ResourceType rt : obsoleteTypes) { obsoleteTypeIds.add(rt.getId()); } inventoryManager.markTypesDeleted(obsoleteTypeIds, true); // 2) Immediately remove the uninventoried resources by forcing the normally async work to run in-band new AsyncResourceDeleteJob().execute(null); // 3) Immediately finish removing the deleted types by forcing the normally async work to run in-band new PurgeResourceTypesJob().executeJobCode(null); } } catch (Throwable t) { // Catch all exceptions, so a failure here does not cause the outer tx to rollback. log.error("Failure during removal of obsolete ResourceTypes and Subcategories.", t); } } @RequiredPermission(Permission.MANAGE_SETTINGS) @SuppressWarnings("unchecked") public void getPluginTypes(Subject subject, String pluginName, Set<ResourceType> legitTypes, Set<ResourceType> obsoleteTypes, PluginMetadataManager metadataCache) { try { Query query = entityManager.createNamedQuery(ResourceType.QUERY_FIND_BY_PLUGIN); query.setParameter("plugin", pluginName); List<ResourceType> existingTypes = query.getResultList(); if (existingTypes != null) { for (ResourceType existingType : existingTypes) { if (metadataCache.getType(existingType.getName(), existingType.getPlugin()) == null) { // The type is obsolete - (i.e. it's no longer defined by the plugin). obsoleteTypes.add(existingType); } else { legitTypes.add(existingType); } } } } catch (Exception e) { // Catch all exceptions, so a failure here does not cause the outer tx to rollback. log.error("Failure during removal of obsolete ResourceTypes and Subcategories.", e); } } @RequiredPermission(Permission.MANAGE_SETTINGS) public void completeRemoveResourceType(Subject subject, ResourceType existingType) { existingType = entityManager.find(ResourceType.class, existingType.getId()); if (entityManager.contains(existingType)) { entityManager.refresh(existingType); } // Completely remove the type from the type hierarchy. removeFromParents(existingType); removeFromChildren(existingType); entityManager.merge(existingType); contentMetadataMgr.deleteMetadata(subject, existingType); entityManager.flush(); existingType = entityManager.find(existingType.getClass(), existingType.getId()); try { bundleMgr.deleteMetadata(subject, existingType); } catch (Exception e) { throw new RuntimeException("Bundle metadata deletion failed. Cannot finish deleting " + existingType, e); } entityManager.flush(); existingType = entityManager.find(existingType.getClass(), existingType.getId()); try { alertMetadataMgr.deleteAlertTemplates(subject, existingType); } catch (Exception e) { throw new RuntimeException("Alert template deletion failed. Cannot finish deleting " + existingType, e); } entityManager.flush(); existingType = entityManager.find(existingType.getClass(), existingType.getId()); // Remove all compatible groups that are of the type. List<ResourceGroup> compatGroups = existingType.getResourceGroups(); if (compatGroups != null) { Iterator<ResourceGroup> compatGroupIterator = compatGroups.iterator(); while (compatGroupIterator.hasNext()) { ResourceGroup compatGroup = compatGroupIterator.next(); try { resourceGroupManager.deleteResourceGroup(subject, compatGroup.getId()); } catch (ResourceGroupDeleteException e) { throw new RuntimeException(e); } compatGroupIterator.remove(); } } entityManager.flush(); measurementMetadataMgr.deleteMetadata(existingType); entityManager.flush(); // remove DeleteResourceHistories try { Query nativeQuery = entityManager.createNamedQuery(DeleteResourceHistory.QUERY_DELETE_BY_RESOURCE_TYPE_ID); nativeQuery.setParameter("resourceTypeId", existingType.getId()); nativeQuery.executeUpdate(); } catch (Throwable t) { throw new RuntimeException("DeleteResourceHistory deletion failed, . Cannot finish deleting " + existingType, t); } // TODO: Clean out event definitions? // Finally, remove the type itself. // Refresh it first to make sure any newly discovered Resources of the type get added to the persistence // context and hopefully get removed via cascade when we remove the type. entityManager.refresh(existingType); entityManager.remove(existingType); entityManager.flush(); } private void removeFromParents(ResourceType typeToBeRemoved) { // Wrap in new HashSet to avoid ConcurrentModificationExceptions. Set<ResourceType> parentTypes = new HashSet<ResourceType>(typeToBeRemoved.getParentResourceTypes()); for (ResourceType parentType : parentTypes) { parentType.removeChildResourceType(typeToBeRemoved); entityManager.merge(parentType); } } private void removeFromChildren(ResourceType typeToBeRemoved) { // Wrap in new HashSet to avoid ConcurrentModificationExceptions. Set<ResourceType> childTypes = new HashSet<ResourceType>(typeToBeRemoved.getChildResourceTypes()); for (ResourceType childType : childTypes) { childType.removeParentResourceType(typeToBeRemoved); entityManager.merge(childType); } } @TransactionTimeout(1800) @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public ResourceType updateTypeInNewTx(ResourceType resourceType) { // see if there is already an existing type that we need to update log.info("Updating resource type [" + toConciseString(resourceType) + "]..."); ResourceType existingType; try { existingType = resourceTypeManager.getResourceTypeByNameAndPlugin(resourceType.getName(), resourceType.getPlugin()); } catch (NonUniqueResultException nure) { log.info("Found more than one existing ResourceType for " + resourceType); // TODO: Delete the redundant ResourceTypes to get the DB into a valid state. throw new IllegalStateException(nure); } // Connect the parent types if they exist, which they should. // We'll do this no matter if the resourceType exists or not - but we use existing vs. resourceType appropriately // This is to support the case when an existing type gets a new parent resource type in <runs-inside> updateParentResourceTypes(resourceType, existingType); if (existingType == null) { persistNewType(resourceType); } else { mergeExistingType(resourceType, existingType); } return resourceType; } /** * This impl needs to take into consideration that a large resource population may already exist for the type. */ private void mergeExistingType(ResourceType resourceType, ResourceType existingType) { log.debug("Merging type [" + resourceType + "] + into existing type [" + existingType + "]..."); // even though we've updated our child types to use new subcategory references, its still // not safe to delete the old sub categories yet, because we haven't yet deleted all of the old // child types which may still be referencing these sub categories // Update the rest of these related resources long startTime = System.currentTimeMillis(); pluginConfigMetadataMgr.updatePluginConfigurationDefinition(existingType, resourceType); long endTime = System.currentTimeMillis(); log.debug("Updated plugin configuration definition for ResourceType[" + toConciseString(existingType) + "] in " + (endTime - startTime) + " ms"); resourceConfigMetadataMgr.updateResourceConfigurationDefinition(existingType, resourceType); measurementMetadataMgr.updateMetadata(existingType, resourceType); contentMetadataMgr.updateMetadata(existingType, resourceType); operationMetadataMgr.updateMetadata(existingType, resourceType); resourceMetadataManager.updateDriftMetadata(existingType, resourceType); updateProcessScans(resourceType, existingType); eventMetadataMgr.updateMetadata(existingType, resourceType); // Update the type itself if (resourceType.getCategory() != existingType.getCategory()) { log.info("Changing category of Resource type [" + resourceType + "] from " + existingType.getCategory() + " to " + resourceType.getCategory() + "..."); existingType.setCategory(resourceType.getCategory()); } existingType.setCreateDeletePolicy(resourceType.getCreateDeletePolicy()); existingType.setCreationDataType(resourceType.getCreationDataType()); existingType.setDescription(resourceType.getDescription()); existingType.setDisplayName(resourceType.getDisplayName()); existingType.setSingleton(resourceType.isSingleton()); existingType.setSupportsManualAdd(resourceType.isSupportsManualAdd()); existingType.setSupportsMissingAvailabilityType(resourceType.isSupportsMissingAvailabilityType()); existingType.setSubCategory(resourceType.getSubCategory()); existingType = entityManager.merge(existingType); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void updateDriftMetadata(ResourceType existingType, ResourceType resourceType) { existingType = entityManager.find(ResourceType.class, existingType.getId()); // // Only if one or more drift definitions are different do we have to do anything to the persisted metadata. // Set<DriftDefinitionTemplate> existingDriftTemplates = existingType.getDriftDefinitionTemplates(); // We are only concerned with the plugin defined templates, user defined templates are not affected. Set<DriftDefinitionTemplate> existingPluginDriftTemplates = new HashSet<DriftDefinitionTemplate>( existingDriftTemplates.size()); for (DriftDefinitionTemplate existingTemplate : existingDriftTemplates) { if (!existingTemplate.isUserDefined()) { existingPluginDriftTemplates.add(existingTemplate); } } Set<DriftDefinitionTemplate> newPluginDriftTemplates = resourceType.getDriftDefinitionTemplates(); // note: the size of the sets are typically really small (usually between 1 and 3), // so iterating through them is fast. // look at all the configs to ensure we detect any changes to individual settings on the templates Set<String> existingNames = new HashSet<String>(existingPluginDriftTemplates.size()); DriftDefinitionComparator dirComp = new DriftDefinitionComparator(CompareMode.ONLY_DIRECTORY_SPECIFICATIONS); for (Iterator<DriftDefinitionTemplate> i = existingDriftTemplates.iterator(); i.hasNext();) { DriftDefinitionTemplate existingTemplate = i.next(); String existingName = existingTemplate.getName(); DriftDefinition existingDef = existingTemplate.getTemplateDefinition(); Set<DriftDefinition> attachedDefs = existingTemplate.getDriftDefinitions(); boolean noAttachedDefs = (null == attachedDefs || attachedDefs.isEmpty()); boolean notPinned = !existingTemplate.isPinned(); boolean stillDefined = false; // for later to determine if any existing templates are no longer defined in the plugin existingNames.add(existingName); for (DriftDefinitionTemplate newTemplate : newPluginDriftTemplates) { String newName = newTemplate.getName(); // The new template existed previously. See if it has changed and if so, in what way: // // IF the existingTemplate // has no attached defs AND // is not pinned // THEN we can update it with impunity // ELSE IF the directories have not changed // THEN we can update the base info fields only // Note that in the latter case we update the template but we will not push the // changes down to attached defs. This is a little odd because the template and defs can // get out of sync, but we don't want a plugin change to affect existing defs in case // the user has made manual changes, or wants it the way it is. if (newName.equals(existingName)) { stillDefined = true; DriftDefinition newDef = newTemplate.getTemplateDefinition(); boolean noDirChanges = (0 == dirComp.compare(existingDef, newDef)); if ((noAttachedDefs && notPinned) || noDirChanges) { entityManager.remove(existingTemplate.getConfiguration()); // don't orphan the config existingTemplate.setTemplateDefinition(newDef); } else { // can't update directories for an existing template if pinned and/or having attached defs log.error("Failed to update drift definition [" + newName + "] on type [" + resourceType.getName() + "]. It is not allowed to update directories on an existing template that is pinned " + "or has attached definitions. It would invalidate pinned snapshots as the fileset " + "would no longer map from template to definition."); } break; } } // If the template is no longer defined then what we do depends on whether it has attached // definitions. If not it can be deleted, otherwise we keep it around so the user doesn't lose // anything, but set it to user-defined, in essence removing it from the plugin. if (!stillDefined) { if (noAttachedDefs) { entityManager.remove(existingTemplate); i.remove(); } else { existingTemplate.setUserDefined(true); log.warn("Plugin no longer defines drift template [" + existingTemplate.getName() + "] on type [" + resourceType.getName() + "]. This template has attached definitions. To preserve the existing definitions the " + " template will not be removed but is instead being set as user-defined. The user will " + " be responsible for further maintenance of this template."); } } } // Now add new templates, not previously defined for (DriftDefinitionTemplate newTemplate : newPluginDriftTemplates) { String newName = newTemplate.getName(); if (existingNames.contains(newName)) { continue; } newTemplate.setResourceType(existingType); entityManager.persist(newTemplate); existingDriftTemplates.add(newTemplate); } } private void persistNewType(ResourceType resourceType) { log.info("Persisting new ResourceType [" + toConciseString(resourceType) + "]..."); // If the type didn't exist then we'll persist here which will cascade through // all child types as well as plugin and resource configs and their delegate types and // metric and operation definitions and their dependent types, // but first do some validity checking. // Ensure that the new type has any built-in metrics (like Availability Type) MeasurementMetadataManagerBean.getMetricDefinitions(resourceType); //Ensure any explicitly targeted resource types of a bundle type are refreshed with their //persisted counterparts (which should have been persisted before persisting the type //due to the dependency graph ordering) if (resourceType.getBundleType() != null && !resourceType.getBundleType().getExplicitlyTargetedResourceTypes().isEmpty()) { Set<ResourceType> existingTypes = new HashSet<ResourceType>( resourceType.getBundleType().getExplicitlyTargetedResourceTypes().size()); for (ResourceType targetedType : resourceType.getBundleType().getExplicitlyTargetedResourceTypes()) { ResourceType existingType = resourceTypeManager.getResourceTypeByNameAndPlugin(targetedType.getName(), targetedType.getPlugin()); existingTypes.add(existingType); } resourceType.getBundleType().getExplicitlyTargetedResourceTypes().clear(); resourceType.getBundleType().getExplicitlyTargetedResourceTypes().addAll(existingTypes); } entityManager.merge(resourceType); } private void updateParentResourceTypes(ResourceType newType, ResourceType existingType) { if (log.isDebugEnabled()) { if (existingType != null) { log.debug("Setting parent types on existing type: " + existingType + " to [" + newType.getParentResourceTypes() + "] - current parent types are [" + existingType.getParentResourceTypes() + "]..."); } else { log.debug("Setting parent types on new type: " + newType + " to [" + newType.getParentResourceTypes() + "]..."); } } Set<ResourceType> newParentTypes = newType.getParentResourceTypes(); newType.setParentResourceTypes(new HashSet<ResourceType>()); Set<ResourceType> originalExistingParentTypes = new HashSet<ResourceType>(); if (existingType != null) { originalExistingParentTypes.addAll(existingType.getParentResourceTypes()); } for (ResourceType newParentType : newParentTypes) { try { boolean isExistingParent = originalExistingParentTypes.remove(newParentType); if (existingType == null || !isExistingParent) { ResourceType realParentType = (ResourceType) entityManager .createNamedQuery(ResourceType.QUERY_FIND_BY_NAME_AND_PLUGIN) .setParameter("name", newParentType.getName()) .setParameter("plugin", newParentType.getPlugin()).getSingleResult(); ResourceType type = (existingType != null) ? existingType : newType; if (existingType != null) { log.info("Adding ResourceType [" + toConciseString(type) + "] as child of ResourceType [" + toConciseString(realParentType) + "]..."); } realParentType.addChildResourceType(type); } } catch (NoResultException nre) { throw new RuntimeException("Couldn't persist type [" + newType + "] because parent [" + newParentType + "] wasn't already persisted."); } } for (ResourceType obsoleteParentType : originalExistingParentTypes) { log.info("Removing type [" + toConciseString(existingType) + "] from parent type [" + toConciseString(obsoleteParentType) + "]..."); obsoleteParentType.removeChildResourceType(existingType); moveResourcesToNewParent(existingType, obsoleteParentType, newParentTypes); } } private static String toConciseString(ResourceType type) { return (type != null) ? (type.getPlugin() + ":" + type.getName() + "(id=" + type.getId() + ")") : "null"; } private void moveResourcesToNewParent(ResourceType existingType, ResourceType obsoleteParentType, Set<ResourceType> newParentTypes) { final Subject overlord = subjectManager.getOverlord(); ResourceCriteria criteria = new ResourceCriteria(); criteria.addFilterResourceTypeId(existingType.getId()); criteria.addFilterParentResourceTypeId(obsoleteParentType.getId()); //Use CriteriaQuery to automatically chunk/page through criteria query results CriteriaQueryExecutor<Resource, ResourceCriteria> queryExecutor = new CriteriaQueryExecutor<Resource, ResourceCriteria>() { @Override public PageList<Resource> execute(ResourceCriteria criteria) { return resourceManager.findResourcesByCriteria(overlord, criteria); } }; CriteriaQuery<Resource, ResourceCriteria> resources = new CriteriaQuery<Resource, ResourceCriteria>(criteria, queryExecutor); for (Resource resource : resources) { Resource newParent = null; newParentTypes: for (ResourceType newParentType : newParentTypes) { Resource ancestorResource = resource.getParentResource(); while (ancestorResource != null) { if (ancestorResource.getResourceType().equals(newParentType)) { // We found an ancestor to be the new parent of our orphaned Resource. newParent = ancestorResource; break newParentTypes; } ancestorResource = ancestorResource.getParentResource(); } for (Resource childResource : resource.getChildResources()) { if (childResource.getResourceType().equals(newParentType)) { // We found a child to be the new parent of our orphaned Resource. // TODO: Check if there are are multiple children of the new parent type. If so, // log an error and don't move the resource. newParent = childResource; break newParentTypes; } } } if (newParent != null) { if (resource.getParentResource() != null) { resource.getParentResource().removeChildResource(resource); } newParent.addChildResource(resource); // Assigning a new parent changes the ancestry for the resource and its children. Since the // children are not handled in this method, update their ancestry now. resourceManager.updateAncestry(subjectManager.getOverlord(), resource.getId()); } else { log.info("We were unable to move " + resource + " from invalid parent " + resource.getParentResource() + " to a new valid parent with one of the following types: " + newParentTypes); } } } /** * Update the set of process scans for a given resource type * * @param resourceType * @param existingType */ private void updateProcessScans(ResourceType resourceType, ResourceType existingType) { Set<ProcessScan> existingScans = existingType.getProcessScans(); Set<ProcessScan> newScans = resourceType.getProcessScans(); Set<ProcessScan> scansToPersist = CollectionsUtil.missingInFirstSet(existingScans, newScans); Set<ProcessScan> scansToDelete = CollectionsUtil.missingInFirstSet(newScans, existingScans); Set<ProcessScan> scansToUpdate = CollectionsUtil.intersection(existingScans, newScans); // update scans that may have changed for (ProcessScan scan : scansToUpdate) { for (ProcessScan nScan : newScans) { if (scan.equals(nScan)) { scan.setName(nScan.getName()); } } } // persist new scans for (ProcessScan scan : scansToPersist) { existingType.addProcessScan(scan); } // remove deleted ones for (ProcessScan scan : scansToDelete) { existingScans.remove(scan); entityManager.remove(scan); } } @Override public void removeObsoleteSubCategories(Subject subject, ResourceType newType, ResourceType existingType) { // TODO Auto-generated method stub } }