Class BaseBehavior

java.lang.Object
dev.agenor.runtime.behavior.BaseBehavior
All Implemented Interfaces:
Behavior
Direct Known Subclasses:
CyclicBehavior, HumanCheckpointBehavior, OneShotBehavior

public abstract class BaseBehavior extends Object implements Behavior
Base implementation of Behavior interface. Provides common functionality for all behaviors.
  • Field Details

    • log

      protected final org.slf4j.Logger log
  • Constructor Details

  • Method Details

    • getBehaviorId

      public String getBehaviorId()
      Description copied from interface: Behavior
      Returns the unique identifier for this behavior.

      The behavior ID must be unique within the owning agent's scope. It is used for behavior management operations such as removal and cancellation.

      Uniqueness: Two behaviors in the same agent cannot have the same ID. Attempting to add a behavior with a duplicate ID may either replace the existing behavior or fail, depending on implementation.

      Naming Convention: Use descriptive IDs that indicate the behavior's purpose:

      • Good: "health-monitor", "order-processor", "data-collector"
      • Avoid: "behavior1", "temp", "test"
      Specified by:
      getBehaviorId in interface Behavior
      Returns:
      the behavior's unique identifier, never null or empty
      See Also:
    • getAgent

      public Agent getAgent()
      Description copied from interface: Behavior
      Returns the agent that owns this behavior.

      Every behavior belongs to exactly one agent. This relationship is established when the behavior is added to the agent via Agent.addBehavior(Behavior) and provides the behavior with:

      • Access to the agent's MessageDispatcher
      • Agent identity for logging and monitoring
      • Context for behavior execution

      Lifecycle: The agent reference is typically set when the behavior is added to the agent and remains constant for the behavior's lifetime.

      Null Safety: May return null if the behavior has not yet been added to an agent. Implementations should handle this case gracefully.

      Specified by:
      getAgent in interface Behavior
      Returns:
      the owning agent, or null if not yet associated with an agent
      See Also:
    • getType

      public BehaviorType getType()
      Description copied from interface: Behavior
      Returns the type of this behavior.

      The behavior type determines how the BehaviorScheduler manages its execution. Different types have different scheduling strategies and lifecycle patterns.

      Type Impact on Scheduling:

      Behavior scheduling characteristics by type
      Type Scheduling Interval Required
      ONE_SHOT Executed once immediately No
      CYCLIC Repeated at fixed intervals Yes - via Behavior.getInterval()
      EVENT_DRIVEN Not scheduled, responds to events No
      WAKER Custom wake-up logic No

      Type Immutability: The behavior type is typically set at construction time and should not change during the behavior's lifetime. This allows the scheduler to make optimization decisions based on the type.

      Behaviors that do not fit a standard pattern: answer with the type whose scheduling semantics you want. There is no constant meaning "none of these" — a behavior that runs on an interval is BehaviorType.CYCLIC and one that runs once is BehaviorType.ONE_SHOT, however it is implemented internally. A composite expresses what the type alone cannot through CompositeBehavior.getSchedulingHint(), which is what the scheduler inspects for it.

      Specified by:
      getType in interface Behavior
      Returns:
      the behavior type, never null
      See Also:
    • getInterval

      public Duration getInterval()
      Description copied from interface: Behavior
      Returns the execution interval for cyclic behaviors.

      The interval specifies how much time should elapse between consecutive executions of a CYCLIC behavior. This value is used by the scheduler to determine the fixed rate or fixed delay for repeated execution.

      Applicability: This method is primarily relevant for:

      • CYCLIC - Required, must not be null
      • Other types - Typically returns null (interval not applicable)

      Scheduling Semantics: The interval represents the time between the start of consecutive executions (fixed rate), not between the end of one and start of the next:

       Time: 0s        10s       20s       30s
             |---------|---------|---------|
             Execute   Execute   Execute   Execute
      
       Interval = 10 seconds (fixed rate)
       

      Choosing Intervals:

      • Short intervals (< 1s) - Polling, real-time monitoring. Use cautiously to avoid scheduler overhead.
      • Medium intervals (1s-1m) - Regular checks, health monitoring, periodic data collection.
      • Long intervals (> 1m) - Cleanup tasks, batch processing, low-priority maintenance.

      Performance Considerations:

      • Ensure execution time is less than the interval to prevent overlap
      • Very short intervals can saturate the scheduler thread pool
      • Consider using event-driven behaviors for immediate responses

      Example:

      
       // Health check every 30 seconds
       Behavior healthCheck = new BaseBehavior(
           BehaviorType.CYCLIC,
           Duration.ofSeconds(30)
       ) {
           @Override
           public CompletableFuture<Void> execute() {
               return CompletableFuture.runAsync(() -> {
                   checkSystemHealth();
               });
           }
       };
      
       // Data collection every 5 minutes
       Behavior dataCollector = new BaseBehavior(
           BehaviorType.CYCLIC,
           Duration.ofMinutes(5)
       ) {
           @Override
           public CompletableFuture<Void> execute() {
               return CompletableFuture.runAsync(() -> {
                   collectAndStoreData();
               });
           }
       };
       

      Validation: Schedulers should validate that:

      • CYCLIC behaviors have a non-null interval
      • Interval is positive and reasonable (e.g., > 1ms)
      Specified by:
      getInterval in interface Behavior
      Returns:
      the execution interval for cyclic behaviors, or null for non-cyclic behaviors or when interval is not applicable
      See Also:
    • isActive

      public boolean isActive()
      Description copied from interface: Behavior
      Checks whether this behavior should continue running.

      A behavior is considered active if it has not been explicitly stopped and is eligible for execution. The scheduler uses this flag to determine whether to continue scheduling the behavior.

      Active State Semantics:

      • true - Behavior can execute and should be scheduled
      • false - Behavior should not execute and may be unscheduled

      Transition to Inactive: A behavior becomes inactive when:

      • Behavior.stop() is called explicitly
      • One-shot behaviors complete their execution
      • The owning agent is stopped
      • Internal logic determines the behavior should end

      Usage in Execute: Always check this flag at the beginning of Behavior.execute():

      
       public CompletableFuture<Void> execute() {
           if (!isActive()) {
               return CompletableFuture.completedFuture(null);
           }
           // Behavior logic...
       }
       

      For Long-Running Operations: Check the active flag periodically during execution to enable responsive cancellation:

      
       while (isActive() && hasMoreWork()) {
           processNextItem();
       }
       

      Thread Safety: This method must be thread-safe as it may be called concurrently with Behavior.stop() from different threads.

      Specified by:
      isActive in interface Behavior
      Returns:
      true if the behavior is active and should continue executing, false if the behavior has been stopped
      See Also:
    • stop

      public void stop()
      Description copied from interface: Behavior
      Stops this behavior, preventing further execution.

      This method transitions the behavior to an inactive state. After calling stop(), Behavior.isActive() will return false and the behavior will no longer be executed by the scheduler.

      Stopping Semantics:

      • Immediate Effect - The behavior is marked inactive immediately
      • Graceful - Current execution completes naturally
      • Idempotent - Multiple calls to stop() are safe
      • Permanent - Stopped behaviors cannot be restarted (create a new instance instead)

      Scheduler Integration: When a behavior is stopped:

      1. The active flag is set to false
      2. The scheduler is notified to cancel future executions
      3. Current execution (if any) continues to completion
      4. No new executions are scheduled

      Resource Cleanup: Implementations should use this method to clean up resources:

      
       @Override
       public void stop() {
           super.stop();  // Mark inactive
      
           // Clean up resources
           if (connection != null) {
               connection.close();
           }
      
           if (executor != null) {
               executor.shutdown();
           }
       }
       

      Composite Behaviors: For composite behaviors (SEQUENTIAL, PARALLEL, FSM), stopping the parent should propagate to all child behaviors:

      
       @Override
       public void stop() {
           super.stop();
           childBehaviors.forEach(Behavior::stop);
       }
       

      Thread Safety: This method must be thread-safe and may be called:

      • While the behavior is executing
      • From a different thread than execution
      • Multiple times concurrently

      Agent Lifecycle: When an agent stops, it calls stop() on all its behaviors. There is no need to explicitly stop behaviors during agent shutdown.

      Specified by:
      stop in interface Behavior
      See Also:
    • activate

      public boolean activate()
      Reactivates a stopped behavior, allowing it to be scheduled again.

      This method is called by the agent when restarting after a stop. It resets the active flag to true, enabling the behavior to execute.

      Returns:
      true if the behavior was reactivated, false if already active
      Since:
      0.4.0
    • execute

      public CompletableFuture<Void> execute()
      Description copied from interface: Behavior
      Executes this behavior once, asynchronously.

      This method performs a single execution cycle of the behavior's logic. The actual execution pattern (one-shot, cyclic, etc.) is determined by the BehaviorType and managed by the BehaviorScheduler.

      Asynchronous Execution: This method is non-blocking and returns immediately with a CompletableFuture that completes when the behavior's work is done. This enables:

      • Parallel execution of multiple behaviors
      • Non-blocking agent operations
      • Compositional behavior chains
      • Centralized error handling

      Active State Check: Implementations should check Behavior.isActive() before performing work and return early if the behavior has been stopped:

      
       public CompletableFuture<Void> execute() {
           if (!isActive()) {
               return CompletableFuture.completedFuture(null);
           }
           // Perform behavior logic...
       }
       

      Error Handling: Exceptions thrown during execution should be handled within the behavior or propagated via the returned future. Uncaught exceptions may:

      • Stop cyclic behavior execution
      • Mark the behavior as failed
      • Trigger agent error handlers

      Thread Safety: This method may be called concurrently from different threads, especially for event-driven behaviors. Implementations must be thread-safe.

      Performance: Keep execution time reasonable, especially for cyclic behaviors. Long-running operations should be delegated to separate threads or use async APIs.

      Example:

      
       @Override
       public CompletableFuture<Void> execute() {
           return CompletableFuture.supplyAsync(() -> {
               if (!isActive()) return null;
      
               try {
                   // Perform behavior work
                   Data data = collectData();
                   processData(data);
      
                   // Send results
                   Message result = Message.builder()
                           .topic("data.processed")
                           .content(data)
                           .build();
                   agent.getMessageDispatcher().publish("data.processed", result);
      
                   return null;
      
               } catch (Exception e) {
                   log.error("Behavior execution failed", e);
                   throw new CompletionException(e);
               }
           });
       }
       
      Specified by:
      execute in interface Behavior
      Returns:
      a CompletableFuture that completes when execution is finished, or completes exceptionally if execution fails
      See Also:
    • setAgent

      public void setAgent(Agent agent)
      Set the owning agent for this behavior
    • action

      protected abstract void action()
      The main action to be performed by this behavior. Must be implemented by subclasses.
    • onStop

      protected void onStop()
      Called when the behavior is stopped. Override for cleanup logic.
    • onError

      protected void onError(Exception error)
      Called when an error occurs during execution. Override for custom error handling.