package org.rhq.enterprise.server.resource.metadata; import java.util.ArrayList; 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.PersistenceContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.bundle.Bundle; import org.rhq.core.domain.bundle.BundleType; import org.rhq.core.domain.bundle.ResourceTypeBundleConfiguration; import org.rhq.core.domain.configuration.definition.ConfigurationDefinition; import org.rhq.core.domain.content.PackageType; import org.rhq.core.domain.criteria.BundleCriteria; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.util.PageList; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.bundle.BundleManagerLocal; import org.rhq.enterprise.server.configuration.metadata.ConfigurationMetadataManagerLocal; import org.rhq.enterprise.server.resource.ResourceTypeManagerLocal; import org.rhq.enterprise.server.util.CriteriaQuery; import org.rhq.enterprise.server.util.CriteriaQueryExecutor; @Stateless public class ContentMetadataManagerBean implements ContentMetadataManagerLocal { private static final Log log = LogFactory.getLog(ContentMetadataManagerBean.class); @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityMgr; @EJB private ConfigurationMetadataManagerLocal configurationMetadataMgr; @EJB private BundleManagerLocal bundleMgr; @EJB private ResourceTypeManagerLocal resourceTypeMgr; @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void updateMetadata(ResourceType existingType, ResourceType newType) { log.debug("Updating bundle type and package types for " + existingType); existingType = entityMgr.find(ResourceType.class, existingType.getId()); // set the bundle type if one is defined BundleType newBundleType = newType.getBundleType(); if (newBundleType != null) { BundleType existingBundleType = existingType.getBundleType(); Set<ResourceType> targetedResourceTypes = new HashSet<ResourceType>( newBundleType.getExplicitlyTargetedResourceTypes().size()); if (!newBundleType.getExplicitlyTargetedResourceTypes().isEmpty()) { for (ResourceType targetType : newBundleType.getExplicitlyTargetedResourceTypes()) { ResourceType existingTargetType = resourceTypeMgr .getResourceTypeByNameAndPlugin(targetType.getName(), targetType.getPlugin()); if (existingTargetType == null) { throw new IllegalStateException( "Cannot find a resource type explicitly targeted by bundle type " + newBundleType + ". This should not happen because such type should always be persisted prior to the bundle type."); } targetedResourceTypes.add(existingTargetType); } } // If bundleType is not null then this in a bundle plugin and we do not need to do any further // processing because a bundle plugin cannot define any other content. // Also note that ANY changes to the newBundleType need to be made in here and NOT in the code // above. This is because the above code can query the database during which the changes might // be flushed to the DB (if at least 1 of those changes involved associating the new bundle type // with an entity from the persistence context. if (existingBundleType != null) { newBundleType.setId(existingBundleType.getId()); } newBundleType.setResourceType(existingType); newBundleType.getExplicitlyTargetedResourceTypes().clear(); newBundleType.getExplicitlyTargetedResourceTypes().addAll(targetedResourceTypes); newBundleType = entityMgr.merge(newBundleType); existingType.setBundleType(newBundleType); if (log.isDebugEnabled()) { log.debug("Updating bundle type to " + newBundleType); } return; } else { if (log.isDebugEnabled()) { log.debug("Removing bundle type"); } existingType.setBundleType(null); } // set the bundle configuration if the new type is a potential bundle deployment target ResourceTypeBundleConfiguration newBundleConfiguration = newType.getResourceTypeBundleConfiguration(); ResourceTypeBundleConfiguration existingBundleConfiguration = existingType.getResourceTypeBundleConfiguration(); if (newBundleConfiguration != null) { if (existingBundleConfiguration == null) { // the new type has now become a bundle target where the old type was not existingType.setResourceTypeBundleConfiguration(newBundleConfiguration); } else { // the old type was already a bundle target, we need to merge the new bundle config with the existing old one if (!existingBundleConfiguration.equals(newBundleConfiguration)) { entityMgr.remove(existingBundleConfiguration.getBundleConfiguration()); entityMgr.persist(newBundleConfiguration.getBundleConfiguration()); existingType.setResourceTypeBundleConfiguration(newBundleConfiguration); } } } else { if (existingBundleConfiguration != null) { if (log.isDebugEnabled()) { log.debug("Removing bundle configuration"); } entityMgr.remove(existingBundleConfiguration.getBundleConfiguration()); existingType.setResourceTypeBundleConfiguration(null); } } // Easy case: If there are no package definitions in the new type, null out any in the existing and return if (newType.getPackageTypes().isEmpty()) { if (log.isDebugEnabled()) { log.debug("Removing all package types for " + existingType); } for (PackageType packageType : existingType.getPackageTypes()) { entityMgr.remove(packageType); } existingType.getPackageTypes().clear(); return; } // The new type has package definitions // Easy case: If the existing type did not have any package definitions, simply use the new type defs and return if (existingType.getPackageTypes().isEmpty()) { if (log.isDebugEnabled()) { log.debug(existingType + " previously did not define any package types. Adding " + newType.getPackageTypes()); } for (PackageType newPackageType : newType.getPackageTypes()) { newPackageType.setResourceType(existingType); entityMgr.persist(newPackageType); } existingType.setPackageTypes(newType.getPackageTypes()); return; } // Both the new and existing types have definitions, so merge Set<PackageType> existingPackageTypes = existingType.getPackageTypes(); Map<String, PackageType> newPackageTypeDefinitions = new HashMap<String, PackageType>(newType.getPackageTypes() .size()); for (PackageType newPackageType : newType.getPackageTypes()) { newPackageTypeDefinitions.put(newPackageType.getName(), newPackageType); } // Remove all definitions that are in the existing type but not in the new type List<PackageType> removedPackageTypes = new ArrayList<PackageType>(existingType.getPackageTypes()); removedPackageTypes.removeAll(newType.getPackageTypes()); for (PackageType removedPackageType : removedPackageTypes) { existingType.removePackageType(removedPackageType); entityMgr.remove(removedPackageType); } // Merge definitions that were already in the existing type and again in the new type List<PackageType> mergedPackageTypes = new ArrayList<PackageType>(existingType.getPackageTypes()); mergedPackageTypes.retainAll(newType.getPackageTypes()); if (log.isDebugEnabled()) { log.debug("Updating package types: " + mergedPackageTypes); } for (PackageType mergedPackageType : mergedPackageTypes) { updatePackageConfigurations(mergedPackageType, newPackageTypeDefinitions.get(mergedPackageType.getName())); mergedPackageType.update(newPackageTypeDefinitions.get(mergedPackageType.getName())); entityMgr.merge(mergedPackageType); } // Persist all new definitions List<PackageType> newPackageTypes = new ArrayList<PackageType>(newType.getPackageTypes()); newPackageTypes.removeAll(existingType.getPackageTypes()); if (log.isDebugEnabled()) { log.debug("Adding package types: " + newPackageTypes); } for (PackageType newPackageType : newPackageTypes) { newPackageType.setResourceType(existingType); entityMgr.persist(newPackageType); existingPackageTypes.add(newPackageType); } } void updatePackageConfigurations(PackageType existingType, PackageType newType) { ConfigurationDefinition newConfigurationDefinition = newType.getDeploymentConfigurationDefinition(); if (newConfigurationDefinition != null) { if (existingType.getDeploymentConfigurationDefinition() == null) { // everything new entityMgr.persist(newConfigurationDefinition); existingType.setDeploymentConfigurationDefinition(newConfigurationDefinition); } else { // update existing ConfigurationDefinition existingDefinition = existingType.getDeploymentConfigurationDefinition(); configurationMetadataMgr.updateConfigurationDefinition(newConfigurationDefinition, existingDefinition); } } else { // newDefinition == null if (existingType.getDeploymentConfigurationDefinition() != null) { existingType.setDeploymentConfigurationDefinition(null); } } // The only place in the code base where I see that the PackageType.packageExtraPropertiesDefinition gets set // is here in this method. The code in ContentMetadataParser that creates the PackageType objects from a // plugin descriptor never references the property. Can the packageExtraPropertiesDefinition property be // altogether removed from the code base? // // jsanda - 11/3/2010 // newConfigurationDefinition = newType.getPackageExtraPropertiesDefinition(); // if (newConfigurationDefinition != null) { // if (existingType.getPackageExtraPropertiesDefinition() == null) { // // everything new // entityMgr.persist(newConfigurationDefinition); // existingType.setPackageExtraPropertiesDefinition(newConfigurationDefinition); // } else { // // update existing // ConfigurationDefinition existingDefinition = existingType.getPackageExtraPropertiesDefinition(); // configurationMetadataMgr.updateConfigurationDefinition(newConfigurationDefinition, // existingDefinition); // } // } else { // // newDefinition == null // if (existingType.getPackageExtraPropertiesDefinition() != null) { // existingType.setPackageExtraPropertiesDefinition(null); // } // } } @Override public void deleteMetadata(Subject subject, ResourceType resourceType) { log.debug("Deleting bundle type and bundles for " + resourceType); // Currently PackageType deletion is handled via a cascading delete when the // owning ResourceType is deleted. try { deleteBundles(subject, resourceType); } catch (Exception e) { throw new RuntimeException("Failed to delete bundles for " + resourceType, e); } } private void deleteBundles(final Subject subject, ResourceType resourceType) throws Exception { BundleType bundleType = resourceType.getBundleType(); if (bundleType == null) { return; } BundleCriteria criteria = new BundleCriteria(); criteria.addFilterBundleTypeId(bundleType.getId()); //Use CriteriaQuery to automatically chunk/page through criteria query results CriteriaQueryExecutor<Bundle, BundleCriteria> queryExecutor = new CriteriaQueryExecutor<Bundle, BundleCriteria>() { @Override public PageList<Bundle> execute(BundleCriteria criteria) { return bundleMgr.findBundlesByCriteria(subject, criteria); } }; CriteriaQuery<Bundle, BundleCriteria> bundles = new CriteriaQuery<Bundle, BundleCriteria>(criteria, queryExecutor); for (Bundle bundle : bundles) { bundleMgr.deleteBundle(subject, bundle.getId()); } } }