func (measurement *Measurement) Create(context *ctp.ApiContext) *ctp.HttpError {
	measurement.BuildLinks(context)
	measurement.Metric = ctp.ShortenLink(context.CtpBase, measurement.Metric)
	if !ctp.IsShortLink(measurement.Metric) {
		return ctp.NewBadRequestError("Invalid metric URL")
	}

	if !measurement.State.IsValid() {
		return ctp.NewBadRequestError("Invalid or missing state value")
	}

	if measurement.Result != nil {
		if err := measurementCheckResult(context, measurement); err != nil {
			return err
		}
	} else {
		if err := measurementCheckMetric(context, measurement); err != nil {
			return err
		}
	}

	if measurement.Objective != nil {
		if err := measurementObjectiveEvaluate(context, measurement); err != nil {
			return err
		}
	}

	if !ctp.CreateResource(context, "measurements", measurement) {
		return ctp.NewInternalServerError("Could not save measurement object")
	}
	return nil
}
func measurementCheckMetric(context *ctp.ApiContext, item *Measurement) *ctp.HttpError {
	var metric Metric

	if item.Metric == "" {
		return ctp.NewBadRequestError("Missing metric attribute in measurement.")
	}

	metricParams, ok := ctp.ParseLink(context.CtpBase, "@/metrics/$", item.Metric)
	if !ok {
		return ctp.NewBadRequestError("Metric URL is incorrect in measurement")
	}

	if !ctp.LoadResource(context, "metrics", ctp.Base64Id(metricParams[0]), &metric) {
		return ctp.NewBadRequestErrorf("Metric %s does not exist", ctp.ExpandLink(context.CtpBase, item.Metric))
	}
	return nil
}
func measurementCheckResult(context *ctp.ApiContext, item *Measurement) *ctp.HttpError {
	var metric Metric

	if item.Metric == "" {
		return ctp.NewBadRequestError("Missing metric attribute in measurement.")
	}

	if item.Result == nil {
		return ctp.NewBadRequestError("Missing result attribute in measurement.")
	}

	metricParams, ok := ctp.ParseLink(context.CtpBase, "@/metrics/$", item.Metric)
	if !ok {
		return ctp.NewBadRequestError("Metric URL is incorrect in measurement")
	}

	if !ctp.LoadResource(context, "metrics", ctp.Base64Id(metricParams[0]), &metric) {
		return ctp.NewBadRequestErrorf("Metric %s does not exist", ctp.ExpandLink(context.CtpBase, item.Metric))
	}

	for _, row := range item.Result.Value {
		if len(row) != len(metric.ResultFormat) {
			return ctp.NewBadRequestErrorf("Metric expects %d columns, but result value provides %d", len(metric.ResultFormat), len(row))
		}
		for name, cell := range row {
			metricDetailFound := false
			for _, metricDetail := range metric.ResultFormat {
				if metricDetail.Name == name {
					metricDetailFound = true
					kind := reflect.ValueOf(cell).Kind()
					switch metricDetail.Type {
					case "number":
						if kind != reflect.Float64 {
							return ctp.NewBadRequestError("Metric expects a number, but result is of different type")
						}
					case "boolean":
						if kind != reflect.Bool {
							return ctp.NewBadRequestError("Metric expects a boolean, but result is of different type")
						}
					case "string":
						if kind != reflect.String {
							return ctp.NewBadRequestError("Metric expects a string, but result is of different type")
						}
					default:
						return ctp.NewBadRequestError("Metric type information is incorrect")
					}
					break // from for
				}
			}
			if !metricDetailFound {
				return ctp.NewBadRequestErrorf("Metric does not describe '%s', which appears in result", name)
			}
		}
	}
	return nil
}
func (trigger *Trigger) Create(context *ctp.ApiContext) *ctp.HttpError {
	trigger.BuildLinks(context)
	trigger.Measurement = ctp.ShortenLink(context.CtpBase, trigger.Measurement)
	if !ctp.IsShortLink(trigger.Measurement) {
		return ctp.NewBadRequestError("Invalid measurement URL")
	}

	ok, err := triggerCheckCondition(context, trigger, nil)
	if err != nil {
		return ctp.NewBadRequestErrorf("%s", err.Error())
	}

	if ok {
		triggerLogAndNotify(context, trigger, nil)
	}

	if !ctp.CreateResource(context, "triggers", trigger) {
		return ctp.NewHttpError(http.StatusInternalServerError, "Could not save object")
	}
	return nil
}
func (measurement *Measurement) Update(context *ctp.ApiContext, update ctp.ResourceUpdater) *ctp.HttpError {
	measurement.BuildLinks(context)
	up, ok := update.(*Measurement)
	if !ok {
		return ctp.NewInternalServerError("Updated object is not a measurement") // should never happen
	}

	switch context.QueryParam {
	case "userActivated":
		switch up.State {
		case "activated":
			if measurement.State == "deactivated" {
				measurement.State = "pending" // FIXME: add backoffice logic for notification of state change?
			}
		case "deactivated":
			measurement.State = "deactivated"
			measurement.Result = nil
		default:
			return ctp.NewBadRequestError("state can only be 'activated' or 'deactivated'")
		}
	case "objective":
		measurement.Objective = up.Objective
		if err := measurementObjectiveEvaluate(context, measurement); err != nil {
			return err
		}
	case "result":
		if measurement.State == "deactivated" {
			return ctp.NewHttpError(http.StatusConflict, "Measurement is not in activated state.")
		}
		if measurement.State == "pending" {
			measurement.State = "activated"
		}

		if up.Result == nil {
			return ctp.NewBadRequestError("No result provided in request")
		}

		measurement.Result = up.Result

		if measurement.Result.UpdateTime.IsZero() {
			measurement.Result.UpdateTime = ctp.Now()
		}

		if err := measurementCheckMetric(context, measurement); err != nil {
			return err
		}

		if measurement.Objective != nil {
			if err := measurementObjectiveEvaluate(context, measurement); err != nil {
				return err
			}
		}

		measurementTriggersEvaluate(context, measurement)

	default:
		return ctp.NewBadRequestError("invalid query string") // should never happen, because already filtered in serve.go
	}

	if !ctp.UpdateResource(context, "measurements", measurement.Id, measurement) {
		return ctp.NewInternalServerError("Could not update measurement object")
	}
	return nil
}