Annotation Interface AuditedConditional


@Retention(RUNTIME) @Target(METHOD) @Documented public @interface AuditedConditional
Conditional cousin of Audited. Method-level annotation that asks AuditedAspect to emit an audit event of the declared AuditEventType only when the when() SpEL predicate evaluates to true on the post-invocation context.

Motivated by AUDIT_PHASE_C_RECCE.md bucket 2c: a cluster of imperative auditTrailService.addUpdateEvent(...) callers sit behind an early-return guard (e.g.

  if ( dimension.getCellTypeAssignments().stream().noneMatch( CellTypeAssignment::isPreferred ) ) {
      return PreferredCellTypeAssignmentChangeOutcome.UNCHANGED;
  }
  ...
  auditTrailService.addUpdateEvent( ee, FooEvent.class, "..." );
Plain @Audited would unconditionally fire on every successful return, which is wrong (the guard branch is a no-op). This annotation lets the aspect skip the no-op branch by evaluating a SpEL predicate against the same context used for Audited.messageSpel(): method arguments resolve by name, the return value is exposed as #result, and positional access via #root.args[i] also works.

Common predicate shapes:

  • when = "!#result.isEmpty()" — fire only when a collection-returning method actually produced something.
  • when = "#result.name() != 'UNCHANGED'" — fire only when an enum-returning outcome method actually changed state.
  • when = "#result > 0" — fire only when a count-returning method did non-zero work.
  • when = "!#toRemove.isEmpty() || !#toAdd.isEmpty()" — fire only when the input deltas would actually result in a change (parameters resolved by name).

Semantics:

  • @AfterReturning only — throwing methods record nothing (same as Audited).
  • If the when() SpEL fails to evaluate or returns null, no audit row is written and the failure is logged at ERROR. This is the safe default: when the predicate is unclear, err on the side of not emitting (no false positives in the audit log). Contrast with Audited.messageSpel(), where a SpEL failure falls back to Audited.message() because dropping the row would be worse than a bad note.
  • Otherwise the emission path is identical to Audited: locate the first Auditable argument, optional AuditEventPayload, resolve note via message() / messageSpel(), delegate to AuditTrailService.addUpdateEventWithPayload, publish an AuditedEvent.

A method may carry either Audited or @AuditedConditional but not both — the aspect's two @AfterReturning advices match on the respective annotation and would otherwise double-fire.

  • Optional Element Summary

    Optional Elements
    Modifier and Type
    Optional Element
    Description
    Optional plain (non-SpEL) note string.
    Optional SpEL expression for the note, evaluated only when when() is true.
    The concrete AuditEventType subclass to record.
    Optional SpEL expression that resolves to a Class<? extends AuditEventType> at runtime.
    SpEL predicate evaluated against the post-invocation context.
  • Element Details

    • value

      Class<? extends AuditEventType> value
      The concrete AuditEventType subclass to record. Must be instantiable via a public no-arg constructor.

      May be left at the default (AuditEventType.class — the abstract base) when valueSpel() chooses the event class at runtime. See Audited.value() for the parallel rule.

      Default:
      ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType.class
    • valueSpel

      String valueSpel
      Optional SpEL expression that resolves to a Class<? extends AuditEventType> at runtime. Mirrors Audited.valueSpel(): when set, it overrides value() (with a WARN if both are non-default). When evaluation fails or resolves to null/non-Class, the aspect falls back to value(); if value() is also at the default the audit row is skipped.
      Default:
      ""
    • when

      String when
      SpEL predicate evaluated against the post-invocation context. The audit row is emitted only when this expression evaluates to Boolean.TRUE.

      The evaluation context is identical to Audited.messageSpel(): method parameters resolve by name (e.g. #ee, #dimension) — requires -parameters compile flag, enabled project-wide in the parent pom; positional access via #root.args[i]; the method's return value is exposed as #result.

      The default "true" makes the annotation a strict superset of Audited (an always-fire conditional). Practical usage will always supply a non-trivial predicate.

      Default:
      "true"
    • message

      String message
      Optional plain (non-SpEL) note string. Stored verbatim in AUDIT_EVENT.NOTE. Empty default = no note. See Audited.message().
      Default:
      ""
    • messageSpel

      String messageSpel
      Optional SpEL expression for the note, evaluated only when when() is true. Semantics match Audited.messageSpel(): failure to evaluate falls back to message(); the audit row is still written. (The when predicate is the only gate on emission.)
      Default:
      ""