/**
   * If the measurement baselines for the corresponding resources are the same, that value will be
   * returned; otherwise null will be returned
   */
  public MeasurementBaseline getBaselineIfEqual(Subject subject, int groupId, int definitionId) {
    Query query =
        entityManager.createQuery(
            "" //
                + "SELECT MIN(mb.baselineMin),  MAX(mb.baselineMin), " //
                + "       MIN(mb.baselineMean), MAX(mb.baselineMean), " //
                + "       MIN(mb.baselineMax),  MAX(mb.baselineMax), " //
                + "       COUNT(mb.id) " //
                + "  FROM MeasurementBaseline mb " //
                + "  JOIN mb.schedule ms " //
                + "  JOIN ms.resource res " //
                + "  JOIN res.implicitGroups rg " //
                + " WHERE rg.id = :groupId " //
                + "   AND ms.definition.id = :definitionId ");
    query.setParameter("groupId", groupId);
    query.setParameter("definitionId", definitionId);
    Object[] results = (Object[]) query.getSingleResult();

    MeasurementBaseline baseline = new MeasurementBaseline();
    if ((Long) results[6] == 0) {
      // no baselines calculated yet, return null to indicate that
      return null;
    }

    // there was at least one baseline, so one or more of min/mean/max might be non-null
    if (results[0] == null || results[1] == null) {
      baseline.setMin(null);
    } else if (Math.abs((Double) results[0] - (Double) results[1]) < 1e-9) {
      baseline.setMin((Double) results[0]); // they are close enough to being equal
    } else {
      baseline.setMin(
          -1.0); // use negative to represent mixed, because we currently don't support graphing
      // negs
    }
    if (results[2] == null || results[3] == null) {
      baseline.setMean(null);
    } else if (Math.abs((Double) results[2] - (Double) results[3]) < 1e-9) {
      baseline.setMean((Double) results[2]); // they are close enough to being equal
    } else {
      baseline.setMean(
          -1.0); // use negative to represent mixed, because we currently don't support graphing
      // negs
    }
    if (results[4] == null || results[5] == null) {
      baseline.setMax(null);
    } else if (Math.abs((Double) results[4] - (Double) results[5]) < 1e-9) {
      baseline.setMax((Double) results[4]); // they are close enough to being equal
    } else {
      baseline.setMax(
          -1.0); // use negative to represent mixed, because we currently don't support graphing
      // negs
    }
    return baseline;
  }
  @TransactionAttribute(TransactionAttributeType.NEVER)
  public MeasurementBaseline calculateAutoBaseline(
      Subject subject, int groupId, int definitionId, long startDate, long endDate, boolean save)
      throws BaselineCreationException, MeasurementNotFoundException {

    MeasurementBaseline result =
        measurementBaselineManager.calculateAutoBaselineForGroupInNewTransaction(
            subject, groupId, definitionId, startDate, endDate, save);

    if (save) {
      // note, this executes in a new transaction so the baseline must already be committed to the
      // database
      agentStatusManager.updateByMeasurementBaseline(result.getId());
    }

    return result;
  }
  public void enableAutoBaselineCalculation(
      Subject subject, Integer[] resourceIds, Integer[] definitionIds) {
    // bail out early if there's nothing to do
    if ((resourceIds.length < 1) || (definitionIds.length < 1)) {
      return;
    }

    List<MeasurementBaseline> bList =
        getBaselinesForResourcesAndDefinitionIds(resourceIds, definitionIds);
    for (MeasurementBaseline bl : bList) {
      if (!authorizationManager.hasResourcePermission(
          subject, Permission.MANAGE_MEASUREMENTS, bl.getSchedule().getResource().getId())) {
        throw new PermissionException(
            "Cannot enable baseline [" + bl + "] - you do not have permission");
      }

      bl.setUserEntered(false);
    }
  }
  private MeasurementBaseline calculateBaselineForGroup(
      int groupId,
      int definitionId,
      boolean userEntered,
      long startDate,
      long endDate,
      boolean save)
      throws DataNotAvailableException, BaselineCreationException {

    MeasurementAggregate agg =
        dataManager.getAggregate(
            subjectManager.getOverlord(), groupId, definitionId, startDate, endDate);

    Subject overlord = subjectManager.getOverlord();
    List<Integer> resourceIds = resourceManager.findImplicitResourceIdsByResourceGroup(groupId);
    List<MeasurementSchedule> schedules =
        measurementScheduleManager.findSchedulesByResourceIdsAndDefinitionId(
            overlord, ArrayUtils.unwrapCollection(resourceIds), definitionId);

    MeasurementBaseline baseline = null;
    for (MeasurementSchedule schedule : schedules) {
      // attach the entity, so we can find the baseline
      schedule = entityManager.merge(schedule);

      if (save && (schedule.getBaseline() != null)) {
        /*
         * If saving, make sure we're updating the existing one, if it exists
         */
        baseline = schedule.getBaseline();
      } else {
        /*
         * Otherwise, if we're not saving or if the the schedule doesn't have a current baseline, we create a new
         * baseline object
         */
        baseline = new MeasurementBaseline();

        if (save) {
          /*
           * But, if we *are* in save mode, then set the relationship so when we merge the schedule below it
           * persists this new baseline too
           */
          baseline.setSchedule(schedule);
        }
      }

      baseline.setUserEntered(userEntered);
      baseline.setMean(agg.getAvg());
      baseline.setMin(agg.getMin());
      baseline.setMax(agg.getMax());

      if (save) {
        entityManager.persist(baseline);
        entityManager.merge(schedule);
      }
    }

    // all baselines should be the same
    return baseline;
  }
  private MeasurementBaseline calculateBaseline(
      MeasurementSchedule schedule, boolean userEntered, long startDate, long endDate, boolean save)
      throws DataNotAvailableException, BaselineCreationException {
    /*
     * jmarques: 2007-10-26
     *
     * navigation from schedule to definition is safe here because the only caller to this method is
     * calculateAutoBaseline( Subject, Integer, long, long, boolean ), which uses entityManager.find, so the
     * schedule should still be attached since the transaction is propagated to this method
     */
    if (schedule.getDefinition().getNumericType() != NumericType.DYNAMIC) {
      throw new BaselineCreationException(
          "Baseline calculation is only valid for a dynamic measurement");
    }

    MeasurementAggregate agg =
        dataManager.getAggregate(
            subjectManager.getOverlord(), schedule.getId(), startDate, endDate);

    // attach the entity, so we can find the baseline
    schedule = entityManager.merge(schedule);

    MeasurementBaseline baseline = null;
    if (save && (schedule.getBaseline() != null)) {
      /*
       * If saving, make sure we're updating the existing one, if it exists
       */
      baseline = schedule.getBaseline();
    } else {
      /*
       * Otherwise, if we're not saving or if the the schedule doesn't have a current baseline, we create a new
       * baseline object
       */
      baseline = new MeasurementBaseline();

      if (save) {
        /*
         * But, if we *are* in save mode, then set the relationship so when we merge the schedule below it
         * persists this new baseline too
         */
        baseline.setSchedule(schedule);
      }
    }

    baseline.setUserEntered(userEntered);
    baseline.setMean(agg.getAvg());
    baseline.setMin(agg.getMin());
    baseline.setMax(agg.getMax());

    if (save) {
      entityManager.persist(baseline);
      entityManager.merge(schedule);
    }

    return baseline;
  }