public record CausewayBeanTypeClassifier()

in core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayBeanTypeClassifier.java [47:199]


public record CausewayBeanTypeClassifier(
        @NonNull Can<String> activeProfiles,
        @NonNull _ClassCache classCache,
        @NonNull ContextType contextType) {

    public enum ContextType {
        SPRING,
        MOCKUP
    }

    // -- CONSTRUCTION

    CausewayBeanTypeClassifier(final ApplicationContext applicationContext) {
        this(Can.ofArray(applicationContext.getEnvironment().getActiveProfiles()), ContextType.SPRING);
    }

    CausewayBeanTypeClassifier(final Can<String> activeProfiles, final ContextType contextType) {
        this(activeProfiles, _ClassCache.getInstance(), contextType);
    }

    // -- CLASSIFY

    /**
     * Classify {@link LogicalType} as detected and named by either Causeway or Spring.
     * <p>
     * Typically Causeway we will use a different fallback naming strategy for 'unnamed' types,
     * that is, it uses the fully qualified class name.
     *
     * @param logicalType with name as either forced by Causeway or suggested by Spring
     */
    public CausewayBeanMetaData classify(final @NonNull LogicalType logicalType, final DiscoveredBy discoveredBy) {

        var type = logicalType.correspondingClass();

        Supplier<LogicalType> named = ()->discoveredBy.isSpring()
            ? LogicalType.infer(type) // use only if discovered by Spring but NOT managed by Spring
            : logicalType; // name is already inferred, when discovered by Causeway

        if(ClassUtils.isPrimitiveOrWrapper(type)
                || type.isEnum()) {
            return CausewayBeanMetaData.value(named.get(), discoveredBy);
        }

        if(CollectionSemantics.valueOf(type).isPresent()) {
            return CausewayBeanMetaData.collection(named.get(), discoveredBy);
        }

        if(type.isInterface()
                // modifier predicate must be called after testing for non-scalar type above,
                // otherwise we'd get false positives
                || Modifier.isAbstract(type.getModifiers())) {

            // apiNote: abstract types and interfaces cannot be vetoed
            // and should also never be identified as ENTITY, VIEWMODEL or MIXIN
            // however, concrete types that inherit abstract ones with vetoes,
            // will effectively be vetoed through means of annotation synthesis
            return CausewayBeanMetaData.interfaceOrAbstract(named.get(), discoveredBy);
        }

        var typeHead = classCache().head(type);

        // handle vetoing ...
        if(TypeVetoMarker.anyMatchOn(typeHead)) {
            return CausewayBeanMetaData.vetoed(named.get(), discoveredBy); // reject
        }

        var profiles = typeHead.springProfiles();
        if(profiles.isNotEmpty()
                && !profiles.stream().anyMatch(this::isProfileActive)) {
            return CausewayBeanMetaData.vetoed(named.get(), discoveredBy); // reject
        }

        // handle introspection veto (programmatic bean) ...
        if(TypeProgrammaticMarker.anyMatchOn(typeHead)) {
            if(contextType==ContextType.MOCKUP) return CausewayBeanMetaData.springNotContributing(logicalType);
            return switch (discoveredBy) {
                 case SPRING, CAUSEWAY_UPFRONT -> CausewayBeanMetaData.springNotContributing(logicalType);
                 case CAUSEWAY_ONTHEFLY -> CausewayBeanMetaData.programmatic(named.get());
            };
        }

        // when implements ViewModel, yield VIEW_MODEL unless vetoed
        if(org.apache.causeway.applib.ViewModel.class.isAssignableFrom(type)) {
            return CausewayBeanMetaData.viewModel(named.get(), discoveredBy);
        }

        // value types
        if(typeHead.hasAnnotation(org.apache.causeway.applib.annotation.Value.class)) {
            return CausewayBeanMetaData.value(named.get(), discoveredBy);
        }

        // domain service
        if(typeHead.hasAnnotation(DomainService.class)) {
            return CausewayBeanMetaData.springContributing(logicalType);
        }

        // entity support
        if(typeHead.isJdoPersistenceCapable()){
            return CausewayBeanMetaData.entity(named.get(), discoveredBy, PersistenceStack.JDO);
        }
        if(typeHead.hasAnnotation(Entity.class)) {
            return CausewayBeanMetaData.entity(named.get(), discoveredBy, PersistenceStack.JPA);
        }

        // domain object
        var aDomainObject = typeHead.annotation(DomainObject.class).orElse(null);
        if(aDomainObject!=null) {
            switch (aDomainObject.nature()) {
            case BEAN:
                return CausewayBeanMetaData.unspecified(named.get(), discoveredBy, BeanSort.MANAGED_BEAN_CONTRIBUTING);
            case MIXIN:
                // memoize mixin main name
                typeHead.attributeMap().put(_ClassCache.Attribute.MIXIN_MAIN_METHOD_NAME, aDomainObject.mixinMethod());
                return CausewayBeanMetaData.mixin(named.get(), discoveredBy);
            case ENTITY:
                return CausewayBeanMetaData.entity(named.get(), discoveredBy, PersistenceStack.UNSPECIFIED);
            case VIEW_MODEL:
            case NOT_SPECIFIED:
                //because object is not associated with a persistence context unless discovered above
                return CausewayBeanMetaData.viewModel(named.get(), discoveredBy);
            }
        }

        if(typeHead.hasJaxbRootElementSemantics()) {
            return CausewayBeanMetaData.viewModel(named.get(), discoveredBy);
        }

        if(typeHead.hasAnnotation(Component.class)) {
            return CausewayBeanMetaData.unspecified(logicalType, discoveredBy, BeanSort.MANAGED_BEAN_NOT_CONTRIBUTING);
        }

        // unless explicitly declared otherwise, map records to viewmodels
        if(type.isRecord()) {
            return CausewayBeanMetaData.unspecified(named.get(), discoveredBy, BeanSort.VIEW_MODEL);
        }

        if(Serializable.class.isAssignableFrom(type)) {
            return CausewayBeanMetaData.unspecified(named.get(), discoveredBy, BeanSort.VALUE);
        }

        return CausewayBeanMetaData.unspecified(named.get(), discoveredBy, BeanSort.UNKNOWN);
    }

    // -- HELPER

    /*TODO yet this is a naive implementation, not evaluating any expression logic like eg. @Profile("!dev")
      either we find a Spring Boot utility class that does this logic for us, or we make it clear with the
      docs, that we have only limited support for the @Profile annotation*/
    private boolean isProfileActive(final String profile) {
        return activeProfiles.contains(profile);
    }

}