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.core.config.beans;
  
  import java.util.Objects;
  import java.util.stream.Stream;
  
  import jakarta.inject.Named;
  
  import org.springframework.beans.BeansException;
  import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  import org.springframework.context.ApplicationContext;
  import org.springframework.context.ApplicationContextAware;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.stereotype.Component;
  
  import org.apache.causeway.applib.annotation.DomainObject;
  import org.apache.causeway.applib.annotation.DomainService;
  import org.apache.causeway.commons.collections.Can;
  import org.apache.causeway.commons.internal.base._NullSafe;
  import org.apache.causeway.commons.internal.base._Timing;
  import org.apache.causeway.core.config.CausewayModuleCoreConfig;
  import org.apache.causeway.core.config.beans.CausewayBeanTypeClassifier.ContextType;
  
  import lombok.extern.log4j.Log4j2;
  
  /**
   * The framework's stereotypes {@link DomainService}, {@link DomainObject}, etc.
   * are meta annotated with eg. {@link Component}, which allows for the Spring framework to pick up the
   * annotated type as candidate to become a managed bean.
   * <p>
   * By plugging into Spring's bootstrapping via a {@link BeanFactoryPostProcessor}, intercepting those
   * types is possible. Eg. {@link DomainObject} should not be managed by Spring, only discovered.
   *
   * @since 2.0
   *
   */
  @Configuration(proxyBeanMethods = false)
  @Named(CausewayModuleCoreConfig.NAMESPACE + ".CausewayBeanFactoryPostProcessor")
  @Log4j2
  public class CausewayBeanFactoryPostProcessor
  implements
      BeanFactoryPostProcessor,
      ApplicationContextAware {
  
      private CausewayBeanTypeClassifier causewayBeanTypeClassifier;
      private CausewayBeanTypeRegistry componentScanResult = CausewayBeanTypeRegistry.empty();
  
      @Override
      public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
          this.causewayBeanTypeClassifier = new CausewayBeanTypeClassifier(applicationContext);
      }
  
      @Override
      public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
          var stopWatch = _Timing.now();
  
          // make sure we have an applicationContext before calling post processing
          Objects.requireNonNull(causewayBeanTypeClassifier,
                  "postProcessBeanFactory() called before app-ctx was made available");
  
          var registry = (BeanDefinitionRegistry) beanFactory;
          var beanDefNames = registry.getBeanDefinitionNames();
  
          TransactionInterceptorPatcher.setDefaultTransactionInterceptorToFallback(registry);
  
          var componentCollector = new CausewayComponentCollector(registry, causewayBeanTypeClassifier);
  
          Stream.of(beanDefNames)
              //.parallel()
              .forEach(componentCollector::collect);
  
          beanFactory.registerScope("causeway-domain-object", new CausewayDomainObjectScope(beanFactory));
  
          this.componentScanResult = new CausewayBeanTypeRegistry(
                  Can.ofCollection(componentCollector.introspectableTypes().values()));
  
          log.info("post processing {} bean definitions took {}ms", _NullSafe.size(beanDefNames), stopWatch.getMillis());
  
          if(log.isDebugEnabled()) {
              componentScanResult.streamScannedTypes()
                  .forEach(type->log.debug("discovered: {}", type));
          }
      }
  
      @Bean(name = CausewayModuleCoreConfig.NAMESPACE + ".CausewayBeanTypeClassifier")
      public CausewayBeanTypeClassifier getCausewayBeanTypeClassifier() {
          return causewayBeanTypeClassifier!=null
                  ? causewayBeanTypeClassifier
                  : (causewayBeanTypeClassifier = new CausewayBeanTypeClassifier(Can.empty(), ContextType.MOCKUP)); // JUnit support
      }
  
      @Bean(name = CausewayModuleCoreConfig.NAMESPACE + ".CausewayBeanTypeRegistry")
      public CausewayBeanTypeRegistry getComponentScanResult() {
          return componentScanResult;
      }
  
  }
  