/* * RHQ Management Platform * Copyright (C) 2005-2014 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.enterprise.server.resource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; 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 javax.persistence.TypedQuery; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleTrigger; import org.jboss.ejb3.annotation.TransactionTimeout; import org.rhq.core.db.DatabaseType; import org.rhq.core.db.DatabaseTypeFactory; import org.rhq.core.domain.alert.Alert; import org.rhq.core.domain.alert.AlertCondition; import org.rhq.core.domain.alert.AlertConditionLog; import org.rhq.core.domain.alert.AlertDampeningEvent; import org.rhq.core.domain.alert.AlertDefinition; import org.rhq.core.domain.alert.notification.AlertNotification; import org.rhq.core.domain.alert.notification.AlertNotificationLog; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.core.domain.bundle.BundleResourceDeployment; import org.rhq.core.domain.cloud.StorageNode; import org.rhq.core.domain.configuration.PluginConfigurationUpdate; import org.rhq.core.domain.configuration.ResourceConfigurationUpdate; import org.rhq.core.domain.content.ContentServiceRequest; import org.rhq.core.domain.content.InstalledPackage; import org.rhq.core.domain.content.InstalledPackageHistory; import org.rhq.core.domain.content.PackageBits; import org.rhq.core.domain.content.PackageInstallationStep; import org.rhq.core.domain.content.PackageVersion; import org.rhq.core.domain.content.ResourceRepo; import org.rhq.core.domain.criteria.ResourceCriteria; import org.rhq.core.domain.criteria.ResourceTypeCriteria; import org.rhq.core.domain.discovery.AvailabilityReport; import org.rhq.core.domain.drift.JPADrift; import org.rhq.core.domain.drift.JPADriftChangeSet; import org.rhq.core.domain.event.Event; import org.rhq.core.domain.event.EventSource; import org.rhq.core.domain.measurement.Availability; import org.rhq.core.domain.measurement.AvailabilityType; import org.rhq.core.domain.measurement.MeasurementBaseline; import org.rhq.core.domain.measurement.MeasurementDataTrait; import org.rhq.core.domain.measurement.MeasurementOOB; import org.rhq.core.domain.measurement.MeasurementSchedule; import org.rhq.core.domain.measurement.ResourceAvailability; import org.rhq.core.domain.measurement.calltime.CallTimeDataKey; import org.rhq.core.domain.measurement.calltime.CallTimeDataValue; import org.rhq.core.domain.operation.ResourceOperationHistory; import org.rhq.core.domain.operation.ResourceOperationScheduleEntity; import org.rhq.core.domain.resource.Agent; import org.rhq.core.domain.resource.CreateResourceHistory; import org.rhq.core.domain.resource.DeleteResourceHistory; import org.rhq.core.domain.resource.InventoryStatus; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceAncestryFormat; import org.rhq.core.domain.resource.ResourceCategory; import org.rhq.core.domain.resource.ResourceError; import org.rhq.core.domain.resource.ResourceErrorType; import org.rhq.core.domain.resource.ResourceErrorTypeComposite; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.resource.composite.DisambiguationReport; import org.rhq.core.domain.resource.composite.RecentlyAddedResourceComposite; import org.rhq.core.domain.resource.composite.ResourceAvailabilitySummary; import org.rhq.core.domain.resource.composite.ResourceComposite; import org.rhq.core.domain.resource.composite.ResourceFacets; import org.rhq.core.domain.resource.composite.ResourceHealthComposite; import org.rhq.core.domain.resource.composite.ResourceIdFlyWeight; import org.rhq.core.domain.resource.composite.ResourceInstallCount; import org.rhq.core.domain.resource.composite.ResourceLineageComposite; import org.rhq.core.domain.resource.composite.ResourceWithAvailability; import org.rhq.core.domain.resource.flyweight.FlyweightCache; import org.rhq.core.domain.resource.flyweight.ResourceFlyweight; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.core.domain.resource.group.composite.AutoGroupComposite; 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.domain.util.PageOrdering; import org.rhq.core.util.IntExtractor; import org.rhq.core.util.collection.ArrayUtils; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.agentclient.AgentClient; 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.core.AgentManagerLocal; import org.rhq.enterprise.server.discovery.DiscoveryServerServiceImpl; import org.rhq.enterprise.server.measurement.AvailabilityManagerLocal; import org.rhq.enterprise.server.measurement.MeasurementScheduleManagerLocal; import org.rhq.enterprise.server.resource.disambiguation.DisambiguationUpdateStrategy; import org.rhq.enterprise.server.resource.disambiguation.Disambiguator; import org.rhq.enterprise.server.resource.group.ResourceGroupDeleteException; import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal; import org.rhq.enterprise.server.rest.ResourceHandlerBean; import org.rhq.enterprise.server.util.CriteriaQueryGenerator; import org.rhq.enterprise.server.util.CriteriaQueryRunner; import org.rhq.enterprise.server.util.LookupUtil; import org.rhq.enterprise.server.util.QueryUtility; import static org.rhq.core.domain.criteria.Criteria.Restriction.COLLECTION_ONLY; import static org.rhq.core.domain.criteria.Criteria.Restriction.COUNT_ONLY; import static org.rhq.enterprise.server.util.CriteriaQueryGenerator.getPageControl; /** * A manager that provides methods for creating, updating, deleting, and querying {@link Resource}s. * * @author Ian Springer * @author Joseph Marques * @author Jay Shaughnessy (delete operations) */ @Stateless public class ResourceManagerBean implements ResourceManagerLocal, ResourceManagerRemote { private static final Log LOG = LogFactory.getLog(ResourceManagerBean.class); private final static String BOUNDED_MAX_RESOURCES = "1000"; private final static String BOUNDED_MAX_RESOURCES_BY_TYPE = "200"; @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @EJB private AgentManagerLocal agentManager; @EJB private AuthorizationManagerLocal authorizationManager; @EJB private ResourceGroupManagerLocal groupManager; @EJB private SubjectManagerLocal subjectManager; @EJB private ResourceManagerLocal resourceManager; // ourself, for xactional semantic consistency @EJB private ResourceGroupManagerLocal resourceGroupManager; @EJB private ResourceTypeManagerLocal typeManager; @EJB //@IgnoreDependency private MeasurementScheduleManagerLocal measurementScheduleManager; @EJB private AvailabilityManagerLocal availabilityManager; @javax.annotation.Resource(name = "RHQ_DS", mappedName = RHQConstants.DATASOURCE_JNDI_NAME) private DataSource dataSource; @Override public void createResource(Subject user, Resource resource, int parentId) throws ResourceAlreadyExistsException { Resource parent = null; if (parentId != Resource.ROOT_ID) { // not trying to create a root resource, so lookup the parent parent = entityManager.find(Resource.class, parentId); // uh-oh if the parent could not be found if (parent == null) { throw new ResourceNotFoundException("Intended parent for new resource does not exist."); } if (!authorizationManager.hasResourcePermission(user, Permission.CREATE_CHILD_RESOURCES, parent.getId())) { throw new PermissionException("You do not have permission to add this resource as a child."); } if (getResourceByParentAndKey(user, parent, resource.getResourceKey(), resource.getResourceType() .getPlugin(), resource.getResourceType().getName()) != null) { throw new ResourceAlreadyExistsException("Resource with key '" + resource.getResourceKey() + "' already exists."); } } if (parent != null && parent != Resource.ROOT) { // Only set the relationships if this is not a root resource. // The cardinal rule is to add the relationship in both directions, // so the parent will have direct access to this child after the method returns. resource.setParentResource(parent); parent.addChildResource(resource); } entityManager.persist(resource); LOG.debug("********* resource persisted ************"); // Execute sub-methods as overlord to bypass additional security checks. Subject overlord = this.subjectManager.getOverlord(); updateImplicitMembership(overlord, resource); // Because this resource is in the process of creation it has no measurement schedules // defined. These are needed before applying alert templates for the resource type. // This call will create the schedules as necessary and, as a side effect, apply the templates. // TODO: jshaughn - This fails for resource types without metric definitions int[] resourceIds = new int[] { resource.getId() }; measurementScheduleManager.findSchedulesForResourceAndItsDescendants(resourceIds, false); } private void updateImplicitMembership(Subject subject, Resource resource) { /* * if this is a new root resource, it could not have been a member of some group - explicitly or implicitly */ if (resource.getParentResource() == null) { return; } groupManager.updateImplicitGroupMembership(subject, resource); } @Override public Resource updateResource(Subject user, Resource resource) { Resource persistedResource = entityManager.find(Resource.class, resource.getId()); if (persistedResource == null) { throw new ResourceNotFoundException(resource.getId()); } if (!authorizationManager.hasResourcePermission(user, Permission.MODIFY_RESOURCE, resource.getId())) { throw new PermissionException("You do not have permission to modify Resource with id " + resource.getId() + "."); } /*if (getResourceByParentAndKey(user, resource.getParentResource(), resource.getResourceKey()) != null) * { throw new ResourceAlreadyExistsException("Resource with key '" + resource.getName() + "' already * exists");}*/ // On name change make sure we update the ancestry as the name is part of the ancestry string if (!persistedResource.getName().equals(resource.getName())) { persistedResource.setName(resource.getName()); updateAncestry(persistedResource); } persistedResource.setLocation(resource.getLocation()); persistedResource.setDescription(resource.getDescription()); persistedResource.setAgentSynchronizationNeeded(); persistedResource.setModifiedBy(user.getName()); return entityManager.merge(persistedResource); } @Override @RequiredPermission(Permission.MANAGE_INVENTORY) @TransactionAttribute(TransactionAttributeType.NEVER) public void uninventoryResourcesOfResourceType(Subject subject, int resourceTypeId) { List<Integer> typeIds = new ArrayList<Integer>(1); typeIds.add(resourceTypeId); List<Integer> resourceIds = resourceManager.findIdsByTypeIds(typeIds); if (resourceIds != null && !resourceIds.isEmpty()) { LOG.info("Uninventorying all [" + resourceIds.size() + "] resources with resource type ID of [" + resourceTypeId + "]"); for (Integer resourceId : resourceIds) { resourceManager.uninventoryResourceInNewTransaction(resourceId); } } } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public void uninventoryAllResourcesByAgent(Subject user, Agent doomedAgent) { Resource platform = resourceManager.getPlatform(doomedAgent); if (platform == null) { // there is no platform resource - just delete the agent itself agentManager.deleteAgent(doomedAgent); } else { resourceManager.uninventoryResources(user, new int[] { platform.getId() }); } } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public List<Integer> uninventoryResources(Subject user, int[] resourceIds) { List<Integer> result = new ArrayList<Integer>(); boolean isInventoryManager = authorizationManager.isInventoryManager(user); for (Integer resourceId : resourceIds) { if (!result.contains(resourceId)) { if (!isInventoryManager) { result.addAll(resourceManager.uninventoryResource(user, resourceId)); } else { result.addAll(resourceManager.uninventoryResourceInNewTransaction(resourceId)); } } } return result; } @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public List<Integer> uninventoryResource(Subject user, int resourceId) { // make sure the user is authorized to delete this resource (which implies you can delete all its children) // TODO: There is a pretty good argument for this being replaced with MANAGE_INVENTORY. It takes an // inventory manager to import resources, so why not to remove them? But, since no one has complained // we're timid about making a change that may hamstring existing setups. if (!authorizationManager.hasResourcePermission(user, Permission.DELETE_RESOURCE, resourceId)) { throw new PermissionException("You do not have permission to uninventory resource [" + resourceId + "]"); } return resourceManager.uninventoryResourceInNewTransaction(resourceId); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public boolean handleMissingResourceInNewTransaction(int resourceId) { Resource resource = entityManager.find(Resource.class, resourceId); if (null == resource) { return true; } ResourceType type = resource.getResourceType(); switch (type.getMissingPolicy()) { case DOWN: return false; case UNINVENTORY: // no need to start the new transaction here, we're already in a new transaction and have // only pulled a couple of things into the hibernate cache. So don't call through the facade. uninventoryResourceInNewTransaction(resourceId); LOG.info("Automatic uninventory of MISSING resource: " + resource); return true; case IGNORE: LookupUtil.getDiscoveryBoss().ignoreResources(subjectManager.getOverlord(), new int[] { resourceId }); LOG.info("Automatic ignore of MISSING resource: " + resource); return true; } return false; } @Override @SuppressWarnings("unchecked") @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public List<Integer> uninventoryResourceInNewTransaction(int resourceId) { Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { LOG.info("Delete resource not possible, as resource with id [" + resourceId + "] was not found"); return Collections.emptyList(); // Resource not found. TODO give a nice message to the user } // if the resource has no parent, its a platform resource and its agent should be purged too Agent doomedAgent = null; boolean isDebugEnabled = LOG.isDebugEnabled(); try { Subject overlord = subjectManager.getOverlord(); if (resource.getParentResource() == null) { // note, this needs to be done before the marking because the agent reference is going to be set to null doomedAgent = agentManager.getAgentByResourceId(overlord, resourceId); // note - test code does not always provide the agent, if not found, just warn if (doomedAgent == null) { LOG.warn("Platform resource had no associated agent. This warning should occur in TEST code only!"); } } // we don't need these attached objects during the potentially long update that follows entityManager.detach(resource); if (null != doomedAgent) { entityManager.detach(doomedAgent); } AgentClient agentClient = null; try { // The test code does not always generate agents for the resources. Catch and log any problem but continue agentClient = agentManager.getAgentClient(overlord, resourceId); } catch (Throwable t) { LOG.warn("No AgentClient found for resource [" + resource + "]. Unable to inform agent of inventory removal (this may be ok): " + t); } // since we delete the resource asynchronously, make sure we remove things that would cause system // side effects after markForDeletion completed but before the resource is actually removed from the DB // delete the resource and all its children if (isDebugEnabled) { LOG.debug("Marking resource [" + resource + "] for asynchronous uninventory"); } // set agent references null // foobar the resourceKeys // update the inventory status to UNINVENTORY List<Integer> toBeDeletedResourceIds = getDescendents(resourceId); int i = 0; if (isDebugEnabled) { LOG.debug("== total size : " + toBeDeletedResourceIds.size()); } while (i < toBeDeletedResourceIds.size()) { int j = i + 1000; if (j > toBeDeletedResourceIds.size()) j = toBeDeletedResourceIds.size(); List<Integer> idsToDelete = toBeDeletedResourceIds.subList(i, j); if (isDebugEnabled) { LOG.debug("== Bounds " + i + ", " + j); } // refresh overlord session for each batch to avoid session timeout overlord = subjectManager.getOverlord(); boolean hasErrors = uninventoryResourcesBulkDelete(overlord, idsToDelete); if (hasErrors) { throw new IllegalArgumentException("Could not remove resources from their containing groups"); } i = j; } // QUERY_MARK_RESOURCES_FOR_ASYNC_DELETION is an expensive recursive query // But luckily we have already (through such a recursive query above) determined the doomed resources // Query markDeletedQuery = entityManager.createNamedQuery(Resource.QUERY_MARK_RESOURCES_FOR_ASYNC_DELETION); // markDeletedQuery.setParameter("resourceId", resourceId); // markDeletedQuery.setParameter("status", InventoryStatus.UNINVENTORIED); // int resourcesDeleted = markDeletedQuery.executeUpdate(); i = 0; int resourcesDeleted = 0; while (i < toBeDeletedResourceIds.size()) { int j = i + 1000; if (j > toBeDeletedResourceIds.size()) j = toBeDeletedResourceIds.size(); List<Integer> idsToDelete = toBeDeletedResourceIds.subList(i, j); Query markDeletedQuery = entityManager .createNamedQuery(Resource.QUERY_MARK_RESOURCES_FOR_ASYNC_DELETION_QUICK); markDeletedQuery.setParameter("resourceIds", idsToDelete); markDeletedQuery.setParameter("status", InventoryStatus.UNINVENTORIED); resourcesDeleted += markDeletedQuery.executeUpdate(); i = j; } if (resourcesDeleted != toBeDeletedResourceIds.size()) { LOG.error("Tried to uninventory " + toBeDeletedResourceIds.size() + " resources, but actually uninventoried " + resourcesDeleted); } // flush to make sure the db is successfully updated with changes before we make more slsb calls and // before we notify the agent entityManager.flush(); // still need to tell the agent about the removed resources so it stops avail reports // but not if this is a synthetic agent that was created in the REST-api // See org.rhq.enterprise.server.rest.ResourceHandlerBean.createPlatformInternal() // See also https://docs.jboss.org/author/display/RHQ/Virtual+platforms+and+synthetic+agents if (agentClient != null) { if (agentClient.getAgent() == null || agentClient.getAgent().getName() == null || !agentClient.getAgent().getName().startsWith(ResourceHandlerBean.DUMMY_AGENT_NAME_PREFIX)) { // don't do that on "REST-agents" try { if (agentClient.pingService(3000L)) { agentClient.getDiscoveryAgentService().uninventoryResource(resourceId); } else { LOG.warn(" Unable to inform agent [" + agentClient.getAgent().getName() + "] of inventory removal for resource [" + resourceId + "]. Agent can not be reached or is not accepting service requests."); } } catch (Exception e) { LOG.warn(" Unable to inform agent of inventory removal for resource [" + resourceId + "]", e); } } } // now remove the doomed agent. if (doomedAgent != null) { agentManager.deleteAgent(doomedAgent); // flush out any issues deleting the agent so we can catch and provide a useful error message entityManager.flush(); } return toBeDeletedResourceIds; } catch (RuntimeException e) { if (doomedAgent != null) { // The most likely reason for a failure, although unlikely in itself, is that newly discovered resources // are currently being merged into the platform, and associated with the doomed agent. In this case // the user must wait until the merge is complete. Make sure the caller knows about this possibility. String msg = "Failed to uninventory platform. This can happen if new resources were actively being imported. Please wait and try again shortly."; if (isDebugEnabled) { throw new IllegalStateException(msg, e); } else { msg += (" Cause:" + e.getMessage()); throw new IllegalStateException(msg); } } throw e; } } @SuppressWarnings("unchecked") private List<Integer> getDescendents(int resourceId) { List<Integer> result; Query query; DatabaseType dbType = DatabaseTypeFactory.getDefaultDatabaseType(); if (DatabaseTypeFactory.isOracle(dbType)) { query = entityManager.createNativeQuery(Resource.QUERY_NATIVE_FIND_DESCENDANTS_ORACLE); } else if (DatabaseTypeFactory.isPostgres(dbType)) { query = entityManager.createNativeQuery(Resource.QUERY_NATIVE_FIND_DESCENDANTS_POSTGRES); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_DESCENDANTS); } query.setParameter("resourceId", resourceId); if (DatabaseTypeFactory.isOracle(dbType)) { List<?> rl = query.getResultList(); result = new ArrayList<Integer>(rl.size()); for (Object id : rl) { result.add(dbType.getInteger(id)); } } else { // native Integer support result = query.getResultList(); } return result; } @Override @SuppressWarnings("unchecked") public List<Integer> getResourceDescendantsByTypeAndName(Subject user, int resourceId, Integer resourceTypeId, String name) { List<Integer> result; Query query; DatabaseType dbType = DatabaseTypeFactory.getDefaultDatabaseType(); if (DatabaseTypeFactory.isOracle(dbType)) { query = entityManager.createNativeQuery(Resource.QUERY_NATIVE_FIND_DESCENDANTS_BY_TYPE_AND_NAME_ORACLE); } else if (DatabaseTypeFactory.isPostgres(dbType)) { query = entityManager.createNativeQuery(Resource.QUERY_NATIVE_FIND_DESCENDANTS_BY_TYPE_AND_NAME_POSTGRES); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_DESCENDANTS_BY_TYPE_AND_NAME); } query.setParameter("resourceId", resourceId); query.setParameter("resourceTypeId", ((null != resourceTypeId) ? resourceTypeId : 0)); name = QueryUtility.formatSearchParameter(name); query.setParameter("resourceName", ((null != name) ? name : "$$$null$$$")); if (DatabaseTypeFactory.isOracle(dbType)) { List<?> rl = query.getResultList(); result = new ArrayList<Integer>(rl.size()); for (Object id : rl) { result.add(dbType.getInteger(id)); } } else { // native Integer support result = query.getResultList(); } return result; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) @TransactionTimeout(value = 20, unit = TimeUnit.MINUTES) public void uninventoryResourceAsyncWork(Subject user, int resourceId) { if (!authorizationManager.isOverlord(user)) { throw new IllegalArgumentException("Only the overlord can execute out-of-band async resource delete method"); } /* * even though the group removal occurs in the in-band work, there can be some group definitions that just * happens to perform its recalculation (either manually or schedules) in the period after the in-band work * completes but before the async job triggers. since the ExpressionEvaluator that underlies the bulk of the * dynagroup query generations automatically adds a filter to only manipulate COMMITTED resource, this work * should be a no-op most of the time. however, in rare circumstances it's possible for an InventoryReport to * come across the wire and flip the status of resources from UNINVENTORIED back to COMMITTED. in this case, * this group removal logic needs to be executed again just prior to removing the rest of the reosurce history. */ boolean hasErrors = uninventoryResourcesBulkDelete(user, Arrays.asList(resourceId)); if (hasErrors) { return; // return early if there were any errors, because we can't remove the resource yet } hasErrors = uninventoryResourceBulkDeleteAsyncWork(user, resourceId); if (hasErrors) { return; // return early if there were any errors, because we can't remove the resource yet } Resource attachedResource = entityManager.find(Resource.class, resourceId); if (LOG.isDebugEnabled()) { LOG.debug("Overlord is asynchronously deleting resource [" + attachedResource + "]"); } if (attachedResource != null) { // our unidirectional one-to-many mapping of drift definition makes it not possible to easily bulk delete drift definition // so remove them here and let cascading of delete_orphan do the work if (attachedResource.getDriftDefinitions() != null) { attachedResource.getDriftDefinitions().clear(); } // one more thing, delete any autogroup backing groups List<ResourceGroup> backingGroups = attachedResource.getAutoGroupBackingGroups(); if (null != backingGroups && !backingGroups.isEmpty()) { int size = backingGroups.size(); int[] backingGroupIds = new int[size]; for (int i = 0; (i < size); ++i) { backingGroupIds[i] = backingGroups.get(i).getId(); } try { resourceGroupManager.deleteResourceGroups(user, backingGroupIds); } catch (Throwable t) { if (LOG.isDebugEnabled()) { LOG.error( "Bulk delete error for autogroup backing group deletion for " + Arrays.toString(backingGroupIds), t); } else { LOG.error("Bulk delete error for autogroup backing group deletion for " + Arrays.toString(backingGroupIds) + ": " + t.getMessage()); } } } // now we can purge the resource, let cascading do the rest entityManager.remove(attachedResource); } } private boolean uninventoryResourcesBulkDelete(Subject overlord, List<Integer> resourceIds) { // Obtain group ids of affected groups, i.e. groups where resources act as the explicit resources. // The RHQ_RESOURCE_GROUP_RES_EXP_MAP table is used, because the resource type of a group is set to a // non null value iff the number of unique resource types of the member _EXPLICIT_ resources is equal to 1. // In other words, the implicit resources of a group have no impact on the group type (compatible/mixed). Query nativeQuery = entityManager.createNativeQuery(ResourceGroup.QUERY_GET_GROUP_IDS_BY_RESOURCE_IDS); nativeQuery.setParameter("resourceIds", resourceIds); // Note that different DB vendors return different types for IDs because the representation // is different at the storage layer. This is an em native query so we need to handle the differences. // Postgres will return an Integer, but Oracle returns a BigDecimal, etc. List<?> rs = nativeQuery.getResultList(); String[] nativeQueriesToExecute = new String[] { // ResourceGroup.QUERY_DELETE_EXPLICIT_BY_RESOURCE_IDS, // unmap from explicit groups ResourceGroup.QUERY_DELETE_IMPLICIT_BY_RESOURCE_IDS // unmap from implicit groups }; boolean hasErrors = false; for (String nativeQueryToExecute : nativeQueriesToExecute) { // execute all in new transactions, continuing on error, but recording whether errors occurred hasErrors |= resourceManager.bulkNativeQueryDeleteInNewTransaction(overlord, nativeQueryToExecute, resourceIds); } // update the resource type of affected groups by calling setResouceType() DatabaseType dbType = DatabaseTypeFactory.getDefaultDatabaseType(); Integer groupId = null; for (Object r : rs) { try { groupId = dbType.getInteger(r); resourceGroupManager.setResourceTypeInNewTx(groupId); } catch (ResourceGroupDeleteException rgde) { LOG.warn("Unable to change resource type for group with id [" + groupId + "]", rgde); } } // if any of the doomed resources are storage nodes (we don't know, we only have ids, so we'll just have // to make the call anyway. Unfortunately, this can't wait until the async work because the StorageNode // is still active and should not be linked to an uninventoried resource. resourceManager.unlinkStorageNodesInNewTx(resourceIds); return hasErrors; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void unlinkStorageNodesInNewTx(List<Integer> resourceIds) { try { Query query = entityManager.createNamedQuery(StorageNode.QUERY_UPDATE_REMOVE_LINKED_RESOURCES); query.setParameter("resourceIds", resourceIds); query.executeUpdate(); } catch (Exception e) { LOG.warn( "May not have been able to unlink StorageNode from it's Resource. This is unlikely to be a problem.", e); } } private boolean uninventoryResourceBulkDeleteAsyncWork(Subject overlord, int resourceId) { String[] namedQueriesToExecute = new String[] { // ResourceRepo.DELETE_BY_RESOURCES, // MeasurementBaseline.QUERY_DELETE_BY_RESOURCES, // baseline BEFORE schedules MeasurementDataTrait.QUERY_DELETE_BY_RESOURCES, // traits BEFORE schedules CallTimeDataValue.QUERY_DELETE_BY_RESOURCES, // call time data values BEFORE schedules & call time data keys CallTimeDataKey.QUERY_DELETE_BY_RESOURCES, // call time data keys BEFORE schedules MeasurementOOB.DELETE_FOR_RESOURCES, // MeasurementSchedule.DELETE_BY_RESOURCES, // schedules AFTER baselines, traits, and calltime data Availability.QUERY_DELETE_BY_RESOURCES, // ResourceError.QUERY_DELETE_BY_RESOURCES, // Event.DELETE_BY_RESOURCES, // EventSource.QUERY_DELETE_BY_RESOURCES, // BundleResourceDeployment.QUERY_DELETE_BY_RESOURCES, // PackageInstallationStep.QUERY_DELETE_BY_RESOURCES, // steps BEFORE installed package history InstalledPackageHistory.QUERY_DELETE_BY_RESOURCES, // history BEFORE installed packages & content service requests InstalledPackage.QUERY_DELETE_BY_RESOURCES, // ContentServiceRequest.QUERY_DELETE_BY_RESOURCES, // ResourceOperationScheduleEntity.QUERY_DELETE_BY_RESOURCES, // ResourceOperationHistory.QUERY_DELETE_BY_RESOURCES, // DeleteResourceHistory.QUERY_DELETE_BY_PARENT_RESOURCE_IDS, // delete history items where this resource was parent (it's children was deleted) CreateResourceHistory.QUERY_DELETE_BY_RESOURCES, // ResourceConfigurationUpdate.QUERY_DELETE_BY_RESOURCES_0, // orphan parent list or maps (execute only on non selfRefCascade dbs) ResourceConfigurationUpdate.QUERY_DELETE_BY_RESOURCES_1, // first, delete the raw configs for the config ResourceConfigurationUpdate.QUERY_DELETE_BY_RESOURCES_2, // then delete the config objects ResourceConfigurationUpdate.QUERY_DELETE_BY_RESOURCES_3, // then the history objects wrapping those configs PluginConfigurationUpdate.QUERY_DELETE_BY_RESOURCES_0, // orphan parent list or maps (execute only on non selfRefCascade dbs) PluginConfigurationUpdate.QUERY_DELETE_BY_RESOURCES_1, // first, delete the raw configs for the config PluginConfigurationUpdate.QUERY_DELETE_BY_RESOURCES_2, // then delete the config objects PluginConfigurationUpdate.QUERY_DELETE_BY_RESOURCES_3, // then the history objects wrapping those configs AlertConditionLog.QUERY_DELETE_BY_RESOURCES, // Don't AlertConditionLog.QUERY_DELETE_BY_RESOURCES_BULK_DELETE, // alter AlertNotificationLog.QUERY_DELETE_BY_RESOURCES, // the Alert.QUERY_DELETE_BY_RESOURCES, // order AlertCondition.QUERY_DELETE_BY_RESOURCES, // of AlertDampeningEvent.QUERY_DELETE_BY_RESOURCES, // alert- AlertNotification.QUERY_DELETE_BY_RESOURCES, // related AlertDefinition.QUERY_DELETE_BY_RESOURCES, // deletes JPADrift.QUERY_DELETE_BY_RESOURCES, // drift before changeset JPADriftChangeSet.QUERY_DELETE_BY_RESOURCES }; List<Integer> resourceIds = new ArrayList<Integer>(); resourceIds.add(resourceId); boolean supportsCascade = DatabaseTypeFactory.getDefaultDatabaseType().supportsSelfReferringCascade(); boolean hasErrors = false; boolean debugEnabled = LOG.isDebugEnabled(); // Get all packages installed in this resource // We want to remove the backing Packages for this resource's InstalledPackages, as long as // those Packages are only used by this resource, and not referenced in other ways. // We need to determine now which Packages can be removed before we delete the InstalledPackages. List<Integer> installedPackageIds = getInstalledPackagesIds(resourceId); for (String namedQueryToExecute : namedQueriesToExecute) { // execute all in new transactions, continuing on error, but recording whether errors occurred // If the db vendor can not support our self-referring cascade delete data model then we may have // to leave some config prop rows orphaned. Only execute the selected queries if you *do* // want to avoid self-referring cascade delete (and leave orphans) if (supportsCascade && ( // namedQueryToExecute.equals(ResourceConfigurationUpdate.QUERY_DELETE_BY_RESOURCES_0) || // namedQueryToExecute.equals(PluginConfigurationUpdate.QUERY_DELETE_BY_RESOURCES_0))) { continue; } if (debugEnabled) { LOG.debug("uninv, running query: " + namedQueryToExecute); } hasErrors |= resourceManager.bulkNamedQueryDeleteInNewTransaction(overlord, namedQueryToExecute, resourceIds); } // If this resource had packages installed, remove their version and bits if (installedPackageIds.size() > 0) { hasErrors |= cleanOrphanedPackageVersions(overlord, installedPackageIds); } return hasErrors; } @SuppressWarnings("unchecked") private List<Integer> getInstalledPackagesIds(Integer resourceId) { List<Integer> installedPackageIds = new ArrayList<Integer>(); try { installedPackageIds = entityManager.createNamedQuery(PackageVersion.QUERY_FIND_ID_PACKAGES_BY_RESOURCE_ID) .setParameter("resourceId", resourceId) .getResultList(); } catch (Exception e) { LOG.warn("May not have been able get installed packages for" + resourceId + ". This is unlikely to be a problem.", e); return installedPackageIds; } return installedPackageIds; } public boolean cleanPackageBits() { boolean hasErrors = false; if(DatabaseTypeFactory.isPostgres(DatabaseTypeFactory.getDefaultDatabaseType())) { try { Connection c = dataSource.getConnection(); Statement unlinkStatement = c.createStatement(); String unlinkSQLProto = "SELECT lo_unlink(%s)"; PreparedStatement ps = c.prepareStatement("SELECT bits FROM public.rhq_package_bits AS pb\n" + "WHERE pb.id NOT IN ( SELECT pv.package_bits_id FROM public.rhq_package_version pv WHERE pv" + ".package_bits_id IS NOT NULL )"); ResultSet rs = ps.executeQuery(); while(rs.next()) { int oid = rs.getInt(1); String sqlUnlink = String.format(unlinkSQLProto, oid); unlinkStatement.execute(sqlUnlink); } rs.close(); ps.close(); c.close(); } catch (SQLException e) { LOG.error("Could not clean orphaned LOBs from the Postgres", e); } } try { Query query = entityManager.createNamedQuery(PackageBits.DELETE_IF_NO_PACKAGE_VERSION); query.executeUpdate(); } catch (Throwable t) { if (LOG.isDebugEnabled()) { LOG.error("Bulk named query delete error for '" + PackageBits.DELETE_IF_NO_PACKAGE_VERSION, t); } else { LOG.error("Bulk named query delete error for '" + PackageBits.DELETE_IF_NO_PACKAGE_VERSION + ": " + t.getMessage()); } hasErrors = true; } return hasErrors; } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public boolean cleanPackageVersions(List<Integer> packageIds) { boolean hasErrors = false; String queryName = PackageVersion.DELETE_BY_PKG_IF_NO_CONTENT_SOURCES_OR_REPOS; Query deleteVersionQuery = entityManager.createNamedQuery(queryName); try { for (Integer pkgId : packageIds) { deleteVersionQuery.setParameter("packageId", pkgId); deleteVersionQuery.executeUpdate(); } } catch (Throwable t) { if (LOG.isDebugEnabled()) { LOG.error("Bulk named query delete error for '" + queryName, t); } else { LOG.error("Bulk named query delete error for '" + queryName + ": " + t.getMessage()); } hasErrors = true; } return hasErrors; } private boolean cleanOrphanedPackageVersions(Subject subject, List<Integer> packageIds) { boolean hasErrors = false; if (!authorizationManager.isOverlord(subject)) { throw new IllegalArgumentException("Only the overlord can execute arbitrary named query strings"); } hasErrors |= resourceManager.cleanPackageVersions(packageIds); hasErrors |= resourceManager.cleanPackageBits(); return hasErrors; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public boolean bulkNativeQueryDeleteInNewTransaction(Subject subject, String nativeQueryString, List<Integer> resourceIds) { if (!authorizationManager.isOverlord(subject)) { throw new IllegalArgumentException("Only the overlord can execute arbitrary native query strings"); } try { Query nativeQuery = entityManager.createNativeQuery(nativeQueryString); nativeQuery.setParameter("resourceIds", resourceIds); nativeQuery.executeUpdate(); } catch (Throwable t) { if (LOG.isDebugEnabled()) { LOG.error("Bulk native query delete error for '" + nativeQueryString + "' for " + resourceIds, t); } else { LOG.error("Bulk native query delete error for '" + nativeQueryString + "' for " + resourceIds + ": " + t.getMessage()); } return true; // had errors } return false; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public boolean bulkNamedQueryDeleteInNewTransaction(Subject subject, String namedQuery, List<Integer> resourceIds) { if (!authorizationManager.isOverlord(subject)) { throw new IllegalArgumentException("Only the overlord can execute arbitrary named query strings"); } try { Query nativeQuery = entityManager.createNamedQuery(namedQuery); nativeQuery.setParameter("resourceIds", resourceIds); nativeQuery.executeUpdate(); } catch (Throwable t) { if (LOG.isDebugEnabled()) { LOG.error("Bulk named query delete error for '" + namedQuery + "' for " + resourceIds, t); } else { LOG.error("Bulk named query delete error for '" + namedQuery + "' for " + resourceIds + ": " + t.getMessage()); } return true; // had errors } return false; } @Override @RequiredPermission(Permission.MANAGE_INVENTORY) public Resource setResourceStatus(Subject user, Resource resource, InventoryStatus newStatus, boolean setDescendents) { // do special processing if we are being asked to commit the resource to inventory if (newStatus == InventoryStatus.COMMITTED) { if ((resource.getParentResource() != null) && (resource.getParentResource().getInventoryStatus() != InventoryStatus.COMMITTED)) { throw new IllegalStateException("Cannot commit resource [" + resource + "] to inventory, because its parent resource [" + resource.getParentResource() + "] has not yet been committed."); } if ((resource.getResourceType() == null) || (resource.getResourceType().isIgnored())) { if (LOG.isDebugEnabled()) { LOG.debug("Not commiting resource [" + resource + "] to inventory because its type is ignored"); } return resource; } } long now = System.currentTimeMillis(); updateInventoryStatus(resource, newStatus, now); resource = entityManager.merge(resource); if (setDescendents) { for (Resource childResource : resource.getChildResources()) { updateInventoryStatus(childResource, newStatus, now); childResource = entityManager.merge(childResource); setResourceStatus(user, childResource, newStatus, true); } } else if (resource.getResourceType().getCategory() == ResourceCategory.PLATFORM) { // Commit platform services when the platform is committed. for (Resource childResource : resource.getChildResources()) { if (childResource.getResourceType().getCategory() == ResourceCategory.SERVICE) { updateInventoryStatus(childResource, newStatus, now); childResource = entityManager.merge(childResource); setResourceStatus(user, childResource, newStatus, false); } } } return resource; } private void updateInventoryStatus(Resource resource, InventoryStatus newStatus, long now) { resource.setInventoryStatus(newStatus); resource.setItime(now); resource.setAgentSynchronizationNeeded(); } @Override @SuppressWarnings("unchecked") public Resource getResourceById(Subject user, int resourceId) { Query query = entityManager.createNamedQuery(Resource.QUERY_FIND_BY_ID); query.setParameter("resourceId", resourceId); List<Resource> resources = query.getResultList(); if (resources.size() != 1) { throw new ResourceNotFoundException(resourceId); } if (!authorizationManager.canViewResource(user, resourceId)) { throw new PermissionException("User [" + user + "] does not have permission to view resource [" + resourceId + "]"); } return resources.get(0); } @Override @Nullable public Resource getResourceByParentAndKey(Subject user, Resource parent, String key, String plugin, String typeName) { Query query = entityManager.createNamedQuery(Resource.QUERY_FIND_BY_PARENT_AND_KEY); query.setParameter("parent", parent); query.setParameter("key", key); query.setParameter("plugin", plugin); query.setParameter("typeName", typeName); Resource resource; try { resource = (Resource) query.getSingleResult(); } catch (NoResultException e) { return null; } if (!authorizationManager.canViewResource(user, resource.getId())) { throw new PermissionException("You do not have permission to get this resource by parent and key."); } return resource; } @Override @SuppressWarnings("unchecked") public List<ResourceWithAvailability> findResourcesByParentAndType(Subject user, Resource parent, ResourceType type) { Query query; if (authorizationManager.isInventoryManager(user)) { query = entityManager.createNamedQuery(Resource.QUERY_FIND_BY_PARENT_AND_TYPE_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_BY_PARENT_AND_TYPE); query.setParameter("subject", user); } query.setParameter("parent", parent); query.setParameter("type", type); query.setParameter("inventoryStatus", InventoryStatus.COMMITTED); // We are not doing a query with constructor here, as this would fire a select per // resource and row. So we need to construct the ResourceWithAvailability objects ourselves. List<Object[]> objs = query.getResultList(); List<ResourceWithAvailability> results = new ArrayList<ResourceWithAvailability>(objs.size()); for (Object[] ob : objs) { Resource r = (Resource) ob[0]; AvailabilityType at = (AvailabilityType) ob[1]; ResourceWithAvailability rwa = new ResourceWithAvailability(r, at); results.add(rwa); } return results; } @Override @Nullable public Resource getParentResource(int resourceId) { Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { throw new ResourceNotFoundException(resourceId); } Resource parent = resource.getParentResource(); if (parent != null) { parent.getId(); // Important: This ensures Hibernate actually populates parent's fields. } return parent; } @Nullable private Integer getParentResourceId(int resourceId) { Query query = entityManager.createNamedQuery(Resource.QUERY_FIND_PARENT_ID); query.setParameter("id", resourceId); try { return (Integer) query.getSingleResult(); } catch (NoResultException nre) { // this is OK, no parent means this is a platform return null; } } // lineage is a getXXX (not findXXX) because it logically returns a single object, but modeled as a list here @Override public List<Integer> getResourceIdLineage(int resourceId) { List<Integer> lineage = new ArrayList<Integer>(); Integer child = resourceId; Integer parent; while ((parent = getParentResourceId(child)) != null) { lineage.add(parent); child = parent; } return lineage; } // lineage is a getXXX (not findXXX) because it logically returns a single object, but modeled as a list here @Override public List<Resource> getResourceLineage(int resourceId) { LinkedList<Resource> resourceLineage = new LinkedList<Resource>(); Resource resource = entityManager.find(Resource.class, resourceId); if (resource == null) { throw new ResourceNotFoundException(resourceId); } resourceLineage.add(resource); int childResourceId = resourceId; Resource parent; while ((parent = getParentResource(childResourceId)) != null) { resourceLineage.addFirst(parent); childResourceId = parent.getId(); // This also ensures Hibernate actually populates parent's fields. } return resourceLineage; } @Override public List<ResourceLineageComposite> getResourceLineageAndSiblings(Subject subject, int resourceId) { // Get the raw resource lineage up to the platform. We'll check the auth below. List<Resource> rawResourceLineage = getResourceLineage(resourceId); int depth = rawResourceLineage.size(); Resource parent = (depth > 1) ? rawResourceLineage.get(depth - 2) : null; // Build up a list of composite Resources for the ancestry that includes which ancestors, if any, should be // locked from view. boolean isInventoryManager = authorizationManager.isInventoryManager(subject); List<ResourceLineageComposite> resourceLineage = new ArrayList<ResourceLineageComposite>( rawResourceLineage.size()); for (Resource resource : rawResourceLineage) { boolean isLocked = !(isInventoryManager || authorizationManager.canViewResource(subject, resource.getId())); ResourceLineageComposite composite = new ResourceLineageComposite(resource, isLocked); resourceLineage.add(composite); } // Build up the result list, including only the direct ancestors of the Resource and all viewable siblings of // the ancestors. The list will represent a tree, rooted at the platform, in depth-first order. List<ResourceLineageComposite> result = new LinkedList<ResourceLineageComposite>(); for (ResourceLineageComposite ancestor : resourceLineage) { // Always include a direct ancestor. result.add(ancestor); // If the ancestor is not locked, include viewable children. if (!ancestor.isLocked() || ancestor.getResource() == parent) { // Get viewable committed children, but bounded to ensure it's not an overwhelming return set ResourceCriteria criteria = new ResourceCriteria(); criteria.addFilterParentResourceId(ancestor.getResource().getId()); criteria.addSortName(PageOrdering.ASC); criteria.clearPaging();//disable paging as the code assumes all the results will be returned. List<Resource> children = findResourcesByCriteriaBounded(subject, criteria, 0, 0); // Remove any that are in the lineage to avoid repeated handling. children.removeAll(rawResourceLineage); for (Resource child : children) { // Ensure the parentResource field is fetched. (do this here and not via criteria.fetchParentResource // because that option would require inventory manager perm) child.getParentResource().getId(); // The query only returned viewable children, so the composite should not be locked. ResourceLineageComposite composite = new ResourceLineageComposite(child, false); result.add(composite); } } } return result; } @Override public List<ResourceLineageComposite> getResourceLineage(Subject subject, int resourceId) { boolean isInventoryManager = authorizationManager.isInventoryManager(subject); // get the raw resource lineage up to the platform. We'll check the auth below List<Resource> rawLineage = getResourceLineage(resourceId); // record which of the raw ancestry is locked from view List<ResourceLineageComposite> resourceLineage = new ArrayList<ResourceLineageComposite>(rawLineage.size()); for (Resource resource : rawLineage) { boolean isLocked = false; if (!isInventoryManager) { isLocked = !authorizationManager.canViewResource(subject, resource.getId()); } resourceLineage.add(new ResourceLineageComposite(resource, isLocked)); } return resourceLineage; } @Override public Map<Integer, String> getResourcesAncestry(Subject subject, Integer[] resourceIds, ResourceAncestryFormat format) { Map<Integer, String> result = new HashMap<Integer, String>(resourceIds.length); if (resourceIds.length == 0) { return result; } ResourceCriteria resourceCriteria = new ResourceCriteria(); resourceCriteria.addFilterIds(resourceIds); resourceCriteria.fetchResourceType(true); resourceCriteria.clearPaging();//disable paging as the code assumes all the results will be returned. List<Resource> resources = findResourcesByCriteria(subject, resourceCriteria); if (ResourceAncestryFormat.RAW == format) { for (Resource resource : resources) { result.put(resource.getId(), resource.getAncestry()); } return result; } HashSet<Integer> typesSet = new HashSet<Integer>(); HashSet<String> ancestries = new HashSet<String>(); for (Resource resource : resources) { ResourceType type = resource.getResourceType(); if (type != null) { typesSet.add(type.getId()); } ancestries.add(resource.getAncestry()); } // In addition to the types of the result resources, get the types of their ancestry typesSet.addAll(getAncestryTypeIds(ancestries)); ResourceTypeCriteria resourceTypeCriteria = new ResourceTypeCriteria(); resourceTypeCriteria.addFilterIds(typesSet.toArray(new Integer[typesSet.size()])); resourceTypeCriteria.addFilterIgnored(null); // don't worry if they are ignored or not, get the ancestry anyway List<ResourceType> types = typeManager.findResourceTypesByCriteria(subject, resourceTypeCriteria); for (Resource resource : resources) { String decodedAncestry = getDecodedAncestry(resource, types, format); result.put(resource.getId(), decodedAncestry); } return result; } /** * Get the complete set of resource type Ids in the ancestries provided. This is useful for * being able to load all the types in advance of generating decoded values. * * @return */ private HashSet<Integer> getAncestryTypeIds(Collection<String> ancestries) { HashSet<Integer> result = new HashSet<Integer>(); for (String ancestry : ancestries) { if (null == ancestry) { continue; } String[] ancestryEntries = ancestry.split(Resource.ANCESTRY_DELIM); for (String ancestryEntry : ancestryEntries) { String[] entryTokens = ancestryEntry.split(Resource.ANCESTRY_ENTRY_DELIM); int rtId = Integer.valueOf(entryTokens[0]); result.add(rtId); } } return result; } private String getDecodedAncestry(Resource resource, List<ResourceType> typeList, ResourceAncestryFormat format) { String ancestry = resource.getAncestry(); StringBuilder sb = new StringBuilder(""); if (ResourceAncestryFormat.VERBOSE != format) { if (ResourceAncestryFormat.EXTENDED == format) { sb.append(resource.getName()); } if (null != ancestry) { if (ResourceAncestryFormat.EXTENDED == format) { sb.append(" < "); } String[] ancestryEntries = ancestry.split(Resource.ANCESTRY_DELIM); for (int i = 0; i < ancestryEntries.length; ++i) { String[] entryTokens = ancestryEntries[i].split(Resource.ANCESTRY_ENTRY_DELIM); String ancestorName = entryTokens[2]; sb.append((i > 0) ? " < " : ""); sb.append(ancestorName); } } } else { Map<Integer, ResourceType> types = new HashMap<Integer, ResourceType>(typeList.size()); for (ResourceType type : typeList) { types.put(type.getId(), type); } ResourceType type = types.get(resource.getResourceType().getId()); String resourceLongName = getResourceLongName(resource.getName(), type); // decode ancestry if (null != ancestry) { String[] ancestryEntries = ancestry.split(Resource.ANCESTRY_DELIM); for (int i = ancestryEntries.length - 1, j = 0; i >= 0; --i, ++j) { String[] entryTokens = ancestryEntries[i].split(Resource.ANCESTRY_ENTRY_DELIM); int ancestorTypeId = Integer.valueOf(entryTokens[0]); String ancestorName = entryTokens[2]; // indent with spaces if (j > 0) { sb.append("\n"); for (int k = 0; k < j; ++k) { sb.append(" "); } } type = types.get(ancestorTypeId); sb.append(getResourceLongName(ancestorName, type)); } // add target resource, indent with spaces sb.append("\n"); for (int k = 0; k <= ancestryEntries.length; ++k) { sb.append(" "); } sb.append(resourceLongName); } else { // just show the resource name/type info sb.append(resourceLongName); } } return sb.toString(); } private String getResourceLongName(String resourceName, ResourceType type) { return resourceName + " [" + type.getPlugin() + ", " + type.getName() + "]"; } // Used by Portal WAR only @Override @Deprecated @NotNull public Resource getRootResourceForResource(int resourceId) { Query query; Resource result; DatabaseType dbType = DatabaseTypeFactory.getDefaultDatabaseType(); if (DatabaseTypeFactory.isOracle(dbType)) { query = entityManager.createNativeQuery(Resource.QUERY_NATIVE_FIND_RESOURCE_PLATFORM_ORACLE); } else if (DatabaseTypeFactory.isPostgres(dbType)) { query = entityManager.createNativeQuery(Resource.QUERY_NATIVE_FIND_RESOURCE_PLATFORM_POSTGRES); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_ROOT_PLATFORM_OF_RESOURCE); } query.setParameter("resourceId", resourceId); Integer platformId; if (DatabaseTypeFactory.isOracle(dbType)) { platformId = dbType.getInteger(query.getSingleResult()); } else { // native Integer support platformId = (Integer) query.getSingleResult(); } result = entityManager.find(Resource.class, platformId); return result; } @Override @SuppressWarnings("unchecked") public PageList<Resource> findResourceByParentAndInventoryStatus(Subject user, Resource parent, InventoryStatus inventoryStatus, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); Query queryCount; Query query; if (authorizationManager.isInventoryManager(user)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_PARENT_AND_INVENTORY_STATUS_ADMIN); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_BY_PARENT_AND_INVENTORY_STATUS_ADMIN, pageControl); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_PARENT_AND_INVENTORY_STATUS); queryCount.setParameter("subject", user); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_BY_PARENT_AND_INVENTORY_STATUS, pageControl); query.setParameter("subject", user); } queryCount.setParameter("parent", parent); queryCount.setParameter("inventoryStatus", inventoryStatus); long count = (Long) queryCount.getSingleResult(); query.setParameter("parent", parent); query.setParameter("inventoryStatus", inventoryStatus); List<Resource> results = query.getResultList(); return new PageList<Resource>(results, (int) count, pageControl); } @SuppressWarnings("unchecked") public PageList<Resource> findChildResources(Subject user, Resource parent, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); Query queryCount; Query query; if (authorizationManager.isInventoryManager(user)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_CHILDREN_ADMIN); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_CHILDREN_ADMIN, pageControl); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_CHILDREN); queryCount.setParameter("subject", user); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_CHILDREN, pageControl); query.setParameter("subject", user); } queryCount.setParameter("parent", parent); long count = (Long) queryCount.getSingleResult(); query.setParameter("parent", parent); List<Resource> results = query.getResultList(); return new PageList<Resource>(results, (int) count, pageControl); } @Override @SuppressWarnings("unchecked") public List<Integer> findChildrenResourceIds(int parentResourceId, InventoryStatus status) { Query query = entityManager.createNamedQuery(Resource.QUERY_FIND_CHILDREN_IDS_ADMIN); query.setParameter("parentResourceId", parentResourceId); query.setParameter("inventoryStatus", status); List<Integer> results = query.getResultList(); return results; } @Override @SuppressWarnings("unchecked") public PageList<Resource> findChildResourcesByCategoryAndInventoryStatus(Subject user, Resource parent, @Nullable ResourceCategory category, InventoryStatus status, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); Query queryCount; Query query; if (authorizationManager.isInventoryManager(user)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_CHILDREN_BY_CATEGORY_AND_INVENTORY_STATUS_ADMIN); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_CHILDREN_BY_CATEGORY_AND_INVENTORY_STATUS_ADMIN, pageControl); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_CHILDREN_BY_CATEGORY_AND_INVENTORY_STATUS); queryCount.setParameter("subject", user); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_CHILDREN_BY_CATEGORY_AND_INVENTORY_STATUS, pageControl); query.setParameter("subject", user); } queryCount.setParameter("parent", parent); queryCount.setParameter("category", category); queryCount.setParameter("status", status); long count = (Long) queryCount.getSingleResult(); query.setParameter("parent", parent); query.setParameter("category", category); query.setParameter("status", status); List<Resource> results = query.getResultList(); return new PageList<Resource>(results, (int) count, pageControl); } @Override @SuppressWarnings("unchecked") public PageList<Resource> findResourcesByCategory(Subject user, ResourceCategory category, InventoryStatus inventoryStatus, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); Query queryCount; Query query; if (authorizationManager.isInventoryManager(user)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_CATEGORY_AND_INVENTORY_STATUS_ADMIN); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_BY_CATEGORY_AND_INVENTORY_STATUS_ADMIN, pageControl); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_CATEGORY_AND_INVENTORY_STATUS); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_BY_CATEGORY_AND_INVENTORY_STATUS, pageControl); queryCount.setParameter("subject", user); query.setParameter("subject", user); } queryCount.setParameter("category", category); query.setParameter("category", category); queryCount.setParameter("inventoryStatus", inventoryStatus); query.setParameter("inventoryStatus", inventoryStatus); long count = (Long) queryCount.getSingleResult(); List<Resource> results = query.getResultList(); return new PageList<Resource>(results, (int) count, pageControl); } /** * This finder query can be used to find resources with various combinations of attributes in their composite form. * Except for the user parameter, the other parameters can be left null so that the query will not filter by that * attribute. * * @param user * @param category Limit the search to a given {@link ResourceCategory} * @param typeName Limit the search to to {@link ResourceType}(s) with the given name * @param pluginName Limit the search to the plugin with the given name * @param parentResource Limit the search to children of a given parent resource * @param searchString * @param pageControl * * @return */ @Override @SuppressWarnings("unchecked") public PageList<ResourceComposite> findResourceComposites(Subject user, ResourceCategory category, String typeName, String pluginName, Resource parentResource, String searchString, boolean attachParentResource, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); pageControl.addDefaultOrderingField("res.id"); String queryName; String queryCountName; if (authorizationManager.isInventoryManager(user)) { if (attachParentResource) { queryName = Resource.QUERY_FIND_COMPOSITE_WITH_PARENT_ADMIN; } else { queryName = Resource.QUERY_FIND_COMPOSITE_ADMIN; } queryCountName = Resource.QUERY_FIND_COMPOSITE_COUNT_ADMIN; } else { if (attachParentResource) { queryName = Resource.QUERY_FIND_COMPOSITE_WITH_PARENT; } else { queryName = Resource.QUERY_FIND_COMPOSITE; } queryCountName = Resource.QUERY_FIND_COMPOSITE_COUNT; } Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, queryName, pageControl); Query queryCount = PersistenceUtility.createCountQuery(entityManager, queryCountName); if (!authorizationManager.isInventoryManager(user)) { queryCount.setParameter("subject", user); query.setParameter("subject", user); } searchString = QueryUtility.formatSearchParameter(searchString); query.setParameter("category", category); queryCount.setParameter("category", category); query.setParameter("resourceTypeName", typeName); queryCount.setParameter("resourceTypeName", typeName); query.setParameter("pluginName", pluginName); queryCount.setParameter("pluginName", pluginName); query.setParameter("search", searchString); queryCount.setParameter("search", searchString); query.setParameter("escapeChar", QueryUtility.getEscapeCharacter()); queryCount.setParameter("escapeChar", QueryUtility.getEscapeCharacter()); query.setParameter("inventoryStatus", InventoryStatus.COMMITTED); queryCount.setParameter("inventoryStatus", InventoryStatus.COMMITTED); query.setParameter("parentResource", parentResource); queryCount.setParameter("parentResource", parentResource); long count = (Long) queryCount.getSingleResult(); return new PageList<ResourceComposite>(query.getResultList(), (int) count, pageControl); } /** * Get a Resource Composite for Resources limited by the given parameters * * @param user User executing the query * @param category Category this query should be limited to * @param resourceTypeId the PK of the desired resource type or -1 if no limit * @param parentResource the desired parent resource or null if no limit * @param pageControl * * @return */ @Override public PageList<ResourceComposite> findResourceCompositeForParentAndTypeAndCategory(Subject user, ResourceCategory category, int resourceTypeId, Resource parentResource, PageControl pageControl) { // pageControl.initDefaultOrderingField(); // not needed since findResourceComposites will set it ResourceType resourceType = null; if (resourceTypeId != -1) { try { resourceType = entityManager.find(ResourceType.class, resourceTypeId); } catch (NoResultException nre) { // No problem } } String typeNameFilter = (resourceType == null) ? null : resourceType.getName(); String pluginNameFilter = (resourceType == null) ? null : resourceType.getPlugin(); return findResourceComposites(user, category, typeNameFilter, pluginNameFilter, parentResource, null, false, pageControl); } @Override @SuppressWarnings("unchecked") public int[] getResourceCountSummary(Subject user, InventoryStatus status) { Query query; if (authorizationManager.isInventoryManager(user)) { query = entityManager.createNamedQuery(Resource.QUERY_FIND_RESOURCE_SUMMARY_BY_INVENTORY_STATUS_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_RESOURCE_SUMMARY_BY_INVENTORY_STATUS); query.setParameter("subject", user); } query.setParameter("inventoryStatus", status); int[] counts = new int[3]; List<Object[]> resultList = query.getResultList(); for (Object[] row : resultList) { switch ((ResourceCategory) row[0]) { case PLATFORM: counts[0] = ((Long) row[1]).intValue(); break; case SERVER: counts[1] = ((Long) row[1]).intValue(); break; case SERVICE: counts[2] = ((Long) row[1]).intValue(); break; } } return counts; } @Override public int getResourceCountByCategory(Subject user, ResourceCategory category, InventoryStatus status) { Query queryCount; if (authorizationManager.isInventoryManager(user)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_CATEGORY_AND_INVENTORY_STATUS_ADMIN); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_CATEGORY_AND_INVENTORY_STATUS); queryCount.setParameter("subject", user); } queryCount.setParameter("inventoryStatus", status); queryCount.setParameter("category", category); long count = (Long) queryCount.getSingleResult(); return (int) count; } @Override public int getResourceCountByTypeAndIds(Subject user, ResourceType type, int[] resourceIds) { Query queryCount; if (authorizationManager.isInventoryManager(user)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_TYPE_AND_IDS_ADMIN); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_TYPE_AND_IDS); queryCount.setParameter("subject", user); } List<Integer> resourceList = ArrayUtils.wrapInList(resourceIds); queryCount.setParameter("ids", resourceList); queryCount.setParameter("type", type); long count = (Long) queryCount.getSingleResult(); return (int) count; } @Override @SuppressWarnings("unchecked") public List<Integer> findResourcesMarkedForAsyncDeletion(Subject user) { if (!authorizationManager.isOverlord(user)) { throw new IllegalArgumentException("Only the overlord can purge resources marked for deletion"); } Query query = entityManager.createNamedQuery(Resource.QUERY_FIND_RESOURCES_MARKED_FOR_ASYNC_DELETION); List<Integer> results = query.getResultList(); return results; } @Override @SuppressWarnings("unchecked") public List<RecentlyAddedResourceComposite> findRecentlyAddedPlatforms(Subject user, long ctime, int maxItems) { Query query; if (authorizationManager.isInventoryManager(user)) { query = entityManager.createNamedQuery(Resource.QUERY_RECENTLY_ADDED_PLATFORMS_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_RECENTLY_ADDED_PLATFORMS); query.setParameter("subject", user); } query.setParameter("oldestEpochTime", ctime); if ((maxItems > 100) || (maxItems < 0)) {//cap infininte(-1) and large requests to 100 query.setMaxResults(100); // this query is only used by the dashboard portlet, let's not blow it up } else { query.setMaxResults(maxItems); } return query.getResultList(); } @Override @SuppressWarnings("unchecked") public List<RecentlyAddedResourceComposite> findRecentlyAddedServers(Subject user, long ctime, int platformId) { Query query; if (authorizationManager.isInventoryManager(user)) { query = entityManager.createNamedQuery(Resource.QUERY_RECENTLY_ADDED_SERVERS_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_RECENTLY_ADDED_SERVERS); query.setParameter("subject", user); } query.setParameter("oldestEpochTime", ctime); query.setParameter("platformId", platformId); query.setMaxResults(100); // this query is only used by the dashboard portlet, let's not blow it up return query.getResultList(); } @Override @SuppressWarnings("unchecked") public AutoGroupComposite getResourceAutoGroup(Subject user, int resourceId) { Query query; boolean isInventoryManager = authorizationManager.isInventoryManager(user); if (isInventoryManager) { query = entityManager.createNamedQuery(Resource.QUERY_FIND_RESOURCE_AUTOGROUP_COMPOSITE_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_RESOURCE_AUTOGROUP_COMPOSITE); query.setParameter("subject", user); } query.setParameter("id", resourceId); AutoGroupComposite result; try { result = (AutoGroupComposite) query.getSingleResult(); } catch (NoResultException nore) { return null; // Better throw a PermissionException ? } if (isInventoryManager) { query = entityManager.createNamedQuery(Resource.QUERY_FIND_AVAILABILITY_BY_RESOURCE_ID_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_AVAILABILITY_BY_RESOURCE_ID); query.setParameter("subject", user); } query.setParameter("id", resourceId); result.setResources(query.getResultList()); // query result is a List<ResourceWithAvailability> return result; } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public List<AutoGroupComposite> findResourcesAutoGroups(Subject subject, int[] resourceIds) { List<AutoGroupComposite> results = new ArrayList<AutoGroupComposite>(); List<Integer> ids = ArrayUtils.wrapInList(resourceIds); if ((ids == null) || (ids.size() == 0)) { return results; } boolean isInventoryManager = authorizationManager.isInventoryManager(subject); Query query; if (isInventoryManager) { query = entityManager.createNamedQuery(Resource.QUERY_FIND_RESOURCE_AUTOGROUPS_COMPOSITE_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_RESOURCE_AUTOGROUPS_COMPOSITE); query.setParameter("subject", subject); } query.setParameter("ids", ids); AutoGroupComposite oneComp; try { oneComp = (AutoGroupComposite) query.getSingleResult(); } catch (NoResultException nre) { return null; // better throw a PermissionException ? } if (isInventoryManager) { query = entityManager.createNamedQuery(Resource.QUERY_FIND_AVAILABILITY_BY_RESOURCE_IDS_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_AVAILABILITY_BY_RESOURCE_IDS); query.setParameter("subject", subject); } query.setParameter("ids", ids); // We are not doing a query with constructor here, as this would fire a select per // resource and row. So we need to construct the ResourceWithAvailability objects ourselves. List<Object[]> objs = query.getResultList(); for (Object[] ob : objs) { Resource r = (Resource) ob[0]; AvailabilityType at = (AvailabilityType) ob[1]; ResourceWithAvailability rwa = new ResourceWithAvailability(r, at); AutoGroupComposite comp = new AutoGroupComposite(oneComp); List res = new ArrayList(1); // hack to get around type safety res.add(rwa); comp.setResources(res); results.add(comp); } return results; } @Override @SuppressWarnings("unchecked") @NotNull public List<AutoGroupComposite> findChildrenAutoGroups(Subject user, int parentResourceId, int[] resourceTypeIds) { Query query; List<Integer> typeIds = ArrayUtils.wrapInList(resourceTypeIds); if (null != typeIds) { if (authorizationManager.isInventoryManager(user)) { query = entityManager.createNamedQuery(Resource.QUERY_FIND_CHILDREN_AUTOGROUP_COMPOSITES_BY_TYPE_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_CHILDREN_AUTOGROUP_COMPOSITES_BY_TYPE); query.setParameter("subject", user); } query.setParameter("resourceTypeIds", typeIds); } else { if (authorizationManager.isInventoryManager(user)) { query = entityManager.createNamedQuery(Resource.QUERY_FIND_CHILDREN_AUTOGROUP_COMPOSITES_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_CHILDREN_AUTOGROUP_COMPOSITES); query.setParameter("subject", user); } } Resource parentResource = entityManager.getReference(Resource.class, parentResourceId); query.setParameter("parent", parentResource); query.setParameter("inventoryStatus", InventoryStatus.COMMITTED); List<AutoGroupComposite> resourceAutoGroups = query.getResultList(); for (AutoGroupComposite composite : resourceAutoGroups) { if (authorizationManager.isInventoryManager(user)) { query = entityManager.createNamedQuery(Resource.QUERY_FIND_BY_PARENT_AND_TYPE_ADMIN); } else { query = entityManager.createNamedQuery(Resource.QUERY_FIND_BY_PARENT_AND_TYPE); query.setParameter("subject", user); } query.setParameter("parent", parentResource); query.setParameter("type", composite.getResourceType()); query.setParameter("inventoryStatus", InventoryStatus.COMMITTED); List<Object[]> objs = query.getResultList(); @SuppressWarnings("rawtypes") List results = new ArrayList<ResourceWithAvailability>(objs.size()); for (Object[] ob : objs) { Resource r = (Resource) ob[0]; AvailabilityType at = (AvailabilityType) ob[1]; ResourceWithAvailability rwa = new ResourceWithAvailability(r, at); results.add(rwa); } composite.setResources(results); } return resourceAutoGroups; } @Override public List<AutoGroupComposite> findChildrenAutoGroups(Subject user, int parentResourceId) { return findChildrenAutoGroups(user, parentResourceId, null); } @Override @SuppressWarnings("unchecked") public PageList<Resource> findExplicitResourcesByResourceGroup(Subject user, ResourceGroup group, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); Query queryCount; Query query; if (authorizationManager.isInventoryManager(user)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_EXPLICIT_RESOURCE_GROUP_ADMIN); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_BY_EXPLICIT_RESOURCE_GROUP_ADMIN, pageControl); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_EXPLICIT_RESOURCE_GROUP); queryCount.setParameter("subject", user); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_BY_EXPLICIT_RESOURCE_GROUP, pageControl); query.setParameter("subject", user); } queryCount.setParameter("group", group); long count = (Long) queryCount.getSingleResult(); query.setParameter("group", group); List<Resource> results = query.getResultList(); return new PageList<Resource>(results, (int) count, pageControl); } @Override @SuppressWarnings("unchecked") public List<Integer> findExplicitResourceIdsByResourceGroup(int resourceGroupId) { Query query = entityManager.createNamedQuery(Resource.QUERY_FIND_EXPLICIT_IDS_BY_RESOURCE_GROUP_ADMIN); query.setParameter("groupId", resourceGroupId); List<Integer> results = query.getResultList(); return results; } @Override @SuppressWarnings("unchecked") public List<Integer> findImplicitResourceIdsByResourceGroup(int resourceGroupId) { Query query = entityManager.createNamedQuery(Resource.QUERY_FIND_IMPLICIT_IDS_BY_RESOURCE_GROUP_ADMIN); query.setParameter("groupId", resourceGroupId); List<Integer> results = query.getResultList(); return results; } @Override @SuppressWarnings("unchecked") public List<ResourceIdFlyWeight> findFlyWeights(int[] resourceIds) { Integer[] ids = ArrayUtils.wrapInArray(resourceIds); if (ids.length == 0) { return new ArrayList<ResourceIdFlyWeight>(); } List<ResourceIdFlyWeight> results = new ArrayList<ResourceIdFlyWeight>(); Arrays.sort(ids); // likely that ids in close proximity are co-located physically (data block-wise) Query query = entityManager.createNamedQuery(Resource.QUERY_FIND_FLY_WEIGHTS_BY_RESOURCE_IDS); for (int i = 0; i < ids.length; i += 1000) { Integer[] batchRange = ArrayUtils.copyOfRange(ids, i, i + 1000); query.setParameter("resourceIds", Arrays.asList(batchRange)); List<ResourceIdFlyWeight> batchResults = query.getResultList(); results.addAll(batchResults); } return results; } @Override @SuppressWarnings("unchecked") public PageList<Resource> findImplicitResourcesByResourceGroup(Subject user, ResourceGroup group, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); Query queryCount; Query query; if (authorizationManager.isInventoryManager(user)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_IMPLICIT_RESOURCE_GROUP_ADMIN); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_BY_IMPLICIT_RESOURCE_GROUP_ADMIN, pageControl); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_BY_IMPLICIT_RESOURCE_GROUP); queryCount.setParameter("subject", user); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_BY_IMPLICIT_RESOURCE_GROUP, pageControl); query.setParameter("subject", user); } queryCount.setParameter("group", group); long count = (Long) queryCount.getSingleResult(); query.setParameter("group", group); List<Resource> results = query.getResultList(); return new PageList<Resource>(results, (int) count, pageControl); } @Override @SuppressWarnings("unchecked") // RHQ-796, queries now return the parent resource attached public PageList<ResourceWithAvailability> findExplicitResourceWithAvailabilityByResourceGroup(Subject subject, ResourceGroup group, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); Query queryCount; Query query; if (authorizationManager.isInventoryManager(subject)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_EXPLICIT_RESOURCES_FOR_RESOURCE_GROUP_COUNT_ADMIN); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_EXPLICIT_RESOURCES_WITH_AVAILABILITY_FOR_RESOURCE_GROUP_ADMIN, pageControl); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_FIND_EXPLICIT_RESOURCES_FOR_RESOURCE_GROUP_COUNT); queryCount.setParameter("subject", subject); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_EXPLICIT_RESOURCES_WITH_AVAILABILITY_FOR_RESOURCE_GROUP, pageControl); query.setParameter("subject", subject); } queryCount.setParameter("groupId", group.getId()); long count = (Long) queryCount.getSingleResult(); query.setParameter("groupId", group.getId()); List<ResourceWithAvailability> results = query.getResultList(); return new PageList<ResourceWithAvailability>(results, (int) count, pageControl); } @Override @SuppressWarnings("unchecked") // RHQ-796, queries now return the parent resource attached public PageList<ResourceWithAvailability> findImplicitResourceWithAvailabilityByResourceGroup(Subject subject, ResourceGroup group, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); Query queryCount; Query query; if (authorizationManager.isInventoryManager(subject)) { queryCount = entityManager .createNamedQuery(Resource.QUERY_FIND_IMPLICIT_RESOURCES_FOR_RESOURCE_GROUP_COUNT_ADMIN); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_IMPLICIT_RESOURCES_WITH_AVAILABILITY_FOR_RESOURCE_GROUP_ADMIN, pageControl); } else { queryCount = entityManager .createNamedQuery(Resource.QUERY_FIND_IMPLICIT_RESOURCES_FOR_RESOURCE_GROUP_COUNT); queryCount.setParameter("subject", subject); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_FIND_IMPLICIT_RESOURCES_WITH_AVAILABILITY_FOR_RESOURCE_GROUP, pageControl); query.setParameter("subject", subject); } queryCount.setParameter("groupId", group.getId()); long count = (Long) queryCount.getSingleResult(); query.setParameter("groupId", group.getId()); List<ResourceWithAvailability> results = query.getResultList(); return new PageList<ResourceWithAvailability>(results, (int) count, pageControl); } @Override @SuppressWarnings("unchecked") public PageList<ResourceHealthComposite> findResourceHealth(Subject user, int[] resourceIds, PageControl pc) { pc.initDefaultOrderingField("res.name"); List<Integer> resourceIdList = ArrayUtils.wrapInList(resourceIds); if ((resourceIdList == null) || (resourceIdList.size() == 0)) { return new PageList<ResourceHealthComposite>(pc); } Query queryCount = PersistenceUtility .createCountQuery(entityManager, Resource.QUERY_GET_RESOURCE_HEALTH_BY_IDS); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_GET_RESOURCE_HEALTH_BY_IDS, pc); queryCount.setParameter("resourceIds", resourceIdList); query.setParameter("resourceIds", resourceIdList); // because of the use of the GROUP BY clause, the query count will be returned as // the number of rows not as a single number long count = queryCount.getResultList().size(); List<ResourceHealthComposite> results = query.getResultList(); return new PageList<ResourceHealthComposite>(results, (int) count, pc); } @SuppressWarnings("unused") private void setImplicitMarkers(ResourceGroup group, List<ResourceWithAvailability> resources) { for (ResourceWithAvailability res : resources) { boolean explicitMember = false; for (ResourceGroup explicitGroup : res.getResource().getExplicitGroups()) { if (explicitGroup.getId() == group.getId()) { explicitMember = true; break; } } res.setExplicit(explicitMember); } } @Override @RequiredPermission(Permission.MANAGE_INVENTORY) @SuppressWarnings("unchecked") // note: this method also eagerly loads the parent resource, so that more context info is displayed for each record public PageList<Resource> findAvailableResourcesForResourceGroup(Subject user, int groupId, ResourceType type, ResourceCategory category, String nameFilter, int[] excludeIds, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); List<Integer> excludeList = ArrayUtils.wrapInList(excludeIds); Query queryCount; Query query; /* * don't use WITH_PARENT form for the queryCount; after it runs through the PersistenceUtility * you'll get the error "query specified join fetching, but the owner of the fetched association * was not present in the select list" */ if ((excludeList != null) && (excludeList.size() != 0)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_GET_AVAILABLE_RESOURCES_FOR_RESOURCE_GROUP_WITH_EXCLUDES); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_GET_AVAILABLE_RESOURCES_WITH_PARENT_FOR_RESOURCE_GROUP_WITH_EXCLUDES, pageControl); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_GET_AVAILABLE_RESOURCES_FOR_RESOURCE_GROUP); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_GET_AVAILABLE_RESOURCES_WITH_PARENT_FOR_RESOURCE_GROUP, pageControl); } if ((excludeList != null) && (excludeList.size() != 0)) { queryCount.setParameter("excludeIds", excludeList); query.setParameter("excludeIds", excludeList); } nameFilter = QueryUtility.formatSearchParameter(nameFilter); queryCount.setParameter("groupId", groupId); query.setParameter("groupId", groupId); queryCount.setParameter("type", type); query.setParameter("type", type); queryCount.setParameter("category", category); query.setParameter("category", category); query.setParameter("search", nameFilter); queryCount.setParameter("search", nameFilter); query.setParameter("escapeChar", QueryUtility.getEscapeCharacter()); queryCount.setParameter("escapeChar", QueryUtility.getEscapeCharacter()); query.setParameter("inventoryStatus", InventoryStatus.COMMITTED); queryCount.setParameter("inventoryStatus", InventoryStatus.COMMITTED); long count = (Long) queryCount.getSingleResult(); List<Resource> resources = query.getResultList(); return new PageList<Resource>(resources, (int) count, pageControl); } @Override @SuppressWarnings("unchecked") public PageList<Resource> findAvailableResourcesForRepo(Subject user, int repoId, String search, ResourceCategory category, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); Query queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_GET_AVAILABLE_RESOURCES_FOR_REPO); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_GET_AVAILABLE_RESOURCES_FOR_REPO, pageControl); queryCount.setParameter("repoId", repoId); query.setParameter("repoId", repoId); search = QueryUtility.formatSearchParameter(search); queryCount.setParameter("search", search); query.setParameter("search", search); query.setParameter("escapeChar", QueryUtility.getEscapeCharacter()); queryCount.setParameter("escapeChar", QueryUtility.getEscapeCharacter()); queryCount.setParameter("category", category); query.setParameter("category", category); query.setParameter("inventoryStatus", InventoryStatus.COMMITTED); queryCount.setParameter("inventoryStatus", InventoryStatus.COMMITTED); long count = (Long) queryCount.getSingleResult(); List<Resource> resources = query.getResultList(); return new PageList<Resource>(resources, (int) count, pageControl); } @Override @SuppressWarnings("unchecked") // note, typeId can be null public PageList<Resource> findAvailableResourcesForDashboardPortlet(Subject user, Integer typeId, ResourceCategory category, int[] excludeIds, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); List<Integer> excludeList = ArrayUtils.wrapInList(excludeIds); Query queryCount; Query query; if ((excludeList != null) && (excludeList.size() != 0)) { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_GET_AVAILABLE_RESOURCES_FOR_DASHBOARD_PORTLET_WITH_EXCLUDES); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_GET_AVAILABLE_RESOURCES_FOR_DASHBOARD_PORTLET_WITH_EXCLUDES, pageControl); } else { queryCount = PersistenceUtility.createCountQuery(entityManager, Resource.QUERY_GET_AVAILABLE_RESOURCES_FOR_DASHBOARD_PORTLET); query = PersistenceUtility.createQueryWithOrderBy(entityManager, Resource.QUERY_GET_AVAILABLE_RESOURCES_FOR_DASHBOARD_PORTLET, pageControl); } if ((excludeList != null) && (excludeList.size() != 0)) { queryCount.setParameter("excludeIds", excludeList); query.setParameter("excludeIds", excludeList); } queryCount.setParameter("typeId", typeId); query.setParameter("typeId", typeId); queryCount.setParameter("category", category); query.setParameter("category", category); query.setParameter("inventoryStatus", InventoryStatus.COMMITTED); queryCount.setParameter("inventoryStatus", InventoryStatus.COMMITTED); long count = (Long) queryCount.getSingleResult(); List<Resource> resources = query.getResultList(); return new PageList<Resource>(resources, (int) count, pageControl); } @Override @SuppressWarnings("unchecked") public PageList<Resource> findResourceByIds(Subject subject, int[] resourceIds, boolean attachParentResource, PageControl pageControl) { pageControl.initDefaultOrderingField("res.name"); List<Integer> idList = ArrayUtils.wrapInList(resourceIds); if ((idList == null) || (idList.size() == 0)) { return new PageList<Resource>(Collections.EMPTY_LIST, 0, pageControl); } String queryCountName; String queryName; /* * don't use WITH_PARENT form for the queryCountName; after it runs through the PersistenceUtility * you'll get the error "query specified join fetching, but the owner of the fetched association * was not present in the select list" */ if (authorizationManager.isInventoryManager(subject)) { if (attachParentResource) { queryName = Resource.QUERY_FIND_WITH_PARENT_BY_IDS_ADMIN; } else { queryName = Resource.QUERY_FIND_BY_IDS_ADMIN; } queryCountName = Resource.QUERY_FIND_BY_IDS_ADMIN; } else { if (attachParentResource) { queryName = Resource.QUERY_FIND_WITH_PARENT_BY_IDS; } else { queryName = Resource.QUERY_FIND_BY_IDS; } queryCountName = Resource.QUERY_FIND_BY_IDS; } Query queryCount = PersistenceUtility.createCountQuery(entityManager, queryCountName); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, queryName, pageControl); if (!authorizationManager.isInventoryManager(subject)) { queryCount.setParameter("subject", subject); query.setParameter("subject", subject); } queryCount.setParameter("ids", idList); query.setParameter("ids", idList); long count = (Long) queryCount.getSingleResult(); List<Resource> resources = query.getResultList(); return new PageList<Resource>(resources, (int) count, pageControl); } @Override public Resource getResourceTree(int rootResourceId, boolean recursive) { Subject overlord = subjectManager.getOverlord(); Resource root = getResourceById(overlord, rootResourceId); if (root != null) { prefetchResource(root, recursive); // load the parent - note we only load the root resource's parent if (root.getParentResource() != null) { root.getParentResource().getId(); } } return root; } @Override @NotNull @SuppressWarnings("unchecked") public List<ResourceError> findResourceErrors(Subject user, int resourceId, ResourceErrorType errorType) { // do authz check if (!authorizationManager.canViewResource(user, resourceId)) { throw new PermissionException("User [" + user + "] does not have permission to view resource [" + resourceId + "]"); } // we passed authz check, now get the errors Query query = entityManager.createNamedQuery(ResourceError.QUERY_FIND_BY_RESOURCE_ID_AND_ERROR_TYPE); query.setParameter("resourceId", resourceId); query.setParameter("errorType", errorType); return query.getResultList(); } @Override @NotNull @SuppressWarnings("unchecked") public List<ResourceError> findResourceErrors(Subject user, int resourceId) { // do authz check if (!authorizationManager.canViewResource(user, resourceId)) { throw new PermissionException("User [" + user + "] does not have permission to view resource [" + resourceId + "]"); } // we passed authz check, now get the errors Query query = entityManager.createNamedQuery(ResourceError.QUERY_FIND_BY_RESOURCE_ID); query.setParameter("resourceId", resourceId); return query.getResultList(); } @Override public void addResourceError(ResourceError resourceError) { ResourceErrorType resourceErrorType = resourceError.getErrorType(); // there should be at most one invalid plugin configuration error, availability check // or upgrade error per resource, so delete any currently existing ones before we add this new one Subject overlord = subjectManager.getOverlord(); int resourceId = resourceError.getResource().getId(); resourceManager.clearResourceConfigErrorByType(overlord, resourceId, resourceErrorType); entityManager.persist(resourceError); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public int clearResourceConfigErrorByType(Subject subject, int resourceId, ResourceErrorType resourceErrorType) { if (!authorizationManager.canViewResource(subject, resourceId)) { throw new PermissionException("Cannot delete resource errors of type [" + resourceErrorType + "]. User [" + subject.getName() + "] does not have permission to operate on resource ID [" + resourceId + "]."); } TypedQuery<Integer> query = entityManager .createNamedQuery(ResourceError.QUERY_FIND_ID_BY_RESOURCE_ID_AND_ERROR_TYPE, Integer.class) // .setParameter("resourceId", resourceId) // .setParameter("type", resourceErrorType); List<Integer> entityIds = query.getResultList(); int deleted = 0; for (Integer entityId : entityIds) { deleted += entityManager.createNamedQuery(ResourceError.QUERY_DELETE_BY_ID) // .setParameter("id", entityId).executeUpdate(); } return deleted; } @Override public void clearResourceConfigError(int resourceId) { // TODO change sig to get user passed in, rather than using overlord/assuming user is authz'ed Subject s = subjectManager.getOverlord(); // make a direct local call - no need to go through the ByType method's REQUIRES_NEW interface here int cleared = clearResourceConfigErrorByType(s, resourceId, ResourceErrorType.INVALID_PLUGIN_CONFIGURATION); if (cleared > 1) { LOG.warn("Resource [" + resourceId + "] had [" + cleared + "] INVALID_PLUGIN_CONFIGURATION ResourceErrors associated with it."); } } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void deleteResourceError(Subject user, int resourceErrorId) { ResourceError error = entityManager.find(ResourceError.class, resourceErrorId); if (error != null) { if (!authorizationManager.hasResourcePermission(user, Permission.MODIFY_RESOURCE, error.getResource() .getId())) { throw new PermissionException("Cannot delete Resource error [" + resourceErrorId + "]. User [" + user + "] does not have permission to modify Resource [" + error.getResource().getName() + "]."); } entityManager.remove(error); } } @Override public int removeResourceErrorDuplicates() { TypedQuery<ResourceErrorTypeComposite> invalidCompositesQuery = entityManager.createNamedQuery( // ResourceError.QUERY_FIND_ALL_INVALID_RESOURCE_ERROR_TYPE_COMPOSITE, // ResourceErrorTypeComposite.class // ); List<ResourceErrorTypeComposite> invalidComposites = invalidCompositesQuery.getResultList(); int deleted = 0; for (ResourceErrorTypeComposite invalidComposite : invalidComposites) { TypedQuery<Integer> findDuplicatesQuery = entityManager.createNamedQuery( // ResourceError.QUERY_FIND_ID_BY_RESOURCE_ID_AND_ERROR_TYPE_OLDER_THAN, // Integer.class // ) // .setParameter("resourceId", invalidComposite.getResourceId()) // .setParameter("type", invalidComposite.getResourceErrorType()) // .setParameter("upToTime", invalidComposite.getLastOccurred()); List<Integer> duplicatesIds = findDuplicatesQuery.getResultList(); for (Integer duplicateId : duplicatesIds) { deleted += entityManager.createNamedQuery(ResourceError.QUERY_DELETE_BY_ID) // .setParameter("id", duplicateId).executeUpdate(); } } return deleted; } @Override public int removeStaleAvailabilityResourceErrors() { TypedQuery<Integer> query = entityManager.createNamedQuery( ResourceError.QUERY_FIND_ALL_STALE_AVAILABILITY_RESOURCE_ERROR, Integer.class); List<Integer> resourceIds = query.getResultList(); int deleted = 0; for (Integer resourceId : resourceIds) { deleted += clearResourceConfigErrorByType(subjectManager.getOverlord(), resourceId, ResourceErrorType.AVAILABILITY_CHECK); } return deleted; } @Override public Map<Integer, InventoryStatus> getResourceStatuses(int rootResourceId, boolean descendents) { Resource root = entityManager.find(Resource.class, rootResourceId); Map<Integer, InventoryStatus> statuses = new LinkedHashMap<Integer, InventoryStatus>(); statuses.put(root.getId(), root.getInventoryStatus()); getResourceStatuses(rootResourceId, descendents, statuses); return statuses; } @Override public Resource getPlatform(Agent agent) { Query query = entityManager.createNamedQuery(Resource.QUERY_FIND_PLATFORM_BY_AGENT); query.setParameter("category", ResourceCategory.PLATFORM); query.setParameter("agent", agent); try { Resource platform = (Resource) query.getSingleResult(); return platform; } catch (NoResultException e) { //this means that the agent didn't send any info to us yet. //this can happen during the inital resource upgrade sync between //the agent and server. return null; } } @Override @SuppressWarnings("unchecked") public ResourceAvailabilitySummary getAvailabilitySummary(Subject user, int resourceId) { if (!authorizationManager.canViewResource(user, resourceId)) { throw new PermissionException("Cannot view resource availability. User [" + user + "] does not have permission to view the resource [" + resourceId + "]."); } Query query = entityManager.createNamedQuery(Availability.FIND_BY_RESOURCE); query.setParameter("resourceId", resourceId); List<Availability> availabilities = query.getResultList(); return new ResourceAvailabilitySummary(availabilities); } @Override @SuppressWarnings("unchecked") public List<ResourceFlyweight> findResourcesByAgent(Subject user, int agentId, PageControl unlimitedInstance) { // Note: I didn't put these queries in as named queries since they have very specific prefeching // for this use case. String reportingQueryString = "" // + " SELECT res.id, res.uuid, res.name, res.resourceKey, " // + " parent.id, parent.name, " // + " currentAvail.availabilityType, " // + " type.id, type.name, type.plugin, type.singleton, type.category, " // + " type.subCategory" // + " FROM Resource res " // + " JOIN res.currentAvailability currentAvail " // + " JOIN res.resourceType type " // + " LEFT JOIN res.parentResource parent " // + " WHERE res.inventoryStatus = :inventoryStatus " // + " AND res.agent.id = :agentId"; Query reportingQuery = entityManager.createQuery(reportingQueryString); reportingQuery.setParameter("agentId", agentId); reportingQuery.setParameter("inventoryStatus", InventoryStatus.COMMITTED); List<Object[]> reportingQueryResults = reportingQuery.getResultList(); List<ResourceFlyweight> resources = getFlyWeightObjectGraphFromReportingQueryResults(reportingQueryResults); if (!authorizationManager.isInventoryManager(user)) { String authorizationQueryString = "" // + "SELECT res.id " // + " FROM Resource res " // + " WHERE res.inventoryStatus = :inventoryStatus " // + " AND res.agent.id = :agentId " // + " AND res.id IN ( SELECT rr.id " // + " FROM Resource rr " // + " JOIN rr.implicitGroups g " // + " JOIN g.roles r " // + " JOIN r.subjects s " // + " WHERE s = :subject)"; Query authorizationQuery = entityManager.createQuery(authorizationQueryString); authorizationQuery.setParameter("agentId", agentId); authorizationQuery.setParameter("subject", user); authorizationQuery.setParameter("inventoryStatus", InventoryStatus.COMMITTED); List<Integer> visibleResources = authorizationQuery.getResultList(); HashSet<Integer> visibleIdSet = new HashSet<Integer>(visibleResources); for (ResourceFlyweight res : resources) { res.setLocked(!visibleIdSet.contains(res.getId())); } } return resources; } private List<ResourceFlyweight> getFlyWeightObjectGraphFromReportingQueryResults(List<Object[]> reportQueryResults) { List<ResourceFlyweight> resources = new ArrayList<ResourceFlyweight>(); FlyweightCache flyweightCache = new FlyweightCache(); for (Object[] prefetched : reportQueryResults) { // casts int i = 0; Integer resourceId = (Integer) prefetched[i++]; String resourceUuid = (String) prefetched[i++]; String resourceName = (String) prefetched[i++]; String resourceKey = (String) prefetched[i++]; Integer parentId = (Integer) prefetched[i++]; i++; // skip parentName AvailabilityType availType = (AvailabilityType) prefetched[i++]; Integer typeId = (Integer) prefetched[i++]; String typeName = (String) prefetched[i++]; String typePlugin = (String) prefetched[i++]; Boolean typeSingleton = (Boolean) prefetched[i++]; ResourceCategory typeCategory = (ResourceCategory) prefetched[i++]; String subCategory = (String) prefetched[i]; //we don't need the resource type reference here, only in the cache flyweightCache .constructResourceType(typeId, typeName, typePlugin, typeSingleton, typeCategory, subCategory); ResourceFlyweight resourceFlyweight = flyweightCache.constructResource(resourceId, resourceName, resourceUuid, resourceKey, parentId, typeId, availType); resources.add(resourceFlyweight); } return resources; } @Override @SuppressWarnings("unchecked") public List<ResourceFlyweight> findResourcesByCompatibleGroup(Subject user, int compatibleGroupId, PageControl pageControl) { // Note: I didn't put these queries in as named queries since they have very specific pre-fetching // for this use case. String reportingQueryString = "" // + " SELECT res.id, res.uuid, res.name, res.resourceKey, " // + " parent.id, parent.name, " // + " currentAvail.availabilityType, " // + " type.id, type.name, type.plugin, type.singleton, type.category, " // + " type.subCategory " + " FROM Resource res " // + " JOIN res.implicitGroups g " // + " JOIN res.currentAvailability currentAvail " // + " JOIN res.resourceType type " // + " LEFT JOIN res.parentResource parent " // + " WHERE res.inventoryStatus = :inventoryStatus " // + " AND g.id = :groupId"; Query reportingQuery = entityManager.createQuery(reportingQueryString); reportingQuery.setParameter("groupId", compatibleGroupId); reportingQuery.setParameter("inventoryStatus", InventoryStatus.COMMITTED); List<Object[]> reportingQueryResults = reportingQuery.getResultList(); List<ResourceFlyweight> resources = getFlyWeightObjectGraphFromReportingQueryResults(reportingQueryResults); // if (false) { //!authorizationManager.isInventoryManager(user)) { // String authorizationQueryString = "" // // + " SELECT res.id \n" // // + " FROM Resource res " // // + " WHERE res.inventoryStatus = :inventoryStatus " // // + " AND (res.id IN (SELECT rr.id FROM Resource rr JOIN rr.explicitGroups g WHERE g.id = :groupId)\n" // + " OR res.id IN (SELECT rr.id FROM Resource rr JOIN rr.parentResource.explicitGroups g WHERE g.id = :groupId)\n" // + " OR res.id IN (SELECT rr.id FROM Resource rr JOIN rr.parentResource.parentResource.explicitGroups g WHERE g.id = :groupId)\n" // + " OR res.id IN (SELECT rr.id FROM Resource rr JOIN rr.parentResource.parentResource.parentResource.explicitGroups g WHERE g.id = :groupId)\n" // + " OR res.id IN (SELECT rr.id FROM Resource rr JOIN rr.parentResource.parentResource.parentResource.parentResource.explicitGroups g WHERE g.id = :groupId)) \n" // + " AND res.id IN (SELECT rr.id FROM Resource rr JOIN rr.implicitGroups g JOIN g.roles r JOIN r.subjects s WHERE s = :subject)"; // // Query authorizationQuery = entityManager.createQuery(authorizationQueryString); // authorizationQuery.setParameter("groupId", compatibleGroupId); // authorizationQuery.setParameter("inventoryStatus", InventoryStatus.COMMITTED); // authorizationQuery.setParameter("subject", user); // List<Integer> visibleResources = authorizationQuery.getResultList(); // // HashSet<Integer> visibleIdSet = new HashSet<Integer>(visibleResources); // // ListIterator<ResourceFlyweight> iter = resources.listIterator(); // while (iter.hasNext()) { // ResourceFlyweight res = iter.next(); // res.setLocked(!visibleIdSet.(res.getId())); // } // } return resources; } @SuppressWarnings("unchecked") private void getResourceStatuses(int parentResourceId, boolean descendents, Map<Integer, InventoryStatus> statuses) { Query query = entityManager.createNamedQuery(Resource.QUERY_GET_STATUSES_BY_PARENT); query.setParameter("parentResourceId", parentResourceId); for (Object[] is : (List<Object[]>) query.getResultList()) { statuses.put((Integer) is[0], (InventoryStatus) is[1]); if (descendents) { getResourceStatuses((Integer) is[0], true, statuses); } } } private void prefetchResource(Resource resource, boolean recursive) { if (resource == null) { return; // Nothing to do on invalid input } resource.getId(); resource.getPluginConfiguration().getNotes(); // Initialize the lazy plugin config // Init the lazy parent... // Don't fetch the parent's children, otherwise we'll end up in infinite recursion. prefetchResource(resource.getParentResource(), false); if (recursive) { // Recurse... for (Resource child : resource.getChildResources()) { prefetchResource(child, true); } } } // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // Remote Interface Impl // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @Override public Resource getResource(Subject subject, int resourceId) { return getResourceById(subject, resourceId); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public ResourceAvailability getLiveResourceAvailability(Subject subject, int resourceId) { Resource res = getResourceById(subject, resourceId); // platforms are never unknown, just up or down, so we need to default the availability to a different value // depending on the resource's category ResourceAvailability result = new ResourceAvailability(res, res.getResourceType().getCategory() == ResourceCategory.PLATFORM ? AvailabilityType.DOWN : AvailabilityType.UNKNOWN); try { // validate the resource and agent, protect against REST dummy agent Agent agent = agentManager.getAgentByResourceId(subjectManager.getOverlord(), resourceId); if (agent == null) { if (LOG.isDebugEnabled()) { LOG.debug("Resource [" + resourceId + "] does not exist or has no agent assigned"); } throw new IllegalStateException("No agent is associated with the resource with id [" + resourceId + "]"); } else if (agent.getName().startsWith(ResourceHandlerBean.DUMMY_AGENT_NAME_PREFIX) && agent.getAgentToken().startsWith(ResourceHandlerBean.DUMMY_AGENT_TOKEN_PREFIX)) { return getResourceById(subject, resourceId).getCurrentAvailability(); } AgentClient client = agentManager.getAgentClient(agent); if (client == null) { throw new IllegalStateException("No agent is associated with the resource with id [" + resourceId + "]"); } AvailabilityReport report = null; // first, quickly see if we can even ping the agent, if not, don't bother trying to get the resource avail boolean agentPing = client.pingService(5000L); if (agentPing) { // we can't serialize the resource due to the hibernate proxies (agent can't deserialize hibernate objs) // but we know we only need the basics for the agent to collect availability, so create a bare resource object Resource bareResource = new Resource(res.getResourceKey(), res.getName(), res.getResourceType()); bareResource.setId(res.getId()); bareResource.setUuid(res.getUuid()); // root the avail check at the desired resource. Ask for a full report to guarantee that we // get back the agent-side avail for the resource and keep the server in sync. This also means we'll // get the descendants as well. report = client.getDiscoveryAgentService().getCurrentAvailability(bareResource, false); } if (report != null) { // although the data came from the agent this should be processed like a server-side report // because it was requested and initiated by the server (bz 1094540). The availabilities will // still be merged but certain backfill logic will remain unscathed. report.setServerSideReport(true); AvailabilityType foundAvail = report.forResource(res.getId()); if (foundAvail != null) { availabilityManager.mergeAvailabilityReport(report); } else { foundAvail = res.getCurrentAvailability() == null ? AvailabilityType.UNKNOWN : res .getCurrentAvailability().getAvailabilityType(); } // make sure we don't somehow leak/persist a MISSING avail foundAvail = (AvailabilityType.MISSING == foundAvail) ? AvailabilityType.DOWN : foundAvail; result.setAvailabilityType(foundAvail); } } catch (Exception e) { if (LOG.isDebugEnabled()) { LOG.debug("Failed to get live availability: " + e.getMessage()); } } return result; } // lineage is a getXXX (not findXXX) because it logically returns a single object, but modeled as a list here @Override public List<Resource> findResourceLineage(Subject subject, int resourceId) { List<Resource> result = getResourceLineage(resourceId); for (Resource resource : result) { if (!authorizationManager.canViewResource(subject, resource.getId())) { throw new PermissionException("User [" + subject + "] does not have permission to view resource [" + resource.getId() + "]"); } } return result; } @Override @SuppressWarnings("unchecked") @RequiredPermission(Permission.MANAGE_INVENTORY) public List<ResourceInstallCount> findResourceInstallCounts(Subject subject, boolean groupByVersions) { String queryName = groupByVersions ? Resource.QUERY_RESOURCE_VERSION_REPORT : Resource.QUERY_RESOURCE_REPORT; Query query = entityManager.createNamedQuery(queryName); List<ResourceInstallCount> results = query.getResultList(); return results; } @Override @SuppressWarnings("unchecked") @RequiredPermission(Permission.MANAGE_INVENTORY) public List<ResourceInstallCount> findResourceComplianceCounts(Subject subject) { Query query; List<ResourceInstallCount> results; query = entityManager.createNamedQuery(Resource.QUERY_RESOURCE_VERSION_AND_DRIFT_IN_COMPLIANCE); results = query.getResultList(); query = entityManager.createNamedQuery(Resource.QUERY_RESOURCE_VERSION_AND_DRIFT_OUT_OF_COMPLIANCE); results.addAll(query.getResultList()); return results; } @Override public PageList<ResourceComposite> findResourceCompositesByCriteria(Subject subject, ResourceCriteria criteria) { boolean isInventoryManager = authorizationManager.isInventoryManager(subject); String compositeProjection; if (isInventoryManager) { compositeProjection = "" // + "new org.rhq.core.domain.resource.composite.ResourceComposite(" // + " %alias%, " // Resource + " %alias%.currentAvailability.availabilityType ) "; // AvailabilityType } else { compositeProjection = "" + " new org.rhq.core.domain.resource.composite.ResourceComposite(" // + " %alias%, " // Resource + " %alias%.currentAvailability.availabilityType, " // AvailabilityType + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 8 ), " // MANAGE_MEASUREMENTS + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 4 ), " // MODIFY_RESOURCE + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 10 ), " // CONTROL + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 7 ), " // MANAGE_ALERTS + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 14 ), " // MANAGE_EVENTS + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 13 ), " // CONFIGURE_READ + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 11 ), " // CONFIGURE_WRITE + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 9 ), " // MANAGE_CONTENT + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 6 ), " // CREATE_CHILD_RESOURCES + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 5 ), " // DELETE_RESOURCES + " ( SELECT count(p) FROM %alias%.implicitGroups g JOIN g.roles r JOIN r.subjects s JOIN r.permissions p WHERE s.id = %subjectId% AND p = 16 ))"; // MANAGE_DRIFT compositeProjection = compositeProjection.replace("%subjectId%", String.valueOf(subject.getId())); } compositeProjection = compositeProjection.replace("%alias%", criteria.getAlias()); CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); generator.alterProjection(compositeProjection); if (!isInventoryManager) { if (criteria.isInventoryManagerRequired()) { throw new PermissionException("Subject [" + subject.getName() + "] requires InventoryManager permission for requested query criteria."); } generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE, null, subject.getId()); } CriteriaQueryRunner<ResourceComposite> queryRunner = new CriteriaQueryRunner<ResourceComposite>(criteria, generator, entityManager, false); // don't auto-init bags, we're returning composites not entities PageList<ResourceComposite> results = queryRunner.execute(); for (ResourceComposite nextComposite : results) { Resource nextResource = nextComposite.getResource(); ResourceType nextResourceType = nextResource.getResourceType(); ResourceFacets facets = typeManager.getResourceFacets(nextResourceType.getId()); queryRunner.initFetchFields(nextResource); // manual field fetch for composite-wrapped entity nextComposite.setResourceFacets(facets); } return results; } @Override public PageList<Resource> findResourcesByCriteria(Subject subject, ResourceCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); if (!authorizationManager.isInventoryManager(subject)) { if (criteria.isInventoryManagerRequired()) { throw new PermissionException("Subject [" + subject.getName() + "] requires InventoryManager permission for requested query criteria."); } generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE, null, subject.getId()); } CriteriaQueryRunner<Resource> queryRunner = new CriteriaQueryRunner<Resource>(criteria, generator, entityManager); PageList<Resource> results = queryRunner.execute(); return results; } @Override public List<Resource> findResourcesByCriteriaBounded(Subject subject, ResourceCriteria criteria, int maxResources, int maxResourcesByType) { // find all of the requested resources but don't return them until they meet our bounded return requirements // maintain any requested sorting criteria.clearPaging(); // perform the requested criteria query PageList<Resource> results = findResourcesByCriteria(subject, criteria); // If not specified use the default maxResources if (maxResources <= 0) { try { maxResources = Integer.parseInt(System.getProperty( "rhq.server.findResourcesByCriteriaBounded.maxResources", BOUNDED_MAX_RESOURCES)); } catch (NumberFormatException ignored) { } if (maxResources <= 0) { maxResources = Integer.parseInt(BOUNDED_MAX_RESOURCES); } } if (results.getTotalSize() <= maxResources) { return results; } // If not specified use the default maxResourcesByType if (maxResourcesByType <= 0) { try { maxResourcesByType = Integer.parseInt(System.getProperty( "rhq.server.findResourcesByCriteriaBounded.maxResourcesByType", BOUNDED_MAX_RESOURCES_BY_TYPE)); } catch (NumberFormatException ignored) { } if (maxResourcesByType <= 0) { maxResourcesByType = Integer.parseInt(BOUNDED_MAX_RESOURCES_BY_TYPE); } } // We need to trim the returned resources, enforce maxResourcesByType Map<Integer, Integer> typeCounts = new HashMap<Integer, Integer>(); for (Iterator<Resource> i = results.iterator(); i.hasNext();) { Resource r = i.next(); Integer typeId = r.getResourceType().getId(); Integer count = typeCounts.get(typeId); if (null == count) { count = 0; } typeCounts.put(typeId, ++count); if (count > maxResourcesByType) { i.remove(); } } // If after we've trimmed all types the results are still more than maxSize then we need to just chop // keeping the most important (presumably the beginning of the list, if it's sorted) while (maxResources < results.size()) { results.remove(maxResources); } return results; } @Override public Resource getPlaformOfResource(Subject subject, int resourceId) { Resource resource; Resource parent = null; do { resource = parent; if (resource != null) { resourceId = parent.getId(); } parent = getParentResource(resourceId); if (parent != null && parent.getResourceType().getCategory().equals(ResourceCategory.PLATFORM)) { resource = parent; parent = null; } } while (parent != null); if (resource != null) { if (!authorizationManager.canViewResource(subject, resource.getId())) { throw new PermissionException("User [" + subject + "] does not have permission to view resource [" + resource.getId() + "]"); } } return resource; } @Override public Resource getParentResource(Subject subject, int resourceId) { Resource resource = getParentResource(resourceId); if (!authorizationManager.canViewResource(subject, resource.getId())) { throw new PermissionException("User [" + subject + "] does not have permission to view resource [" + resource.getId() + "]"); } return resource; } @Override public PageList<Resource> findChildResources(Subject subject, int parentResourceId, PageControl pageControl) { Resource parentResource = getResourceById(subject, parentResourceId); return (findChildResources(subject, parentResource, pageControl)); } @Override public <T> List<DisambiguationReport<T>> disambiguate(List<T> results, IntExtractor<? super T> extractor, DisambiguationUpdateStrategy updateStrategy) { return Disambiguator.disambiguate(results, updateStrategy, extractor, entityManager, typeManager.getDuplicateTypeNames()); } @Override public void updateAncestry(Subject subject, int resourceId) { Resource resource = entityManager.find(Resource.class, resourceId); if (null == resource) { throw new ResourceNotFoundException(resourceId); } updateAncestry(resource); } private void updateAncestry(Resource resource) { resource.updateAncestryForResource(); for (Resource child : resource.getChildResources()) { child.setParentResource(resource); updateAncestry(child); } } @Override @SuppressWarnings("unchecked") public List<Integer> findIdsByTypeIds(List<Integer> resourceTypeIds) { return entityManager.createNamedQuery(Resource.QUERY_FIND_IDS_BY_TYPE_IDS) .setParameter("resourceTypeIds", resourceTypeIds).getResultList(); } @Override public Integer getResourceCount(List<Integer> resourceTypeIds) { return (Integer) entityManager.createNamedQuery(Resource.QUERY_FIND_COUNT_BY_TYPES) .setParameter("resourceTypeIds", resourceTypeIds).getSingleResult(); } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public List<Integer> disableResources(Subject subject, int[] resourceIds) { List<Integer> disableResourceIds = new ArrayList<Integer>(); // one report for each agent, keyed by agent name Map<Agent, AvailabilityReport> reports = resourceManager.getDisableResourcesReportInNewTransaction(subject, resourceIds, disableResourceIds); // Set the resources disabled via the standard mergeInventoryReport mechanism, from the server service // level. We do this for a few reasons: // - The server service uses locking to ensure we don't conflict with an actual report from the agent // - It ensure all necessary db modifications take place, like avail history and current avail // - It ensures that all ancillary avail change logic, like alerting, still happens. DiscoveryServerServiceImpl service = new DiscoveryServerServiceImpl(); for (AvailabilityReport report : reports.values()) { service.mergeAvailabilityReport(report); } return disableResourceIds; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public Map<Agent, AvailabilityReport> getDisableResourcesReportInNewTransaction(Subject subject, int[] resourceIds, List<Integer> disableResourceIds) { // one report for each agent Map<Agent, AvailabilityReport> reports = new HashMap<Agent, AvailabilityReport>(); long now = System.currentTimeMillis(); boolean isInventoryManager = authorizationManager.isInventoryManager(subject); for (Integer resourceId : resourceIds) { if (disableResourceIds.contains(resourceId)) { continue; } // make sure the user is authorized to disable this resource (which implies you can disable all its children) // TODO: this may require its own permission, but until someone needs it we'll piggyback on DELETE, at least // that gives a resource-level permission option. if (!isInventoryManager && !authorizationManager.hasResourcePermission(subject, Permission.DELETE_RESOURCE, resourceId)) { throw new PermissionException("You do not have permission to disable resource [" + resourceId + "]"); } Resource resource = entityManager.find(Resource.class, resourceId); if (null == resource) { LOG.info("Disable resource not possible, resource with id [" + resourceId + "] was not found"); continue; } // you can't disable a platform if (null == resource.getParentResource()) { LOG.info("Disabling a platform is not allowed, skipping platform resource with id [" + resourceId + "]"); continue; } // disable the resource and all its children, get the family resource ids if (LOG.isDebugEnabled()) { LOG.debug("Subject [" + subject + "] is setting resource [" + resource + "] and its children DISABLED."); } List<Integer> familyResourceIds = getFamily(resource); disableResourceIds.addAll(familyResourceIds); // add the family resource id's to the appropriate avail report Agent agent = resource.getAgent(); AvailabilityReport report = reports.get(agent); if (null == report) { report = new AvailabilityReport(agent.getName()); report.setEnablementReport(true); reports.put(agent, report); } for (Integer familyResourceId : familyResourceIds) { report.addAvailability(new AvailabilityReport.Datum(familyResourceId, AvailabilityType.DISABLED, now)); } } return reports; } private List<Integer> getFamily(Resource resource) { List<Integer> result = getDescendents(resource.getId()); return result; } @Override @TransactionAttribute(TransactionAttributeType.NEVER) public List<Integer> enableResources(Subject subject, int[] resourceIds) { List<Integer> enableResourceIds = new ArrayList<Integer>(); // one report for each agent, keyed by agent name Map<Agent, AvailabilityReport> reports = resourceManager.getEnableResourcesReportInNewTransaction(subject, resourceIds, enableResourceIds); // Set the resources disabled via the standard mergeInventoryReport mechanism, from the server service // level. We do this for a few reasons: // - The server service uses locking to ensure we don't conflict with an actual report from the agent // - It ensure all necessary db modifications take place, like avail history and current avail // - It ensures that all ancillary avail change logic, like alerting, still happens. DiscoveryServerServiceImpl service = new DiscoveryServerServiceImpl(); for (AvailabilityReport report : reports.values()) { service.mergeAvailabilityReport(report); } // Ask the relevant agents to send a full availability check so that the newly enabled resources get // current availability as soon as possible. Do this async (via Quartz) so we don't hang waiting for // Agent connections. scheduleAgentRequestFullAvailabilityJob(reports.keySet()); return enableResourceIds; } private void scheduleAgentRequestFullAvailabilityJob(Collection<Agent> agents) { Scheduler scheduler = LookupUtil.getSchedulerBean(); try { final String DEFAULT_JOB_NAME = "AgentRequestFullAvailabilityJob"; final String DEFAULT_JOB_GROUP = "AgentRequestFullAvailabilityGroup"; final String TRIGGER_PREFIX = "AgentRequestFullAvailabilityTrigger"; final String randomSuffix = UUID.randomUUID().toString(); final String triggerName = TRIGGER_PREFIX + " - " + randomSuffix; SimpleTrigger trigger = new SimpleTrigger(triggerName, DEFAULT_JOB_GROUP, new Date()); JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put(AgentRequestFullAvailabilityJob.KEY_TRIGGER_NAME, triggerName); jobDataMap.put(AgentRequestFullAvailabilityJob.KEY_TRIGGER_GROUP_NAME, DEFAULT_JOB_GROUP); AgentRequestFullAvailabilityJob.externalizeJobValues(jobDataMap, AgentRequestFullAvailabilityJob.AGENTS, agents); trigger.setJobName(DEFAULT_JOB_NAME); trigger.setJobGroup(DEFAULT_JOB_GROUP); trigger.setJobDataMap(jobDataMap); if (isJobScheduled(scheduler, DEFAULT_JOB_NAME, DEFAULT_JOB_GROUP)) { scheduler.scheduleJob(trigger); } else { JobDetail jobDetail = new JobDetail(DEFAULT_JOB_NAME, DEFAULT_JOB_GROUP, AgentRequestFullAvailabilityJob.class); scheduler.scheduleJob(jobDetail, trigger); } } catch (SchedulerException e) { LOG.error("Failed to schedule AgentRequestFullAvailabilityJob.", e); } } private boolean isJobScheduled(Scheduler scheduler, String name, String group) { boolean isScheduled = false; try { JobDetail jobDetail = scheduler.getJobDetail(name, group); if (jobDetail != null) { isScheduled = true; } } catch (SchedulerException se) { LOG.error("Error getting job detail", se); } return isScheduled; } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public Map<Agent, AvailabilityReport> getEnableResourcesReportInNewTransaction(Subject subject, int[] resourceIds, List<Integer> enableResourceIds) { // one report for each agent, keyed by agent name Map<Agent, AvailabilityReport> reports = new HashMap<Agent, AvailabilityReport>(); long now = System.currentTimeMillis(); boolean isInventoryManager = authorizationManager.isInventoryManager(subject); for (Integer resourceId : resourceIds) { if (enableResourceIds.contains(resourceId)) { continue; } // make sure the user is authorized to enable this resource (which implies you can enable all its children) // TODO: this may require its own permission, but until someone needs it we'll piggyback on DELETE, at least // that gives a resource-level permission option. if (!isInventoryManager && !authorizationManager.hasResourcePermission(subject, Permission.DELETE_RESOURCE, resourceId)) { throw new PermissionException("You do not have permission to enable resource [" + resourceId + "]"); } Resource resource = entityManager.find(Resource.class, resourceId); if (null == resource) { LOG.info("Enable resource not possible, resource with id [" + resourceId + "] was not found"); continue; } // you can't enable a platform if (null == resource.getParentResource()) { LOG.info("Enabling a platform is not allowed, skipping platform resource with id [" + resourceId + "]"); continue; } // enable the resource and all its children, get the hierarchy if (LOG.isDebugEnabled()) { LOG.debug("Subject [" + subject + "] is setting resource [" + resource + "] and its children enabled."); } List<Integer> familyResourceIds = getFamily(resource); enableResourceIds.addAll(familyResourceIds); // add the family resource id's to the appropriate avail report Agent agent = resource.getAgent(); AvailabilityReport report = reports.get(agent); if (null == report) { report = new AvailabilityReport(agent.getName()); report.setEnablementReport(true); reports.put(agent, report); } for (Integer familyResourceId : familyResourceIds) { report.addAvailability(new AvailabilityReport.Datum(familyResourceId, AvailabilityType.UNKNOWN, now)); } } return reports; } @Override public PageList<Resource> findGroupMemberCandidateResources(Subject subject, ResourceCriteria criteria, int[] alreadySelectedResourceIds) { PageControl originalPageControl = getPageControl(criteria); if (originalPageControl.isUnlimited()) { throw new UnsupportedOperationException("Supplied criteria has an unlimited PageControl"); } Set<Integer> alreadySelectedResourceIdSet = new HashSet<Integer>( ArrayUtils.wrapInList(alreadySelectedResourceIds == null ? new int[0] : alreadySelectedResourceIds)); PageControl pageControl = (PageControl) originalPageControl.clone(); criteria.setPageControl(pageControl); int requiredPageSize = pageControl.getPageSize(); criteria.setRestriction(COUNT_ONLY); int totalSize = findResourcesByCriteria(subject, criteria).getTotalSize(); int totalPages = (totalSize / requiredPageSize) + (((totalSize % requiredPageSize) > 0) ? 1 : 0); criteria.setRestriction(COLLECTION_ONLY); List<Resource> candidates = new LinkedList<Resource>(); for (int pageNumber = 0; candidates.size() < requiredPageSize && pageNumber < totalPages; pageNumber++) { pageControl.setPageNumber(pageNumber); PageList<Resource> foundResources = findResourcesByCriteria(subject, criteria); Collection<Resource> filteredResources = filterOutAlreadySelectedResources(foundResources, alreadySelectedResourceIdSet); candidates.addAll(filteredResources); } if (candidates.size() > requiredPageSize) { candidates = candidates.subList(0, requiredPageSize); } return new PageList<Resource>(candidates, totalSize, originalPageControl); } private Collection<Resource> filterOutAlreadySelectedResources(Collection<Resource> foundResources, Collection<Integer> alreadySelectedResourceIds) { List<Resource> result = new LinkedList<Resource>(); for (Resource foundResource : foundResources) { if (!alreadySelectedResourceIds.contains(foundResource.getId())) { result.add(foundResource); } } return result; } }