//
  //
  // Data points
  //
  public void saveDataPoint(DataPointVO point) {
    stopDataPoint(point.getId());

    // Since the point's data type may have changed, we must ensure that the other attrtibutes are
    // still ok with
    // it.
    int dataType = point.getPointLocator().getDataTypeId();

    // Chart renderer
    if (point.getChartRenderer() != null && !point.getChartRenderer().getDef().supports(dataType))
      // Return to a default renderer
      point.setChartRenderer(null);

    // Text renderer
    if (point.getTextRenderer() != null && !point.getTextRenderer().getDef().supports(dataType))
      // Return to a default renderer
      point.defaultTextRenderer();

    // Event detectors
    Iterator<PointEventDetectorVO> peds = point.getEventDetectors().iterator();
    while (peds.hasNext()) {
      PointEventDetectorVO ped = peds.next();
      if (!ped.getDef().supports(dataType))
        // Remove the detector.
        peds.remove();
    }

    new DataPointDao().saveDataPoint(point);

    if (point.isEnabled()) startDataPoint(point);
  }
  private void startDataPoint(DataPointVO vo) {
    synchronized (dataPoints) {
      Assert.isTrue(vo.isEnabled());

      // Only add the data point if its data source is enabled.
      DataSourceRT ds = getRunningDataSource(vo.getDataSourceId());
      if (ds != null) {
        // Change the VO into a data point implementation.
        DataPointRT dataPoint = new DataPointRT(vo, vo.getPointLocator().createRuntime());

        // Add/update it in the data image.
        dataPoints.put(dataPoint.getId(), dataPoint);

        // Initialize it.
        dataPoint.initialize();
        DataPointListener l = getDataPointListeners(vo.getId());
        if (l != null) l.pointInitialized();

        // Add/update it in the data source.
        ds.addDataPoint(dataPoint);
      }
    }
  }
 @Override
 public int getDataTypeId() {
   return vo.getPointLocator().getDataTypeId();
 }
  private void savePointValue(
      PointValueTime newValue, SetPointSource source, boolean async, boolean saveToDatabase) {
    // Null values are not very nice, and since they don't have a specific meaning they are hereby
    // ignored.
    if (newValue == null) return;

    // Check the data type of the value against that of the locator, just for fun.
    int valueDataType = DataTypes.getDataType(newValue.getValue());
    if (valueDataType != DataTypes.UNKNOWN && valueDataType != vo.getPointLocator().getDataTypeId())
      // This should never happen, but if it does it can have serious downstream consequences. Also,
      // we need
      // to know how it happened, and the stack trace here provides the best information.
      throw new ShouldNeverHappenException(
          "Data type mismatch between new value and point locator: newValue="
              + DataTypes.getDataType(newValue.getValue())
              + ", locator="
              + vo.getPointLocator().getDataTypeId());

    // Check if this value qualifies for discardation.
    if (vo.isDiscardExtremeValues()
        && DataTypes.getDataType(newValue.getValue()) == DataTypes.NUMERIC) {
      double newd = newValue.getDoubleValue();
      if (newd < vo.getDiscardLowLimit() || newd > vo.getDiscardHighLimit())
        // Discard the value
        return;
    }

    if (newValue.getTime() > System.currentTimeMillis() + SystemSettingsDao.getFutureDateLimit()) {
      // Too far future dated. Toss it. But log a message first.
      LOG.warn(
          "Future dated value detected: pointId="
              + vo.getId()
              + ", value="
              + newValue.getStringValue()
              + ", type="
              + vo.getPointLocator().getDataTypeId()
              + ", ts="
              + newValue.getTime(),
          new Exception());
      return;
    }

    boolean backdated = pointValue != null && newValue.getTime() < pointValue.getTime();

    // Determine whether the new value qualifies for logging.
    boolean logValue;
    // ... or even saving in the cache.
    boolean saveValue = true;
    switch (vo.getLoggingType()) {
      case DataPointVO.LoggingTypes.ON_CHANGE:
        if (pointValue == null) logValue = true;
        else if (backdated)
          // Backdated. Ignore it
          logValue = false;
        else {
          if (newValue.getValue() instanceof NumericValue) {
            // Get the new double
            double newd = newValue.getDoubleValue();

            // See if the new value is outside of the tolerance.
            double diff = toleranceOrigin - newd;
            if (diff < 0) diff = -diff;

            if (diff > vo.getTolerance()) {
              toleranceOrigin = newd;
              logValue = true;
            } else logValue = false;
          } else logValue = !ObjectUtils.equals(newValue.getValue(), pointValue.getValue());
        }

        saveValue = logValue;
        break;
      case DataPointVO.LoggingTypes.ALL:
        logValue = true;
        break;
      case DataPointVO.LoggingTypes.ON_TS_CHANGE:
        if (pointValue == null) logValue = true;
        else if (backdated)
          // Backdated. Ignore it
          logValue = false;
        else logValue = newValue.getTime() != pointValue.getTime();

        saveValue = logValue;
        break;
      case DataPointVO.LoggingTypes.INTERVAL:
        if (!backdated) intervalSave(newValue);
      default:
        logValue = false;
    }

    if (!saveToDatabase) logValue = false;

    if (saveValue) valueCache.savePointValue(newValue, source, logValue, async);

    // add annotation to newValue before firing events so event detectors can
    // fetch the annotation
    if (source != null) {
      newValue =
          new AnnotatedPointValueTime(
              newValue.getValue(), newValue.getTime(), source.getSetPointSourceMessage());
    }

    // Ignore historical values.
    if (pointValue == null || newValue.getTime() >= pointValue.getTime()) {
      PointValueTime oldValue = pointValue;
      pointValue = newValue;
      fireEvents(oldValue, newValue, source != null, false);
    } else fireEvents(null, newValue, false, true);
  }