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.extensions.secman.applib.role.fixtures;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.function.Supplier;
  
  import jakarta.inject.Inject;
  
  import org.apache.causeway.applib.services.appfeat.ApplicationFeatureId;
  import org.apache.causeway.applib.services.appfeat.ApplicationFeatureRepository;
  import org.apache.causeway.commons.collections.Can;
  import org.apache.causeway.core.config.CausewayConfiguration;
  import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionMode;
  import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionRepository;
  import org.apache.causeway.extensions.secman.applib.permission.dom.ApplicationPermissionRule;
  import org.apache.causeway.extensions.secman.applib.role.dom.ApplicationRoleRepository;
  import org.apache.causeway.testing.fixtures.applib.fixturescripts.FixtureScript;
  import org.apache.causeway.testing.fixtures.applib.fixturescripts.FixtureScriptWithExecutionStrategy;
  import org.apache.causeway.testing.fixtures.applib.fixturescripts.FixtureScripts;
  
  /**
   * Convenience fixture script intended to be easily subclassed in order to set up an
   * {@link org.apache.causeway.extensions.secman.applib.role.dom.ApplicationRole} with associated permissions.
   *
   * @since 2.x {@index}
   */
  public abstract class AbstractRoleAndPermissionsFixtureScript
  extends FixtureScript
  implements FixtureScriptWithExecutionStrategy {
  
      @Inject private ApplicationRoleRepository applicationRoleRepository;
      @Inject private ApplicationPermissionRepository applicationPermissionRepository;
      @Inject private ApplicationFeatureRepository applicationFeatureRepository;
      @Inject private CausewayConfiguration causewayConfiguration;
  
      private final Supplier<String> roleNameSupplier;
      private final Supplier<String> roleDescriptionSupplier;
  
      protected AbstractRoleAndPermissionsFixtureScript(
              final String roleName,
              final String roleDescriptionIfAny) {
          this(() -> roleName, () -> roleDescriptionIfAny);
      }
  
      protected AbstractRoleAndPermissionsFixtureScript(
              final Supplier<String> roleNameSupplier,
              final Supplier<String> roleDescriptionSupplier) {
          this.roleNameSupplier = nullSafe(roleNameSupplier);
          this.roleDescriptionSupplier = nullSafe(roleDescriptionSupplier);
      }
  
      @Override
      public FixtureScripts.MultipleExecutionStrategy getMultipleExecutionStrategy() {
          return null;
      }
  
      protected final String getRoleName() {
          return roleNameSupplier.get();
      }
  
      protected final String getRoleDescription() {
          return roleDescriptionSupplier.get();
      }
  
      /**
       * Subclasses should override and call
       * {@link #newPermissions(ApplicationPermissionRule, ApplicationPermissionMode, Can)}
       */
      @Override
      protected abstract void execute(ExecutionContext executionContext);
  
      /**
       * For subclasses to call in {@link #execute(FixtureScript.ExecutionContext)}.
       */
      protected void newPermissions(
              final ApplicationPermissionRule rule,
              final ApplicationPermissionMode mode,
              final Can<ApplicationFeatureId> featureIds) {
  
          if(featureIds == null || featureIds.isEmpty()) {
              return;
          }
  
          final List<ApplicationFeatureId> missingFeatureIds = new ArrayList<>();
  
          for(var featureId : featureIds) {
              var feature = applicationFeatureRepository.findFeature(featureId);
              if (feature == null) {
                  missingFeatureIds.add(featureId);
              }
          }
  
          if (causewayConfiguration.getExtensions().getSecman().getFixtureScripts().getAbstractRoleAndPermissionsFixtureScript().getUnknownFeatureIdCheckingPolicy().isFailFast()) {
              // ensure all featureIds specified actually exist.
              var buf = new StringBuilder();
              for(var featureId : missingFeatureIds) {
                  buf.append("- ").append(featureId.getFullyQualifiedName()).append("\n");
              }
              throw new IllegalArgumentException(String.format("No such feature(s):\n%s", buf));
          }
  
          var roleName = getRoleName();
          var securityRole = applicationRoleRepository.findByName(roleName)
                  .orElseGet(() -> applicationRoleRepository.newRole(roleName, getRoleDescription()));
  
          for(var featureId : featureIds) {
              var featureFqn = featureId.getFullyQualifiedName();
  
              // can't use role#addPackage because that does a check for existence of the package, which is
              // not guaranteed to exist yet (the SecurityFeatures#init() may not have run).
              applicationPermissionRepository
              .newPermissionNoCheck(
                      securityRole,
                      rule,
                      mode,
                      featureId.getSort(),
                      featureFqn);
          }
      }
  
      private static <T> Supplier<T> nullSafe(final Supplier<T> supplier) {
          return supplier != null ? supplier : () -> null;
      }
  
  }
  