content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *        http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   */
  package org.apache.causeway.applib.services.wrapper.control;
  
  import java.util.UUID;
  
  import org.jspecify.annotations.Nullable;
  
  import org.apache.causeway.applib.services.iactnlayer.InteractionContext;
  import org.apache.causeway.commons.collections.Can;
  import org.apache.causeway.schema.cmd.v2.CommandDto;
  
  import lombok.NonNull;
  
  /**
   * Controls the way that a (synchronous) wrapper works.
   *
   * @since 2.0 revised for 3.4 {@index}
   */
  public record SyncControl(
          /**
           * Skip checking business rules (hide/disable/validate) before
           * executing the underlying property or action
           */
          boolean isSkipRules,
          boolean isSkipExecute,
          /**
           * Get notified on action invocation or property change.
           */
          Can<CommandListener> commandListeners,
          /**
           * How to handle exceptions if they occur, using the provided
           * {@link ExceptionHandler}.
           *
           * <p>The default behaviour is to rethrow the exception.
           */
          ExceptionHandler exceptionHandler) {
  
      //TODO can this be further simplified, or is there already an API we can reuse?
      @FunctionalInterface
      public interface CommandListener {
          public void onCommand(
                  /**
                   * The unique {@link Command#getInteractionId() interactionId} of the parent {@link Command}, which is to say the
                   * {@link Command} that was active in the original interaction where
                   * {@link org.apache.causeway.applib.services.wrapper.WrapperFactory#asyncWrap(Object, AsyncControl)} (or its brethren)
                   * was called.
                   *
                   * <p>This can be useful for custom systems to link parent and child commands together.
                   */
                  UUID parentInteractionId,
                  InteractionContext interactionContext,
                  /**
                   * Details of the actual child command (action or property edit) to be performed.
                   *
                   * <p>Ultimately this can be handed onto the {@link org.apache.causeway.applib.services.command.CommandExecutorService}.
                   */
                  CommandDto commandDto);
      }
  
      public static SyncControl defaults() {
          return new SyncControl(false, false, null, null);
      }
  
      public SyncControl(
              boolean isSkipRules,
              boolean isSkipExecute,
              @Nullable Can<CommandListener> commandListeners,
              @Nullable ExceptionHandler exceptionHandler) {
          this.isSkipRules = isSkipRules;
          this.isSkipExecute = isSkipExecute;
          this.commandListeners = commandListeners!=null
                  ? commandListeners
                  : Can.empty();
          this.exceptionHandler = exceptionHandler!=null
                  ? exceptionHandler
                  : exception -> { throw exception; };
      }
  
      /**
       * Skip checking business rules (hide/disable/validate) before
       * executing the underlying property or action
       */
      public SyncControl withSkipRules() {
          return new SyncControl(true, isSkipExecute, commandListeners, exceptionHandler);
      }
      public SyncControl withCheckRules() {
          return new SyncControl(false, isSkipExecute, commandListeners, exceptionHandler);
      }
  
      /**
       * Explicitly set the action to be executed.
       */
      public SyncControl withExecute() {
          return new SyncControl(isSkipRules, false, commandListeners, exceptionHandler);
      }
      /**
       * Explicitly set the action to <i>not</i> be executed, in other words a 'dry run'.
       */
      public SyncControl withNoExecute() {
          return new SyncControl(isSkipRules, true, commandListeners, exceptionHandler);
      }
  
      public SyncControl listen(@NonNull CommandListener commandListener) {
          return new SyncControl(isSkipRules, isSkipExecute, commandListeners.add(commandListener), exceptionHandler);
      }
  
      /**
       * How to handle exceptions if they occur, using the provided {@link ExceptionHandler}.
       *
       * <p>The default behaviour is to rethrow the exception.
       */
      public SyncControl withExceptionHandler(final @NonNull ExceptionHandler exceptionHandler) {
          return new SyncControl(isSkipRules, isSkipExecute, commandListeners, exceptionHandler);
      }
  
      /**
       * @return whether this and other share the same execution mode, ignoring exceptionHandling
       */
      public boolean isEquivalent(SyncControl other) {
          return this.isSkipExecute == other.isSkipExecute
                  && this.isSkipRules == other.isSkipRules;
      }
  
  }
  