/* * RHQ Management Platform * Copyright (C) 2005-2010 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.server.measurement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.interceptor.Interceptors; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleTrigger; import org.rhq.core.db.DatabaseType; import org.rhq.core.db.DatabaseTypeFactory; import org.rhq.core.db.H2DatabaseType; import org.rhq.core.db.OracleDatabaseType; import org.rhq.core.db.PostgresqlDatabaseType; import org.rhq.core.db.SQLServerDatabaseType; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.core.domain.common.EntityContext; import org.rhq.core.domain.criteria.MeasurementDefinitionCriteria; import org.rhq.core.domain.criteria.MeasurementScheduleCriteria; import org.rhq.core.domain.criteria.ResourceCriteria; import org.rhq.core.domain.measurement.DataType; import org.rhq.core.domain.measurement.DisplayType; import org.rhq.core.domain.measurement.MeasurementDefinition; import org.rhq.core.domain.measurement.MeasurementSchedule; import org.rhq.core.domain.measurement.MeasurementScheduleRequest; import org.rhq.core.domain.measurement.NumericType; import org.rhq.core.domain.measurement.ResourceMeasurementScheduleRequest; import org.rhq.core.domain.measurement.composite.MeasurementScheduleComposite; import org.rhq.core.domain.resource.Agent; import org.rhq.core.domain.resource.InventoryStatus; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.server.PersistenceUtility; import org.rhq.core.domain.util.OrderingField; 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.collection.ArrayUtils; import org.rhq.core.util.jdbc.JDBCUtil; 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.authz.RequiredPermissions; import org.rhq.enterprise.server.common.PerformanceMonitorInterceptor; import org.rhq.enterprise.server.core.AgentManagerLocal; import org.rhq.enterprise.server.resource.ResourceManagerLocal; import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal; import org.rhq.enterprise.server.util.CriteriaQueryGenerator; import org.rhq.enterprise.server.util.CriteriaQueryRunner; import org.rhq.enterprise.server.util.LookupUtil; /** * A manager for {@link MeasurementSchedule}s. * * @author Heiko W. Rupp * @author Ian Springer * @author Joseph Marques */ @Stateless @Interceptors(PerformanceMonitorInterceptor.class) public class MeasurementScheduleManagerBean implements MeasurementScheduleManagerLocal, MeasurementScheduleManagerRemote { @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @javax.annotation.Resource(name = "RHQ_DS", mappedName = RHQConstants.DATASOURCE_JNDI_NAME) private DataSource dataSource; @EJB private AgentManagerLocal agentManager; @EJB private AuthorizationManagerLocal authorizationManager; @EJB private ResourceManagerLocal resourceManager; @EJB private ResourceGroupManagerLocal resourceGroupManager; @EJB private MeasurementScheduleManagerLocal measurementScheduleManager; @EJB private SubjectManagerLocal subjectManager; private final Log log = LogFactory.getLog(MeasurementScheduleManagerBean.class); public Set<ResourceMeasurementScheduleRequest> findSchedulesForResourceAndItsDescendants(int[] resourceIds, boolean getDescendents) { Set<ResourceMeasurementScheduleRequest> allSchedules = new HashSet<ResourceMeasurementScheduleRequest>(); getSchedulesForResourceAndItsDescendants(resourceIds, allSchedules, getDescendents); return allSchedules; } /** * Get the AgentClient (the connection to the agent) for a certain Schedule * * @param sched A MeasurementSchedule for which we need a connection to the Agent * * @return an AgentClient to communicate with the Agent */ public AgentClient getAgentClientForSchedule(MeasurementSchedule sched) { Resource pRes = sched.getResource(); // Get agent and open a connection to it Agent agent = pRes.getAgent(); AgentClient ac = agentManager.getAgentClient(agent); return ac; } /** * Returns a MeasurementSchedule by its primary key or null. * * @param scheduleId the id of the desired schedule * * @return The MeasurementSchedule or null if not found */ public MeasurementSchedule getScheduleById(int scheduleId) { MeasurementSchedule ms; try { ms = entityManager.find(MeasurementSchedule.class, scheduleId); } catch (NoResultException n) { ms = null; } return ms; } /** * Return a list of MeasurementSchedules for the given ids * * @param scheduleIds PrimaryKeys of the schedules searched * * @return a list of Schedules */ @SuppressWarnings("unchecked") public List<MeasurementSchedule> findSchedulesByIds(int[] scheduleIds) { if (scheduleIds == null || scheduleIds.length == 0) { return new ArrayList<MeasurementSchedule>(); } Query q = entityManager.createNamedQuery(MeasurementSchedule.FIND_BY_IDS); q.setParameter("ids", ArrayUtils.wrapInList(scheduleIds)); List<MeasurementSchedule> ret = q.getResultList(); return ret; } /** * Obtain a MeasurementSchedule by its Id after a check for a valid session * * @param subject a session id that must be valid * @param scheduleId The primary key of the Schedule * * @return a MeasurementSchedule or null, if there is */ public MeasurementSchedule getScheduleById(Subject subject, int scheduleId) { MeasurementSchedule schedule = entityManager.find(MeasurementSchedule.class, scheduleId); if (schedule == null) { return null; } // the auth check eagerly loads the resource if (authorizationManager.canViewResource(subject, schedule.getResource().getId()) == false) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view measurementSchedule[id=" + scheduleId + "]"); } // and this eagerly loads the definition schedule.getDefinition().getId(); return schedule; } /** * <p>Ensures the collection interval is valid by increasing it to the minimum if necessary.</p> * <p>Be careful not to call this for template enable/disable, because that uses special values for the * interval.</p> * @param schedule */ private void verifyMinimumCollectionInterval(MeasurementSchedule schedule) { schedule.setInterval(verifyMinimumCollectionInterval(schedule.getInterval())); } /** * <p>Ensures the collection interval is valid by increasing it to the minimum if necessary.</p> * <p>Be careful not to call this for template enable/disable, because that uses special values for the * interval.</p> * * @param collectionInterval * @return valid interval */ private long verifyMinimumCollectionInterval(long collectionInterval) { long validCollectionInterval = collectionInterval; if (collectionInterval < MeasurementConstants.MINIMUM_COLLECTION_INTERVAL_MILLIS) { validCollectionInterval = MeasurementConstants.MINIMUM_COLLECTION_INTERVAL_MILLIS; } return validCollectionInterval; } /** * Find MeasurementSchedules that are attached to a certain definition and some resources * * @param subject A subject that must be valid * @param definitionId The primary key of a MeasurementDefinition * @param resourceIds primary of Resources wanted * * @return a List of MeasurementSchedules */ public List<MeasurementSchedule> findSchedulesByResourceIdsAndDefinitionId(Subject subject, int[] resourceIds, int definitionId) { return findSchedulesByResourcesAndDefinitions(subject, resourceIds, new int[] { definitionId }); } @SuppressWarnings("unchecked") public List<MeasurementSchedule> findSchedulesByResourceIdsAndDefinitionIds(int[] resourceIds, int[] definitionIds) { Query query = entityManager.createNamedQuery(MeasurementSchedule.FIND_BY_RESOURCE_IDS_AND_DEFINITION_IDS); query.setParameter("definitionIds", ArrayUtils.wrapInList(definitionIds)); query.setParameter("resourceIds", ArrayUtils.wrapInList(resourceIds)); List<MeasurementSchedule> results = query.getResultList(); return results; } private List<MeasurementSchedule> findSchedulesByResourcesAndDefinitions(Subject subject, int[] resourceIds, int[] definitionIds) { for (int resourceId : resourceIds) { if (!authorizationManager.canViewResource(subject, resourceId)) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view metric schedules for resource[id=" + resourceId + "]"); } } return findSchedulesByResourceIdsAndDefinitionIds(resourceIds, definitionIds); } /** * Find MeasurementSchedules that are attached to a certain definition and a resource * * @param subject * @param definitionId The primary key of a MeasurementDefinition * @param resourceId the id of the resource * @param attachBaseline baseline won't be attached to the schedule by default do to LAZY annotation on the managed * relationship. attachBaseline, if true, will eagerly load it for the caller * * @return the MeasurementSchedule of the given definition for the given resource */ public MeasurementSchedule getSchedule(Subject subject, int resourceId, int definitionId, boolean attachBaseline) throws MeasurementNotFoundException { try { List<MeasurementSchedule> results = findSchedulesByResourcesAndDefinitions(subject, new int[] { resourceId }, new int[] { definitionId }); if (results.size() != 1) { throw new MeasurementException("Could not find measurementSchedule[resourceId=" + resourceId + ", definitionId=" + definitionId + "]"); } MeasurementSchedule schedule = results.get(0); if (attachBaseline && (schedule.getBaseline() != null)) { schedule.getBaseline().getId(); // eagerly load the baseline } return schedule; } catch (NoResultException nre) { throw new MeasurementNotFoundException(nre); } } @Deprecated @RequiredPermission(Permission.MANAGE_SETTINGS) public void disableDefaultCollectionForMeasurementDefinitions(Subject subject, int[] measurementDefinitionIds, boolean updateSchedules) { modifyDefaultCollectionIntervalForMeasurementDefinitions(subject, measurementDefinitionIds, -1, updateSchedules); return; } @Deprecated @RequiredPermissions({ @RequiredPermission(Permission.MANAGE_INVENTORY), @RequiredPermission(Permission.MANAGE_SETTINGS) }) public void disableAllDefaultCollections(Subject subject) { entityManager.createNamedQuery(MeasurementDefinition.DISABLE_ALL).executeUpdate(); } @Deprecated @RequiredPermissions({ @RequiredPermission(Permission.MANAGE_INVENTORY), @RequiredPermission(Permission.MANAGE_SETTINGS) }) public void disableAllSchedules(Subject subject) { entityManager.createNamedQuery(MeasurementSchedule.DISABLE_ALL).executeUpdate(); // TODO: how do we ensure the agents sync their schedules now so they turn off everything? } public void createSchedulesForExistingResources(ResourceType type, MeasurementDefinition newDefinition) { List<Resource> resources = type.getResources(); if (resources != null) { for (Resource res : resources) { res.setAgentSynchronizationNeeded(); MeasurementSchedule sched = new MeasurementSchedule(newDefinition, res); sched.setInterval(newDefinition.getDefaultInterval()); entityManager.persist(sched); } } return; } @RequiredPermission(Permission.MANAGE_SETTINGS) @Deprecated public void updateDefaultCollectionIntervalForMeasurementDefinitions(Subject subject, int[] measurementDefinitionIds, long collectionInterval, boolean updateExistingSchedules) { collectionInterval = verifyMinimumCollectionInterval(collectionInterval); modifyDefaultCollectionIntervalForMeasurementDefinitions(subject, measurementDefinitionIds, collectionInterval, updateExistingSchedules); } @RequiredPermission(Permission.MANAGE_SETTINGS) public void updateDefaultCollectionIntervalAndEnablementForMeasurementDefinitions(Subject subject, int[] measurementDefinitionIds, long collectionInterval, boolean enable, boolean updateExistingSchedules) { modifyDefaultCollectionIntervalForMeasurementDefinitions(subject, measurementDefinitionIds, enable, collectionInterval, updateExistingSchedules); } /** * Updates the default enablement and/or collection intervals (i.e. metric templates) for the given measurement * definitions. If updateExistingSchedules is true, the schedules for the corresponding metrics or all inventoried * Resources are also updated. Otherwise, the updated templates will only affect Resources that added to * inventory in the future. * @param subject * * @param measurementDefinitionIds the IDs of the metric defs whose default schedules should be updated * @param collectionInterval if > 0, enable the metric with this value as the the new collection * interval, in milliseconds; if == 0, enable the metric with its current * collection interval; if < 0, disable the metric; if >0, it is assumed that * the caller has verified the value is >=30000, since 30s is the minimum * interval allowed * @param updateExistingSchedules if true, existing Resource schedules for metrics of this type should also be updated */ private void modifyDefaultCollectionIntervalForMeasurementDefinitions(Subject subject, int[] measurementDefinitionIds, long collectionInterval, boolean updateExistingSchedules) { if (measurementDefinitionIds == null || measurementDefinitionIds.length == 0) { log.debug("update metric template: no definitions supplied (interval = " + collectionInterval); return; } boolean enable = (collectionInterval >= 0); // batch the modifications to prevent the ORA error about IN clauses containing more than 1000 items for (int batchIndex = 0; (batchIndex < measurementDefinitionIds.length); batchIndex += 1000) { int[] batchIdArray = ArrayUtils.copyOfRange(measurementDefinitionIds, batchIndex, batchIndex + 1000); modifyDefaultCollectionIntervalForMeasurementDefinitions(subject, batchIdArray, enable, collectionInterval, updateExistingSchedules); } } /** * Updates the default enablement and/or collection intervals (i.e. metric templates) for the given measurement * definitions. If updateExistingSchedules is true, the schedules for the corresponding metrics or all inventoried * Resources are also updated. Otherwise, the updated templates will only affect Resources that are added to * inventory in the future. * * <strong>Only the 3-param modifyDefaultCollectionIntervalForMeasurementDefinitions method should call this method, * since it will batch the metric defs specified by the user to ensure no more than 1000 metric defs are passed to * this method.</strong> * @param subject * * @param measurementDefinitionIds the IDs of the metric defs whose default schedules should be updated; the size of * this array must be <= 1000 * @param enable if true, enable the default schedule, otherwise, disable it * @param collectionInterval if > 0, enable the metric with this value as the the new collection * interval, in milliseconds; if == 0, enable the metric with its current * collection interval; if < 0, disable the metric; if >0, it is assumed that * the caller has verified the value is >=30000, since 30s is the minimum * interval allowed * @param updateExistingSchedules if true, existing Resource schedules for metrics of this type should also be updated */ @SuppressWarnings("unchecked") private void modifyDefaultCollectionIntervalForMeasurementDefinitions(Subject subject, int[] measurementDefinitionIds, boolean enable, long collectionInterval, boolean updateExistingSchedules) { // this method has been rewritten to ensure that the Hibernate cache is not utilized in an // extensive way, regardless of the number of measurementDefinitionIds being processed. Future // enhancements must keep this in mind and avoid using attached objects. // update all of the measurement definitions via native query to avoid Hibernate caching Connection conn = null; PreparedStatement defUpdateStmt = null; PreparedStatement schedUpdateStmt = null; String queryString; int i; try { conn = dataSource.getConnection(); // update the defaults on the measurement definitions if (collectionInterval > 0L) { // This query enables the default schedule and updates its collection interval. queryString = MeasurementDefinition.QUERY_NATIVE_UPDATE_DEFAULTS_BY_IDS; } else { // <=0 : This query enables (=0) or disables (<0) the default schedule but does not update the interval. queryString = MeasurementDefinition.QUERY_NATIVE_UPDATE_DEFAULT_ON_BY_IDS; } String transformedQuery = JDBCUtil.transformQueryForMultipleInParameters(queryString, "@@DEFINITION_IDS@@", measurementDefinitionIds.length); defUpdateStmt = conn.prepareStatement(transformedQuery); i = 1; defUpdateStmt.setBoolean(i++, enable); if (collectionInterval > 0L) { defUpdateStmt.setLong(i++, collectionInterval); } JDBCUtil.bindNTimes(defUpdateStmt, measurementDefinitionIds, i); defUpdateStmt.executeUpdate(); if (updateExistingSchedules) { Map<Integer, ResourceMeasurementScheduleRequest> reqMap = new HashMap<Integer, ResourceMeasurementScheduleRequest>(); List<Integer> idsAsList = ArrayUtils.wrapInList(measurementDefinitionIds); // update the schedules associated with the measurement definitions (i.e. the current inventory) if (collectionInterval > 0L) { // This query enables the schedules and updates their collection intervals. queryString = MeasurementDefinition.QUERY_NATIVE_UPDATE_SCHEDULES_BY_IDS; } else { // <=0 : This query enables (=0) or disables (<0) the schedules but does not update their intervals. queryString = MeasurementDefinition.QUERY_NATIVE_UPDATE_SCHEDULES_ENABLE_BY_IDS; } transformedQuery = JDBCUtil.transformQueryForMultipleInParameters(queryString, "@@DEFINITION_IDS@@", measurementDefinitionIds.length); schedUpdateStmt = conn.prepareStatement(transformedQuery); i = 1; schedUpdateStmt.setBoolean(i++, enable); if (collectionInterval > 0L) { schedUpdateStmt.setLong(i++, collectionInterval); } JDBCUtil.bindNTimes(schedUpdateStmt, measurementDefinitionIds, i++); schedUpdateStmt.executeUpdate(); // Notify the agents of the updated schedules for affected resources // we need specific information to construct the agent updates. This query is specific to // this use case and therefore is defined here and not in a domain module. Note that this // query must not return domain entities as they would be placed in the Hibernate cache. // Return only the data necessary to construct minimal objects ourselves. Using JPQL // is ok, it just lets Hibernate do the heavy lifting for query generation. queryString = "" // + "SELECT ms.id, ms.interval, ms.resource.id, ms.definition.name, ms.definition.dataType, ms.definition.rawNumericType" // + " FROM MeasurementSchedule ms" // + " WHERE ms.definition.id IN ( :definitionIds )"; Query query = entityManager.createQuery(queryString); query.setParameter("definitionIds", idsAsList); List<Object[]> rs = query.getResultList(); for (Object[] row : rs) { i = 0; int schedId = (Integer) row[i++]; long existingInterval = (Long) row[i++]; int resourceId = (Integer) row[i++]; String name = (String) row[i++]; DataType dataType = (DataType) row[i++]; NumericType numericType = (NumericType) row[i++]; ResourceMeasurementScheduleRequest req = reqMap.get(resourceId); if (null == req) { req = new ResourceMeasurementScheduleRequest(resourceId); reqMap.put(resourceId, req); } MeasurementScheduleRequest msr = new MeasurementScheduleRequest(schedId, name, ((collectionInterval > 0) ? collectionInterval : existingInterval), enable, dataType, numericType); req.addMeasurementScheduleRequest(msr); } Map<Agent, Set<ResourceMeasurementScheduleRequest>> agentUpdates = null; agentUpdates = new HashMap<Agent, Set<ResourceMeasurementScheduleRequest>>(); // The number of Agents is manageable, so we can work with entities here for (Integer resourceId : reqMap.keySet()) { Agent agent = agentManager.getAgentByResourceId(subjectManager.getOverlord(), resourceId); // Ignore resources that are not actually associated with an agent. For example, // those with an UNINVENTORIED status. if (null == agent) { if (log.isDebugEnabled()) { log.debug("Ignoring measurement schedule change for non-agent-related resource [" + resourceId + "]. It is probably waiting to be uninventoried."); } continue; } Set<ResourceMeasurementScheduleRequest> agentUpdate = agentUpdates.get(agent); if (agentUpdate == null) { agentUpdate = new HashSet<ResourceMeasurementScheduleRequest>(); agentUpdates.put(agent, agentUpdate); } agentUpdate.add(reqMap.get(resourceId)); } // convert the int[] to Integer[], in case we need to set // send schedule updates to agents for (Map.Entry<Agent, Set<ResourceMeasurementScheduleRequest>> agentEntry : agentUpdates.entrySet()) { boolean synced = sendUpdatedSchedulesToAgent(agentEntry.getKey(), agentEntry.getValue()); if (!synced) { /* * only sync resources that are affected by this set of definitions that were updated, and only * for the agent that couldn't be contacted (under the assumption that 9 times out of 10 the agent * will be up; so, we don't want to unnecessarily mark more resources as needing syncing that don't */ int agentId = agentEntry.getKey().getId(); setAgentSynchronizationNeededByDefinitionsForAgent(agentId, idsAsList); } } } } catch (Exception e) { String errorMessage = "Error updating measurement definitions"; SQLException sqle = null; if (e instanceof SQLException) { sqle = (SQLException) e; } else if (e.getCause() instanceof SQLException) { sqle = (SQLException) e.getCause(); } if (sqle != null) { String s = JDBCUtil.convertSQLExceptionToString((SQLException) e); errorMessage += ": " + s; } log.error(errorMessage, e); throw new MeasurementException("Error updating measurement definitions: " + e); } finally { JDBCUtil.safeClose(defUpdateStmt); JDBCUtil.safeClose(schedUpdateStmt); JDBCUtil.safeClose(conn); } } public int updateSchedulesForContext(Subject subject, EntityContext context, int[] measurementDefinitionIds, long collectionInterval) { collectionInterval = verifyMinimumCollectionInterval(collectionInterval); String measurementScheduleSubQuery = getMeasurementScheduleSubQueryForContext(subject, context, measurementDefinitionIds); String updateQuery = "" // + "UPDATE MeasurementSchedule " // + " SET interval = :interval, " // + " enabled = true " // + " WHERE id IN ( " + measurementScheduleSubQuery + " ) "; Query query = entityManager.createQuery(updateQuery); query.setParameter("interval", collectionInterval); int affectedRows = query.executeUpdate(); scheduleJobToPushScheduleUpdatesToAgents(context, measurementScheduleSubQuery); return affectedRows; } public int enableSchedulesForContext(Subject subject, EntityContext context, int[] measurementDefinitionIds) { String measurementScheduleSubQuery = getMeasurementScheduleSubQueryForContext(subject, context, measurementDefinitionIds); String updateQuery = "" // + "UPDATE MeasurementSchedule " // + " SET enabled = true " // + " WHERE id IN ( " + measurementScheduleSubQuery + " ) "; Query query = entityManager.createQuery(updateQuery); int affectedRows = query.executeUpdate(); scheduleJobToPushScheduleUpdatesToAgents(context, measurementScheduleSubQuery); return affectedRows; } public int disableSchedulesForContext(Subject subject, EntityContext context, int[] measurementDefinitionIds) { String measurementScheduleSubQuery = getMeasurementScheduleSubQueryForContext(subject, context, measurementDefinitionIds); String updateQuery = "" // + "UPDATE MeasurementSchedule " // + " SET enabled = false " // + " WHERE id IN ( " + measurementScheduleSubQuery + " ) "; Query query = entityManager.createQuery(updateQuery); int affectedRows = query.executeUpdate(); scheduleJobToPushScheduleUpdatesToAgents(context, measurementScheduleSubQuery); return affectedRows; } public static final String TRIGGER_NAME = "TriggerName"; public static final String TRIGGER_GROUP_NAME = "TriggerGroupName"; public static final String SCHEDULE_SUBQUERY = "ScheduleSubQuery"; public static final String ENTITYCONTEXT_RESOURCEID = "EntityContext.resourceId"; public static final String ENTITYCONTEXT_GROUPID = "EntityContext.groupId"; public static final String ENTITYCONTEXT_PARENT_RESOURCEID = "EntityContext.parentResourceId"; public static final String ENTITYCONTEXT_RESOURCETYPEID = "EntityContext.resourceTypeId"; private void scheduleJobToPushScheduleUpdatesToAgents(EntityContext entityContext, String scheduleSubQuery) { Scheduler scheduler; try { scheduler = LookupUtil.getSchedulerBean(); final String DEFAULT_AGENT_JOB = "AGENT NOTIFICATION JOB"; final String DEFAULT_AGENT_GROUP = "AGENT NOTIFICATION GROUP"; final String DEFAULT_AGENT_TRIGGER = "AGENT NOTIFICATION TRIGGER"; final String randomSuffix = UUID.randomUUID().toString(); final String jobName = DEFAULT_AGENT_JOB + " - " + randomSuffix; JobDetail jobDetail = new JobDetail(jobName, DEFAULT_AGENT_GROUP, NotifyAgentsOfScheduleUpdatesJob.class); final String triggerName = DEFAULT_AGENT_TRIGGER + " - " + randomSuffix; SimpleTrigger simpleTrigger = new SimpleTrigger(triggerName, DEFAULT_AGENT_GROUP, new Date()); JobDataMap jobDataMap = simpleTrigger.getJobDataMap(); jobDataMap.put(TRIGGER_NAME, triggerName); jobDataMap.put(TRIGGER_GROUP_NAME, DEFAULT_AGENT_GROUP); jobDataMap.put(SCHEDULE_SUBQUERY, scheduleSubQuery); jobDataMap.put(ENTITYCONTEXT_RESOURCEID, Integer.toString(entityContext.getResourceId())); jobDataMap.put(ENTITYCONTEXT_GROUPID, Integer.toString(entityContext.getGroupId())); jobDataMap.put(ENTITYCONTEXT_PARENT_RESOURCEID, Integer.toString(entityContext.getParentResourceId())); jobDataMap.put(ENTITYCONTEXT_RESOURCETYPEID, Integer.toString(entityContext.getResourceTypeId())); if (isJobScheduled(scheduler, DEFAULT_AGENT_JOB, DEFAULT_AGENT_GROUP)) { simpleTrigger.setJobName(DEFAULT_AGENT_JOB); simpleTrigger.setJobGroup(DEFAULT_AGENT_GROUP); scheduler.scheduleJob(simpleTrigger); } else { scheduler.scheduleJob(jobDetail, simpleTrigger); } } catch (RuntimeException e) { // lookup wrapper throws runtime exceptions, no distinction between // types, so fallback and do the best we can. log.error("Failed to schedule agents update notification.", e); notifyAgentsOfScheduleUpdates(entityContext, scheduleSubQuery); } catch (SchedulerException e) { // should never happen, but fallback gracefully... log.error("Failed to schedule agents update notification.", e); notifyAgentsOfScheduleUpdates(entityContext, scheduleSubQuery); } } /* private boolean isTriggerScheduled(Scheduler scheduler, String name, String group) { boolean isScheduled = false; try { Trigger trigger = scheduler.getTrigger(name, group); if (trigger != null) { isScheduled = true; } } catch (SchedulerException se) { log.error("Error getting trigger", se); } return isScheduled; } */ 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 @SuppressWarnings("unchecked") public void notifyAgentsOfScheduleUpdates(EntityContext entityContext, String scheduleSubQuery) { List<Integer> agentIds = new ArrayList<Integer>(); try { String agentsQueryString = "" // + "SELECT DISTINCT ms.resource.agent.id " // + " FROM MeasurementSchedule ms " // + " WHERE ms.id IN ( " + scheduleSubQuery + " ) "; if (log.isDebugEnabled()) { log.debug("agentsQueryString: " + agentsQueryString); } Query agentsQuery = entityManager.createQuery(agentsQueryString); agentIds = agentsQuery.getResultList(); } catch (Throwable t) { log.error("Could not notify agents of updates", t); } // use composite query -- won't load managed entities, requires minimal wire transfer String scheduleRequestQueryString = "" // + "SELECT ms.resource.id, " // + " ms.id, " // + " ms.definition.name, " // + " ms.interval, " // + " ms.enabled, " // + " ms.definition.dataType, " // + " ms.definition.rawNumericType " // + " FROM MeasurementSchedule ms " // + " WHERE ms.id IN ( " + scheduleSubQuery + " ) " // + " AND ms.resource.agent.id = :agentId"; if (log.isDebugEnabled()) { log.debug("scheduleRequestQueryString: " + scheduleRequestQueryString); } Query scheduleRequestQuery = entityManager.createQuery(scheduleRequestQueryString); Map<Integer, ResourceMeasurementScheduleRequest> agentRequests = new HashMap<Integer, ResourceMeasurementScheduleRequest>(); for (int nextAgentId : agentIds) { scheduleRequestQuery.setParameter("agentId", nextAgentId); List<Object[]> scheduleRequests = scheduleRequestQuery.getResultList(); for (Object[] nextScheduleDataSet : scheduleRequests) { int resourceId = (Integer) nextScheduleDataSet[0]; ResourceMeasurementScheduleRequest resourceRequest = agentRequests.get(resourceId); if (resourceRequest == null) { resourceRequest = new ResourceMeasurementScheduleRequest(resourceId); agentRequests.put(resourceId, resourceRequest); } MeasurementScheduleRequest requestData = new MeasurementScheduleRequest( // (Integer) nextScheduleDataSet[1], // scheduleId (String) nextScheduleDataSet[2], // definitionName, (Long) nextScheduleDataSet[3], // interval, (Boolean) nextScheduleDataSet[4], // enabled, (DataType) nextScheduleDataSet[5], // dataType, (NumericType) nextScheduleDataSet[6]); // rawNumericType resourceRequest.addMeasurementScheduleRequest(requestData); } boolean markResources = false; try { Agent nextAgent = agentManager.getAgentByID(nextAgentId); AgentClient agentClient = agentManager.getAgentClient(nextAgent); boolean couldPing = agentClient.pingService(2000); // see if agent is up for sending if (couldPing) { Set<ResourceMeasurementScheduleRequest> requestsToSend = new HashSet<ResourceMeasurementScheduleRequest>( agentRequests.values()); agentClient.getMeasurementAgentService().updateCollection(requestsToSend); } else { log.error("Could not send measurement schedule updates to agent[id=" + nextAgentId + "], marking resources for update; agent ping failed"); markResources = true; } } catch (Throwable t) { log.error("Could not send measurement schedule updates to agent[id=" + nextAgentId + "], marking resources for update", t); markResources = true; } if (markResources) { markResources(entityContext, nextAgentId); } } } /** * The mtime on the Resources will tell the Agent it needs to pull down the * latest schedules next time it performs an Agent-Server sync. * * @param context the entity context * @param agentId the agent id */ private void markResources(EntityContext context, int agentId) { ResourceCriteria criteria = new ResourceCriteria(); criteria.clearPaging(); //important to avoid setting the ordering in the generated query if (context.type == EntityContext.Type.Resource) { criteria.addFilterId(context.resourceId); } else if (context.type == EntityContext.Type.ResourceGroup) { criteria.addFilterImplicitGroupIds(context.groupId); } else if (context.type == EntityContext.Type.AutoGroup) { criteria.addFilterParentResourceId(context.parentResourceId); criteria.addFilterResourceTypeId(context.resourceTypeId); } criteria.addFilterAgentId(agentId); try { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(criteria); generator.alterProjection("resource.id"); String resourceSubQuery = generator.getParameterReplacedQuery(false); String markResourceQueryString = "" // + "UPDATE Resource res " // + " SET res.mtime = :now " // + " WHERE res.id IN ( " + resourceSubQuery + " ) "; if (log.isDebugEnabled()) { log.debug("markResourceQueryString: " + markResourceQueryString); } Query markResourceQuery = entityManager.createQuery(markResourceQueryString); markResourceQuery.setParameter("now", System.currentTimeMillis()); int affectedRows = markResourceQuery.executeUpdate(); if (log.isDebugEnabled()) { log.debug("Marked " + affectedRows + " for future measurement schedule update"); } } catch (Throwable t) { log.error("Could not notify agents of updates", t); } } public String getMeasurementScheduleSubQueryForContext(Subject subject, EntityContext context, int[] measurementDefinitionIds) { if (context.type == EntityContext.Type.Resource) { if (authorizationManager.hasResourcePermission(subject, Permission.MANAGE_MEASUREMENTS, context.resourceId) == false) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to manage schedules for resource[id=" + context.resourceId + "]"); } } else if (context.type == EntityContext.Type.ResourceGroup) { if (authorizationManager.hasGroupPermission(subject, Permission.MANAGE_MEASUREMENTS, context.groupId) == false) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to manage schedules for resourceGroup[id=" + context.groupId + "]"); } } else if (context.type == EntityContext.Type.AutoGroup) { if (authorizationManager.hasAutoGroupPermission(subject, Permission.MANAGE_MEASUREMENTS, context.parentResourceId, context.resourceTypeId) == false) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to manage schedules for autoGroup[parentResourceId=" + context.parentResourceId + ", resourceTypeId=" + context.resourceTypeId + "]"); } } MeasurementScheduleCriteria criteria = new MeasurementScheduleCriteria(); criteria.clearPaging(); //important to avoid setting the ordering in the generated query if (context.type == EntityContext.Type.Resource) { criteria.addFilterResourceId(context.resourceId); } else if (context.type == EntityContext.Type.ResourceGroup) { criteria.addFilterResourceGroupId(context.groupId); } else if (context.type == EntityContext.Type.AutoGroup) { criteria.addFilterAutoGroupParentResourceId(context.parentResourceId); criteria.addFilterAutoGroupResourceTypeId(context.resourceTypeId); } criteria.addFilterDefinitionIds(ArrayUtils.wrapInArray(measurementDefinitionIds)); CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); ; generator.alterProjection("measurementschedule.id"); String measurementScheduleSubQuery = generator.getParameterReplacedQuery(false); return measurementScheduleSubQuery; } private void setAgentSynchronizationNeededByDefinitionsForAgent(int agentId, List<Integer> measurementDefinitionIds) { String updateSQL = "" // + "UPDATE Resource res " // + " SET res.mtime = :now " // + " WHERE res.agent.id = :agentId AND " // + " res.resourceType.id IN ( SELECT md.resourceType.id " // + " FROM MeasurementDefinition md " // + " WHERE md.id IN ( :definitionIds ) )"; Query updateQuery = entityManager.createQuery(updateSQL); updateQuery.setParameter("now", System.currentTimeMillis()); updateQuery.setParameter("agentId", agentId); updateQuery.setParameter("definitionIds", measurementDefinitionIds); int updateCount = updateQuery.executeUpdate(); if (log.isDebugEnabled()) { log.debug("" + updateCount + " resources mtime fields were updated as a result of this metric template update"); } } /** * @deprecated used for portal war */ @Deprecated public void updateSchedulesForAutoGroup(Subject subject, int parentResourceId, int childResourceType, int[] measurementDefinitionIds, long collectionInterval) { updateSchedulesForContext(subject, EntityContext.forAutoGroup(parentResourceId, childResourceType), measurementDefinitionIds, collectionInterval); } @Deprecated @Override public void disableSchedulesForAutoGroup(Subject subject, int parentResourceId, int childResourceType, int[] measurementDefinitionIds) { disableSchedulesForContext(subject, EntityContext.forAutoGroup(parentResourceId, childResourceType), measurementDefinitionIds); } @Deprecated @Override public void enableSchedulesForAutoGroup(Subject subject, int parentResourceId, int childResourceType, int[] measurementDefinitionIds) { enableSchedulesForContext(subject, EntityContext.forAutoGroup(parentResourceId, childResourceType), measurementDefinitionIds); } /** * Determine the Schedules for a Resource and DataType. The data type is used to filter out (numerical) measurement * and / or traits. If it is null, then we don't filter by DataType * * @param subject Subject of the caller * @param resourceId PK of the resource we're interested in * @param dataType DataType of the desired results use null for no filtering * @param displayType the display type of the property or null for no filtering * * @return List of MeasuremenSchedules for the given resource */ @SuppressWarnings("unchecked") public List<MeasurementSchedule> findSchedulesForResourceAndType(Subject subject, int resourceId, DataType dataType, DisplayType displayType, boolean enabledOnly) { OrderingField sortOrder = new OrderingField("ms.definition.displayName", PageOrdering.ASC); Query query = PersistenceUtility.createQueryWithOrderBy(entityManager, MeasurementSchedule.FIND_ALL_FOR_RESOURCE_ID, sortOrder); query.setParameter("resourceId", resourceId); query.setParameter("dataType", dataType); query.setParameter("displayType", displayType); query.setParameter("enabled", enabledOnly ? true : null); List<MeasurementSchedule> results = query.getResultList(); return results; } private boolean sendUpdatedSchedulesToAgent(Agent agent, Set<ResourceMeasurementScheduleRequest> resourceMeasurementScheduleRequest) { try { AgentClient agentClient = LookupUtil.getAgentManager().getAgentClient(agent); if (!agentClient.pingService(2000)) { if (log.isDebugEnabled()) { log.debug("Won't send MeasurementSchedules to offline Agent[id=" + agent.getId() + "]"); } return false; } agentClient.getMeasurementAgentService().updateCollection(resourceMeasurementScheduleRequest); return true; // successfully sync'ed schedules down to the agent } catch (Throwable t) { log.error("Error updating MeasurementSchedules for Agent[id=" + agent.getId() + "]: ", t); } return false; // catch all and presume the live sync failed } /** * Return a list of MeasurementSchedules for the given definition ids and resource id. * * @param definitionIds * @param resourceId * * @return a list of Schedules */ public List<MeasurementSchedule> findSchedulesByResourceIdAndDefinitionIds(Subject subject, int resourceId, int[] definitionIds) { return findSchedulesByResourcesAndDefinitions(subject, new int[] { resourceId }, definitionIds); } /** * This gets measurement schedules for a resource and optionally its dependents. It creates them as necessary. * * @param resourceIds The ids of the resources to retrieve schedules for * @param allSchedules The set to which the schedules should be added * @param getDescendents If true, schedules for all descendent resources will also be loaded */ private void getSchedulesForResourceAndItsDescendants(int[] resourceIds, Set<ResourceMeasurementScheduleRequest> allSchedules, boolean getDescendents) { if (resourceIds == null || resourceIds.length == 0) { // no work to do return; } try { for (int batchIndex = 0; batchIndex < resourceIds.length; batchIndex += 1000) { int[] batchIds = ArrayUtils.copyOfRange(resourceIds, batchIndex, batchIndex + 1000); /* * need to use a native query solution for both the insertion and returning the results because if we * go through Hibernate to return the results it will not see the effects of the insert statement */ measurementScheduleManager.insertSchedulesFor(batchIds); measurementScheduleManager.returnSchedulesFor(batchIds, allSchedules); if (getDescendents) { // recursively get all the default schedules for all children of the resource int[] batchChildrenIds = getChildrenIdByParentIds(batchIds); getSchedulesForResourceAndItsDescendants(batchChildrenIds, allSchedules, getDescendents); } } } catch (Throwable t) { log.warn("problem creating schedules for resourceIds [" + Arrays.toString(resourceIds) + "]", t); } return; } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public int insertSchedulesFor(int[] batchIds) throws Exception { /* * JM: (April 15th, 2009) * * the "res.id" token on the final line does not get the "res" alias from the outer query appropriately; * instead, it tries to reference the table name itself as "RHQ_RESOURCE.ID", which bombs with[2] on * postgres; i thought of using "WHERE ms.resource.uuid = res.uuid" which would work because UUID column * name is not reused for any other entity in the model, let alone on any table used in this query; however, * this felt like a hack, and I wasn't sure whether UUID would be unique across very large inventories; if * it's not, there is a slight chance that the insert query could do the wrong thing (albeit rare), so I * erred on the side of correctness and went with native sql which allowed me to use the proper id alias in * the correlated subquery; correctness aside, keeping the logic using resource id should allow the query * optimizer to use indexes instead of having to look up the rows on the resource table to get the uuid * * [1] - http://opensource.atlassian.com/projects/hibernate/browse/HHH-1397 * [2] - ERROR: invalid reference to FROM-clause entry for table "rhq_resource" * * Query insertHQL = entityManager.createQuery("" // * + "INSERT INTO MeasurementSchedule( enabled, interval, definition, resource ) \n" * + " SELECT md.defaultOn, md.defaultInterval, md, res \n" * + " FROM Resource res, ResourceType rt, MeasurementDefinition md \n" * + " WHERE res.id IN ( :resourceIds ) \n" * + " AND res.resourceType.id = rt.id \n" * + " AND rt.id = md.resourceType.id \n" * + " AND md.id NOT IN ( SELECT ms.definition.id \n" // * + " FROM MeasurementSchedule ms \n" // * + " WHERE ms.resource.id = res.id )"); */ Connection conn = null; PreparedStatement insertStatement = null; int created = -1; try { conn = dataSource.getConnection(); DatabaseType dbType = DatabaseTypeFactory.getDefaultDatabaseType(); String insertQueryString = null; if (dbType instanceof PostgresqlDatabaseType) { insertQueryString = MeasurementSchedule.NATIVE_QUERY_INSERT_SCHEDULES_POSTGRES; } else if (dbType instanceof OracleDatabaseType || dbType instanceof H2DatabaseType) { insertQueryString = MeasurementSchedule.NATIVE_QUERY_INSERT_SCHEDULES_ORACLE; } else if (dbType instanceof SQLServerDatabaseType) { insertQueryString = MeasurementSchedule.NATIVE_QUERY_INSERT_SCHEDULES_SQL_SERVER; } else { throw new IllegalArgumentException("Unknown database type, can't continue: " + dbType); } insertQueryString = JDBCUtil.transformQueryForMultipleInParameters(insertQueryString, "@@RESOURCES@@", batchIds.length); insertStatement = conn.prepareStatement(insertQueryString); JDBCUtil.bindNTimes(insertStatement, batchIds, 1); // first create whatever schedules may be needed created = insertStatement.executeUpdate(); if (log.isDebugEnabled()) { log.debug("Batch created [" + created + "] default measurement schedules for resource batch [" + batchIds + "]"); } } finally { JDBCUtil.safeClose(conn, insertStatement, null); } return created; } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public int returnSchedulesFor(int[] batchIds, Set<ResourceMeasurementScheduleRequest> allSchedules) throws Exception { Connection conn = null; PreparedStatement resultsStatement = null; int created = -1; try { conn = dataSource.getConnection(); String resultsQueryString = MeasurementSchedule.NATIVE_QUERY_REPORTING_RESOURCE_MEASUREMENT_SCHEDULE_REQUEST; resultsQueryString = JDBCUtil.transformQueryForMultipleInParameters(resultsQueryString, "@@RESOURCES@@", batchIds.length); resultsStatement = conn.prepareStatement(resultsQueryString); JDBCUtil.bindNTimes(resultsStatement, batchIds, 1); Map<Integer, ResourceMeasurementScheduleRequest> scheduleRequestMap = new HashMap<Integer, ResourceMeasurementScheduleRequest>(); ResultSet results = resultsStatement.executeQuery(); try { while (results.next()) { Integer resourceId = results.getInt(1); Integer scheduleId = results.getInt(2); String definitionName = results.getString(3); Long interval = results.getLong(4); Boolean enabled = results.getBoolean(5); DataType dataType = DataType.values()[results.getInt(6)]; NumericType rawNumericType = NumericType.values()[results.getInt(7)]; if (results.wasNull()) { rawNumericType = null; } ResourceMeasurementScheduleRequest scheduleRequest = scheduleRequestMap.get(resourceId); if (scheduleRequest == null) { scheduleRequest = new ResourceMeasurementScheduleRequest(resourceId); scheduleRequestMap.put(resourceId, scheduleRequest); allSchedules.add(scheduleRequest); } MeasurementScheduleRequest requestData = new MeasurementScheduleRequest(scheduleId, definitionName, interval, enabled, dataType, rawNumericType); scheduleRequest.addMeasurementScheduleRequest(requestData); } } finally { results.close(); } } finally { JDBCUtil.safeClose(conn, resultsStatement, null); } return created; } @SuppressWarnings("unchecked") private int[] getChildrenIdByParentIds(int[] batchIds) { Query query = entityManager.createNamedQuery(Resource.QUERY_FIND_CHILDREN_IDS_BY_PARENT_IDS); query.setParameter("parentIds", ArrayUtils.wrapInList(batchIds)); List<Integer> results = query.getResultList(); int[] batchChildrenIds = ArrayUtils.unwrapCollection(results); return batchChildrenIds; } public int getScheduledMeasurementsPerMinute() { Number rate = 0; try { Query query = entityManager.createNamedQuery(MeasurementSchedule.GET_SCHEDULED_MEASUREMENTS_PER_MINUTED); query.setParameter("status", InventoryStatus.COMMITTED); rate = (Number) query.getSingleResult(); } catch (Throwable t) { measurementScheduleManager.errorCorrectSchedules(); } return (rate == null) ? 0 : rate.intValue(); } @SuppressWarnings("unchecked") @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void errorCorrectSchedules() { /* * update mtime of resources whose schedules are < 30s, this will indicate to the * agent that it needs to sync / merge schedules for the resources updated here */ try { long now = System.currentTimeMillis(); String updateResourcesQueryString = "" // + " UPDATE Resource " // + " SET mtime = :currentTime " // + " WHERE id IN ( SELECT ms.resource.id " // + " FROM MeasurementSchedule ms " // + " WHERE ms.interval < 30000 ) "; Query updateResourcesQuery = entityManager.createQuery(updateResourcesQueryString); updateResourcesQuery.setParameter("currentTime", now); int resourcesUpdatedCount = updateResourcesQuery.executeUpdate(); // update schedules to 30s whose schedules are < 30s String updateSchedulesQueryString = "" // + " UPDATE MeasurementSchedule " // + " SET interval = 30000 " // + " WHERE interval < 30000 "; Query updateSchedulesQuery = entityManager.createQuery(updateSchedulesQueryString); int schedulesUpdatedCount = updateSchedulesQuery.executeUpdate(); if (resourcesUpdatedCount > 0) { // now try to tell the agents that certain resources have changed String findResourcesQueryString = "" // + " SELECT res.id " // + " FROM Resource res " // + " WHERE res.mtime = :currentTime "; Query findResourcesQuery = entityManager.createQuery(findResourcesQueryString); findResourcesQuery.setParameter("currentTime", now); List<Integer> updatedResourceIds = findResourcesQuery.getResultList(); updateMeasurementSchedulesForResources(ArrayUtils.unwrapCollection(updatedResourceIds)); log.error("MeasurementSchedule data was corrupt: automatically updated " + resourcesUpdatedCount + " resources and " + schedulesUpdatedCount + " to correct the issue; agents were notified"); } else { if (log.isDebugEnabled()) { log.debug("MeasurementSchedule data was checked for corruption, but all data was consistent"); } } } catch (Throwable t) { log.error("There was a problem correcting errors for MeasurementSchedules", t); } } private void updateMeasurementSchedulesForResources(int[] resourceIds) { if (resourceIds.length == 0) { return; } // first get all the resources, which is needed to get the agent mappings Subject overlord = subjectManager.getOverlord(); PageList<Resource> resources = resourceManager.findResourceByIds(overlord, resourceIds, false, PageControl.getUnlimitedInstance()); // then get all the requests Set<ResourceMeasurementScheduleRequest> requests = findSchedulesForResourceAndItsDescendants(resourceIds, false); Map<Agent, Set<ResourceMeasurementScheduleRequest>> agentScheduleMap = new HashMap<Agent, Set<ResourceMeasurementScheduleRequest>>(); for (Resource resource : resources) { Agent agent = resource.getAgent(); Set<ResourceMeasurementScheduleRequest> agentSchedules = agentScheduleMap.get(agent); if (agentSchedules == null) { agentSchedules = new HashSet<ResourceMeasurementScheduleRequest>(); agentScheduleMap.put(agent, agentSchedules); } for (ResourceMeasurementScheduleRequest request : requests) { int resId = resource.getId(); if (request.getResourceId() == resId) { agentSchedules.add(request); } } } for (Map.Entry<Agent, Set<ResourceMeasurementScheduleRequest>> agentScheduleEntry : agentScheduleMap.entrySet()) { Agent agent = agentScheduleEntry.getKey(); Set<ResourceMeasurementScheduleRequest> schedules = agentScheduleEntry.getValue(); try { sendUpdatedSchedulesToAgent(agent, schedules); } catch (Throwable t) { if (log.isDebugEnabled()) { log.debug("Tried to immediately sync agent[name=" + agent.getName() + "] with error-corrected schedules failed"); } } } } public void disableSchedulesForResource(Subject subject, int resourceId, int[] measurementDefinitionIds) { disableSchedulesForContext(subject, EntityContext.forResource(resourceId), measurementDefinitionIds); } public void disableSchedulesForCompatibleGroup(Subject subject, int groupId, int[] measurementDefinitionIds) { disableSchedulesForContext(subject, EntityContext.forGroup(groupId), measurementDefinitionIds); } @RequiredPermission(Permission.MANAGE_SETTINGS) @Override public void disableSchedulesForResourceType(Subject subject, int[] measurementDefinitionIds, boolean updateExistingSchedules) { modifyDefaultCollectionIntervalForMeasurementDefinitions(subject, measurementDefinitionIds, -1, updateExistingSchedules); } @Deprecated @Override public void disableMeasurementTemplates(Subject subject, int[] measurementDefinitionIds) { disableDefaultCollectionForMeasurementDefinitions(subject, measurementDefinitionIds, true); } public void enableSchedulesForResource(Subject subject, int resourceId, int[] measurementDefinitionIds) { enableSchedulesForContext(subject, EntityContext.forResource(resourceId), measurementDefinitionIds); } public void enableSchedulesForCompatibleGroup(Subject subject, int groupId, int[] measurementDefinitionIds) { enableSchedulesForContext(subject, EntityContext.forGroup(groupId), measurementDefinitionIds); } @RequiredPermission(Permission.MANAGE_SETTINGS) @Override public void enableSchedulesForResourceType(Subject subject, int[] measurementDefinitionIds, boolean updateExistingSchedules) { modifyDefaultCollectionIntervalForMeasurementDefinitions(subject, measurementDefinitionIds, 0, updateExistingSchedules); } /** * @deprecated */ @Deprecated @Override public void enableMeasurementTemplates(Subject subject, int[] measurementDefinitionIds) { modifyDefaultCollectionIntervalForMeasurementDefinitions(subject, measurementDefinitionIds, 0, true); } /** * @param subject A subject that must be valid * @param schedule A MeasurementSchedule to persist. */ public void updateSchedule(Subject subject, MeasurementSchedule schedule) { // attach it so we can navigate to its resource object for authz check MeasurementSchedule attached = entityManager.find(MeasurementSchedule.class, schedule.getId()); if (authorizationManager.hasResourcePermission(subject, Permission.MANAGE_MEASUREMENTS, attached.getResource() .getId()) == false) { throw new PermissionException("User[" + subject.getName() + "] does not have permission to view measurementSchedule[id=" + schedule.getId() + "]"); } verifyMinimumCollectionInterval(schedule); entityManager.merge(schedule); } public void updateSchedulesForResource(Subject subject, int resourceId, int[] measurementDefinitionIds, long collectionInterval) { updateSchedulesForContext(subject, EntityContext.forResource(resourceId), measurementDefinitionIds, collectionInterval); } public void updateSchedulesForCompatibleGroup(Subject subject, int groupId, int[] measurementDefinitionIds, long collectionInterval) { // don't verify minimum collection interval here, it will be caught by updateMeasurementSchedules callee updateSchedulesForContext(subject, EntityContext.forGroup(groupId), measurementDefinitionIds, collectionInterval); } @RequiredPermission(Permission.MANAGE_SETTINGS) @Override public void updateSchedulesForResourceType(Subject subject, int[] measurementDefinitionIds, long collectionInterval, boolean updateExistingSchedules) { collectionInterval = verifyMinimumCollectionInterval(collectionInterval); modifyDefaultCollectionIntervalForMeasurementDefinitions(subject, measurementDefinitionIds, collectionInterval, updateExistingSchedules); } @Deprecated @Override public void updateMeasurementTemplates(Subject subject, int[] measurementDefinitionIds, long collectionInterval) { collectionInterval = verifyMinimumCollectionInterval(collectionInterval); updateDefaultCollectionIntervalForMeasurementDefinitions(subject, measurementDefinitionIds, collectionInterval, true); } @SuppressWarnings("unchecked") public PageList<MeasurementScheduleComposite> getMeasurementScheduleCompositesByContext(Subject subject, EntityContext context, PageControl pc) { // check authorization up front, so that criteria-based queries can run without authz checks switch (context.type) { case Resource: if (authorizationManager.canViewResource(subject, context.resourceId) == false) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view measurement schedules for resource[id=" + context.resourceId + "]"); } break; case ResourceGroup: if (authorizationManager.canViewGroup(subject, context.groupId) == false) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view measurement schedules for resourceGroup[id=" + context.groupId + "]"); } break; case AutoGroup: if (authorizationManager.canViewAutoGroup(subject, context.parentResourceId, context.resourceTypeId) == false) { throw new PermissionException("User [" + subject.getName() + "] does not have permission to view measurement schedules for autoGroup[parentResourceId=" + context.parentResourceId + ", resourceTypeId=" + context.resourceTypeId + "]"); } break; } PageList<MeasurementDefinition> definitions; Map<Integer, Long> definitionIntervalMap = new HashMap<Integer, Long>(); Map<Integer, Boolean> definitionEnabledMap = new HashMap<Integer, Boolean>(); if (context.type == EntityContext.Type.ResourceTemplate) { MeasurementDefinitionCriteria criteria = new MeasurementDefinitionCriteria(); criteria.addFilterResourceTypeId(context.resourceTypeId); CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); CriteriaQueryRunner<MeasurementDefinition> queryRunner = new CriteriaQueryRunner(criteria, generator, entityManager); definitions = queryRunner.execute(); for (MeasurementDefinition definition : definitions) { definitionIntervalMap.put(definition.getId(), definition.getDefaultInterval()); definitionEnabledMap.put(definition.getId(), definition.isDefaultOn()); } } else { // Do general criteria setup. MeasurementScheduleCriteria criteria = new MeasurementScheduleCriteria(); switch (context.type) { case Resource: criteria.addFilterResourceId(context.resourceId); break; case ResourceGroup: criteria.addFilterResourceGroupId(context.groupId); break; case AutoGroup: criteria.addFilterAutoGroupParentResourceId(context.parentResourceId); criteria.addFilterAutoGroupResourceTypeId(context.resourceTypeId); break; } criteria.setPageControl(pc); // for primary return list, use passed PageControl pc.addDefaultOrderingField("definition.displayName"); // Get the core definitions. CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); // We previously used the following altered projection for the criteria query: // // generator.alterProjection(" distinct measurementschedule.definition"); // // Hibernate4 no longer allowed for the generated criteria JPQL for this projection: // // SELECT distinct measurementschedule.definition // FROM MeasurementSchedule measurementschedule // LEFT JOIN measurementschedule.definition orderingField0 // WHERE ( measurementschedule.resource.id IN ( :resourceId ) ) // ORDER BY orderingField0.displayName ASC // // It causes: // SQLGrammarException: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list // // In essence, using DISTINCT now requires that we use the LEFT JOIN alias in the select // list. To support this we could probably have made some tricky coding changes to the // generator. But seeing that this would be to support non-default criteria queries (i.e // the altered projection using DISTINCT), of which this is the only one in the code base, // and we are in control of the order by clause, and therefore are predictably working with // the JPQL above, I've chosen to just make a change to the custom altered projection, using // the JPQL to guide me. generator.alterProjection(" distinct orderingField0"); generator.alterCountProjection(" count(distinct orderingField0)"); CriteriaQueryRunner<MeasurementDefinition> queryRunner = new CriteriaQueryRunner(criteria, generator, entityManager); definitions = queryRunner.execute(); // Reset paging -- remove ordering, add group by. criteria.setPageControl(PageControl.getUnlimitedInstance()); generator.setGroupByClause(" measurementschedule.definition.id "); // Get the interval results. generator.alterProjection("" // + " measurementschedule.definition.id, " // + " min(measurementschedule.interval), " // + " max(measurementschedule.interval) "); Query query = generator.getQuery(entityManager); List<Object[]> definitionIntervalResults = query.getResultList(); // Get the enabled results. criteria.addFilterEnabled(true); generator.alterProjection(" measurementschedule.definition.id, count(measurementschedule.id) "); query = generator.getQuery(entityManager); List<Object[]> definitionEnabledResults = query.getResultList(); // Generate intermediate maps for intervals and enabled values. for (Object[] nextInterval : definitionIntervalResults) { int definitionId = (Integer) nextInterval[0]; long minInterval = (Long) nextInterval[1]; long maxInterval = (Long) nextInterval[2]; long interval = (minInterval != maxInterval) ? 0 : minInterval; definitionIntervalMap.put(definitionId, interval); } int size = getResourceCount(context); for (Object[] nextEnabled : definitionEnabledResults) { int definitionId = (Integer) nextEnabled[0]; long enabledCount = (Long) nextEnabled[1]; Boolean enabled = null; if (enabledCount == size) { enabled = true; } else if (enabledCount == 0) { enabled = false; } definitionEnabledMap.put(definitionId, enabled); } } // Finally, merge everything together. List<MeasurementScheduleComposite> composites = new ArrayList<MeasurementScheduleComposite>(); for (MeasurementDefinition next : definitions) { int definitionId = next.getId(); Boolean enabled = false; if (definitionEnabledMap.containsKey(definitionId)) { enabled = definitionEnabledMap.get(definitionId); } long interval = definitionIntervalMap.get(definitionId); MeasurementScheduleComposite result = new MeasurementScheduleComposite(next, enabled, interval); composites.add(result); } return new PageList<MeasurementScheduleComposite>(composites, composites.size(), pc); } private int getResourceCount(EntityContext context) { if (context.type == EntityContext.Type.Resource) { return 1; } else if (context.type == EntityContext.Type.ResourceGroup) { return resourceGroupManager.getExplicitGroupMemberCount(context.groupId); } else if (context.type == EntityContext.Type.AutoGroup) { ResourceCriteria criteria = new ResourceCriteria(); criteria.addFilterParentResourceId(context.parentResourceId); criteria.addFilterResourceTypeId(context.resourceTypeId); criteria.setPageControl(PageControl.getSingleRowInstance()); // get one record, then extract totalSize PageList<Resource> results = resourceManager .findResourcesByCriteria(subjectManager.getOverlord(), criteria); return results.getTotalSize(); } return 0; } @SuppressWarnings("unchecked") public PageList<MeasurementSchedule> findSchedulesByCriteria(Subject subject, MeasurementScheduleCriteria criteria) { CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria); ; if (authorizationManager.isInventoryManager(subject) == false) { generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE, subject.getId()); } CriteriaQueryRunner<MeasurementSchedule> queryRunner = new CriteriaQueryRunner(criteria, generator, entityManager); return queryRunner.execute(); } // public PageList<MeasurementSchedule> getResourceMeasurementSchedulesFromAgent(Subject subject, int resourceId) { // //verifyViewPermissionForMeasurementSchedules(subject, measurementScheduleIds); // // AgentClient agentClient = agentManager.getAgentClient(resourceId); // MeasurementAgentService measurementAgentSvc = agentClient.getMeasurementAgentService(); // // PageList<MeasurementSchedule> schedules = new PageList<MeasurementSchedule>(); // for (int scheduleId : measurementAgentSvc.getMeasurementScheduleIdsForResource(resourceId)) { // MeasurementSchedule schedule = getScheduleById(scheduleId); // schedules.add(schedule); // } // // return schedules; // } // // private void verifyViewPermissionForMeasurementSchedules(Subject subject, int[] measurementScheduleIds) { // for (int id : measurementScheduleIds) { // verifyViewPermission(subject, id); // } // } // // private void verifyViewPermission(Subject subject, int scheduleId) { // MeasurementSchedule schedule = entityManager.find(MeasurementSchedule.class, scheduleId); // if (authorizationManager.hasResourcePermission(subject, Permission.MANAGE_MEASUREMENTS, schedule.getResource() // .getId()) == false) { // throw new PermissionException("User[" + subject.getName() // + "] does not have permission to view measurementSchedule[id=" + schedule.getId() + "]"); // } // } }