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.iactn;
  
  import java.sql.Timestamp;
  import java.util.Collections;
  import java.util.List;
  
  import org.jspecify.annotations.Nullable;
  
  import org.apache.causeway.applib.Identifier;
  import org.apache.causeway.applib.events.domain.AbstractDomainEvent;
  import org.apache.causeway.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
  import org.apache.causeway.applib.services.clock.ClockService;
  import org.apache.causeway.applib.services.eventbus.EventBusService;
  import org.apache.causeway.applib.services.metrics.MetricsService;
  import org.apache.causeway.applib.services.wrapper.WrapperFactory;
  import org.apache.causeway.applib.util.schema.MemberExecutionDtoUtils;
  import org.apache.causeway.commons.internal.collections._Lists;
  import org.apache.causeway.schema.common.v2.DifferenceDto;
  import org.apache.causeway.schema.common.v2.InteractionType;
  import org.apache.causeway.schema.common.v2.PeriodDto;
  import org.apache.causeway.schema.ixn.v2.MemberExecutionDto;
  import org.apache.causeway.schema.ixn.v2.MetricsDto;
  import org.apache.causeway.schema.ixn.v2.ObjectCountsDto;
  
  import lombok.Getter;
  
  /**
   * Represents an action invocation/property edit as a node in a call-stack
   * execution graph, with sub-interactions being made by way of the
   * {@link WrapperFactory}).
   *
   * <p>
   *     The {@link Interaction} has a reference to a {@link Interaction#getCurrentExecution() top-level} execution.
   * </p>
   *
   * @since 1.x {@index}
   */
  public abstract class Execution<T extends MemberExecutionDto, E extends AbstractDomainEvent<?>> {
  
      /**
       * The owning {@link Interaction}.
       */
      @Getter
      private final Interaction interaction;
  
      /**
       * Whether this is an
       * {@link InteractionType#ACTION_INVOCATION action invocation} or a
       * {@link InteractionType#PROPERTY_EDIT property edit}.
       */
      @Getter
      private final InteractionType interactionType;
  
      /**
       * Uniquely identifies the action or property.
       */
      @Getter
      private final Identifier logicalMemberIdentifier;
  
      /**
       * The target of the action invocation.
       *
       * <p>
       * If this interaction is for a mixin action, then will be the mixed-in
       * target (not the transient mixin itself).
       * </p>
       */
      @Getter
      private final Object target;
  
      /**
       * Captures metrics before the Execution Dto is present.
       */
      private int numberObjectsLoadedBefore;
      /**
       * Captures metrics before the Execution Dto is present.
       */
      private int numberObjectsDirtiedBefore;
  
      protected Execution(
              final Interaction interaction,
              final InteractionType interactionType,
              final Identifier logicalMemberIdentifier,
              final Object target) {
  
          this.interaction = interaction;
          this.interactionType = interactionType;
          this.logicalMemberIdentifier = logicalMemberIdentifier;
          this.target = target;
      }
  
      // -- parent, children
  
      private final List<Execution<?, ?>> children = _Lists.newArrayList();
  
      /**
       * The action/property that invoked this action/property edit (if any).
       */
      @Getter
      private Execution<?, ?> parent;
  
      /**
       * <b>NOT API</b>: intended to be called only by the framework.
       */
      public void setParent(final Execution<?, ?> parent) {
          this.parent = parent;
          if (parent != null) {
              parent.children.add(this);
          }
      }
  
      /**
       * The actions/property edits made in turn via the {@link WrapperFactory}.
       */
      public List<Execution<?, ?>> getChildren() {
  
          return Collections.unmodifiableList(children);
  
          // ...
      }
  
      /**
       * The domain event fired on the {@link EventBusService event bus} representing the execution of
       * this action invocation/property edit.
       *
       * <p>
       * This event field is set by the framework before the action invocation/property edit itself;
       * if read by the executing action/property edit method it will be in the
       * {@link AbstractDomainEvent.Phase#EXECUTING executing} phase.
       * </p>
       */
      private E event;
  
      public @Nullable E getEvent() {
          return event;
      }
  
      /**
       * <b>NOT API</b>: intended to be called only by the framework.
       */
      public void setEvent(final @Nullable E event) {
          this.event = event;
      }
  
      /**
       * The date/time at which this execution started.
       */
      @Getter
      private Timestamp startedAt;
  
      /**
       * The date/time at which this execution completed.
       */
      @Getter
      private Timestamp completedAt;
  
      public Timestamp start(
              final ClockService clockService,
              final MetricsService metricsService) {
  
          var startedAt = clockService.getClock().nowAsJavaSqlTimestamp();
          syncMetrics(When.BEFORE, startedAt, metricsService);
          return startedAt;
  
          // ...
      }
  
      /**
       * <b>NOT API</b>: intended to be called only by the framework.
       */
      public void setCompletedAt(
              final Timestamp completedAt,
              final MetricsService metricsService) {
          syncMetrics(When.AFTER, completedAt, metricsService);
      }
  
      /**
       * The object returned by the action invocation (for property edits, this
       * is always <tt>null</tt>).
       *
       * <p>
       * If the action returned either a domain entity or a simple value (and did not throw an
       * exception) then this object is provided here.
       *
       * <p>
       * For <tt>void</tt> methods and for actions returning collections, the value
       * will be <tt>null</tt>.
       * </p>
       *
       * <p>
       *     If the action threw an exception, then the object returned will also
       *     be <tt>null</tt>; the exception is instead captured in {@link #getThrew()}.
       * </p>
       */
      @Getter
      private Object returned;
  
      /**
       * <b>NOT API</b>: intended to be called only by the framework.
       */
      public void setReturned(final Object returned) {
          this.returned = returned;
      }
  
      /**
       * If a property edit or action invocation did not complete successfully
       * but instead threw an exception, then it is captured here.
       */
      @Getter
      private Exception threw;
  
      /**
       * <b>NOT API</b>: intended to be called only by the framework.
       */
      public void setThrew(final Exception threw) {
          this.threw = threw;
      }
  
      // -- dto (property)
  
      /**
       * A serializable representation of this action invocation/property edit.
       *
       * <p>
       * This <i>will</i> be populated (by the framework) during the method call itself (representing the
       * action invocation/property edit), though some fields ({@link Execution#getCompletedAt()},
       * {@link Execution#getReturned()}) will (obviously) still be null.
       * </p>
       */
      @Getter
      private T dto;
  
      /**
       * <b>NOT API</b>: Set by framework (implementation of
       * {@link org.apache.causeway.core.metamodel.execution.InternalInteraction.MemberExecutor})
       */
      public void setDto(final T executionDto) {
          this.dto = executionDto;
      }
  
      // -- helpers (syncMetrics)
  
      enum When {
          BEFORE {
              @Override
              void syncMetrics(
                      final Execution<?, ?> execution,
                      final Timestamp timestamp,
                      final int numberObjectsLoaded,
                      final int numberObjectsDirtied) {
  
                  execution.startedAt = timestamp;
                  execution.numberObjectsLoadedBefore = numberObjectsLoaded;
                  execution.numberObjectsDirtiedBefore = numberObjectsLoaded;
              }
  
              // ....
          },
          AFTER {
              @Override
              void syncMetrics(
                      final Execution<?, ?> execution,
                      final Timestamp timestamp,
                      final int numberObjectsLoaded,
                      final int numberObjectsDirtied) {
  
                  execution.completedAt = timestamp;
  
                  if(execution.dto==null) {
                      return;
                  }
                  final MetricsDto metricsDto = metricsFor(execution);
  
                  final PeriodDto periodDto = timingsFor(metricsDto);
                  periodDto.setStartedAt(JavaSqlXMLGregorianCalendarMarshalling.toXMLGregorianCalendar(execution.startedAt));
                  periodDto.setCompletedAt(JavaSqlXMLGregorianCalendarMarshalling.toXMLGregorianCalendar(execution.completedAt));
  
                  final ObjectCountsDto objectCountsDto = objectCountsFor(metricsDto);
                  numberObjectsLoadedFor(objectCountsDto).setBefore(execution.numberObjectsLoadedBefore);
                  numberObjectsDirtiedFor(objectCountsDto).setBefore(execution.numberObjectsDirtiedBefore);
  
                  numberObjectsLoadedFor(objectCountsDto).setAfter(numberObjectsLoaded);
                  numberObjectsDirtiedFor(objectCountsDto).setAfter(numberObjectsDirtied);
              }
  
              // ....
          };
  
          // -- helpers
  
          private static DifferenceDto numberObjectsDirtiedFor(final ObjectCountsDto objectCountsDto) {
              return MemberExecutionDtoUtils.numberObjectsDirtiedFor(objectCountsDto);
          }
  
          private static DifferenceDto numberObjectsLoadedFor(final ObjectCountsDto objectCountsDto) {
              return MemberExecutionDtoUtils.numberObjectsLoadedFor(objectCountsDto);
          }
  
          private static ObjectCountsDto objectCountsFor(final MetricsDto metricsDto) {
              return MemberExecutionDtoUtils.objectCountsFor(metricsDto);
          }
  
          private static MetricsDto metricsFor(final Execution<?, ?> execution) {
              return MemberExecutionDtoUtils.metricsFor(execution.dto);
          }
  
          private static PeriodDto timingsFor(final MetricsDto metricsDto) {
              return MemberExecutionDtoUtils.timingsFor(metricsDto);
          }
  
          abstract void syncMetrics(
                  final Execution<?, ?> teExecution,
                  final Timestamp timestamp,
                  final int numberObjectsLoaded,
                  final int numberObjectsDirtied);
      }
  
      private void syncMetrics(
              final When when,
              final Timestamp timestamp,
              final MetricsService metricsService) {
  
          final int numberObjectsLoaded = metricsService.numberEntitiesLoaded();
          final int numberObjectsDirtied = metricsService.numberEntitiesDirtied();
  
          when.syncMetrics(this, timestamp, numberObjectsLoaded, numberObjectsDirtied);
      }
  
  }
  