This is an automated email from the ASF dual-hosted git repository. ahuber pushed a commit to branch 3963-dbg.spec.trigger in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 61d517789bfbb9b84164986ec0241847c38700e0 Author: andi-huber <[email protected]> AuthorDate: Sun Feb 8 10:54:58 2026 +0100 CAUSEWAY-3963: For every Type introspected, keep track of what triggered the Introspection Task-Url: https://issues.apache.org/jira/browse/CAUSEWAY-3963 --- .../core/metamodel/spec/IntrospectionTrigger.java | 61 +++++++++++++++++ .../core/metamodel/spec/ObjectSpecification.java | 7 ++ .../metamodel/spec/impl/FacetedMethodsBuilder.java | 5 +- .../spec/impl/ObjectSpecificationDefault.java | 15 ++-- .../spec/impl/ObjectSpecificationMutable.java | 3 +- .../spec/impl/SpecificationLoaderDefault.java | 79 ++++++++++++---------- .../spec/impl/SpecificationLoaderInternal.java | 19 ++++-- 7 files changed, 140 insertions(+), 49 deletions(-) diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/IntrospectionTrigger.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/IntrospectionTrigger.java new file mode 100644 index 00000000000..53d0c682ab2 --- /dev/null +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/IntrospectionTrigger.java @@ -0,0 +1,61 @@ +/* + * 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.metamodel.spec; + +import java.util.Optional; + +import org.apache.causeway.core.config.beans.CausewayBeanTypeRegistry; + +/** + * Some times types end up in the meta-model unexpected. + * The {@link IntrospectionTrigger} helps find the cause for why a specific type got introspected. + * + * @since 4.0 + */ +public record IntrospectionTrigger( + IntrospectionCause introspectionCause, + Optional<ObjectSpecification> triggerSpec) { + + public enum IntrospectionCause { + /** + * originating directly from {@link CausewayBeanTypeRegistry} + */ + BEAN_CANDIDATE, + TYPE_HIERARCHY, + PROPERTY, + ACTION_RETURN, + ACTION_PARAM + } + + private final static IntrospectionTrigger BEAN_CANDIDATE = new IntrospectionTrigger( + IntrospectionCause.BEAN_CANDIDATE, Optional.empty()); + + public static IntrospectionTrigger beanCandidate() { + return BEAN_CANDIDATE; + } + + /** + * @deprecated refactoring helper + */ + @Deprecated + public static IntrospectionTrigger dummy() { + return BEAN_CANDIDATE; + } + +} diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java index 22e37e99899..b6aa801e2ca 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/ObjectSpecification.java @@ -116,6 +116,13 @@ class Comparators{ } IntrospectionPolicy getIntrospectionPolicy(); + + /** + * Introduced for debugging. + * @see IntrospectionTrigger + * @since 4.0 + */ + IntrospectionTrigger introspectionTrigger(); /** * Natural order, that is, by {@link BeanSort} then by {@link LogicalType}. diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/FacetedMethodsBuilder.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/FacetedMethodsBuilder.java index c4be6bfda46..3054a2c2097 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/FacetedMethodsBuilder.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/FacetedMethodsBuilder.java @@ -54,6 +54,7 @@ import org.apache.causeway.core.metamodel.facets.actcoll.typeof.TypeOfFacet; import org.apache.causeway.core.metamodel.facets.object.mixin.MixinFacet; import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutorRegistry; +import org.apache.causeway.core.metamodel.spec.IntrospectionTrigger; import org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationMutable.IntrospectionRequest; import org.apache.causeway.core.metamodel.specloader.typeextract.TypeExtractor; @@ -195,7 +196,7 @@ private List<FacetedMethod> createAssociationFacetedMethods() { // Ensure all return types are known TypeExtractor.streamMethodReturn(associationCandidateMethods) .filter(typeToLoad->typeToLoad!=introspectedClass) - .forEach(typeToLoad->specLoader.loadSpecification(typeToLoad, IntrospectionRequest.TYPE_ONLY)); + .forEach(typeToLoad->specLoader.loadSpecification(typeToLoad, IntrospectionRequest.TYPE_ONLY, IntrospectionTrigger.dummy())); // now create FacetedMethods for collections and for properties var associationFacetedMethods = new ArrayList<FacetedMethod>(); @@ -376,7 +377,7 @@ private boolean representsAction(final ResolvedMethod actionMethod) { // ensure we can load returned element type; otherwise ignore method var anyLoadedAsNull = TypeExtractor.streamMethodReturn(actionMethod) - .map(typeToLoad->specLoaderInternal().loadSpecification(typeToLoad, IntrospectionRequest.TYPE_ONLY)) + .map(typeToLoad->specLoaderInternal().loadSpecification(typeToLoad, IntrospectionRequest.TYPE_ONLY, IntrospectionTrigger.dummy())) .anyMatch(Objects::isNull); if (anyLoadedAsNull) { return false; diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java index 6cd28f22706..3bcd2787f3e 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationDefault.java @@ -104,6 +104,7 @@ import org.apache.causeway.core.metamodel.object.ManagedObjects; import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutorRegistry; import org.apache.causeway.core.metamodel.spec.ActionScope; +import org.apache.causeway.core.metamodel.spec.IntrospectionTrigger; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.MixedIn; import org.apache.causeway.core.metamodel.spec.feature.ObjectAction; @@ -115,6 +116,7 @@ import static org.apache.causeway.commons.internal.base._NullSafe.stream; import lombok.Getter; +import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -133,19 +135,24 @@ final class ObjectSpecificationDefault @Getter(onMethod_={@Override}) private final IntrospectionPolicy introspectionPolicy; + + @Getter(onMethod_={@Override}) @Accessors(fluent = true) + private final IntrospectionTrigger introspectionTrigger; public ObjectSpecificationDefault( final @NonNull CausewayBeanMetaData typeMeta, final @NonNull MetaModelContext mmc, final @NonNull FacetProcessor facetProcessor, final @NonNull PostProcessor postProcessor, - final @NonNull ClassSubstitutorRegistry classSubstitutorRegistry) { + final @NonNull ClassSubstitutorRegistry classSubstitutorRegistry, + final @NonNull IntrospectionTrigger introspectionTrigger) { this.correspondingClass = typeMeta.getCorrespondingClass(); this.logicalType = typeMeta.logicalType(); this.fullName = correspondingClass.getName(); this.shortName = typeMeta.logicalType().logicalSimpleName(); this.beanSort = typeMeta.beanSort(); + this.introspectionTrigger = introspectionTrigger; this.facetHolder = FacetHolder.simple( facetProcessor.getMetaModelContext(), @@ -555,7 +562,7 @@ public final String getFullIdentifier() { } @Override - public void introspect(IntrospectionRequest request) { + public void introspect(IntrospectionRequest request, IntrospectionTrigger introspectionTrigger) { switch (request) { case REGISTER -> introspectUpTo(IntrospectionState.NOT_INTROSPECTED, ()->"introspect(%s)".formatted(request)); @@ -1070,7 +1077,7 @@ private Stream<ObjectAssociation> createMixedInAssociations() { private Stream<ObjectAssociation> createMixedInAssociation(final Class<?> mixinType) { var mixinSpec = specLoaderInternal().loadSpecification(mixinType, - IntrospectionRequest.FULL); + IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); if (mixinSpec == null || mixinSpec == this) { return Stream.empty(); @@ -1104,7 +1111,7 @@ private Stream<ObjectActionMixedIn> createMixedInActions() { private Stream<ObjectActionMixedIn> createMixedInAction(final Class<?> mixinType) { var mixinSpec = specLoaderInternal().loadSpecification(mixinType, - IntrospectionRequest.FULL); + IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); if (mixinSpec == null || mixinSpec == this) { return Stream.empty(); diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationMutable.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationMutable.java index 68f625db7be..7f8ad48053c 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationMutable.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/ObjectSpecificationMutable.java @@ -18,6 +18,7 @@ */ package org.apache.causeway.core.metamodel.spec.impl; +import org.apache.causeway.core.metamodel.spec.IntrospectionTrigger; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; interface ObjectSpecificationMutable extends ObjectSpecification { @@ -37,6 +38,6 @@ enum IntrospectionRequest { FULL } - void introspect(IntrospectionRequest request); + void introspect(IntrospectionRequest request, IntrospectionTrigger introspectionTrigger); } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/SpecificationLoaderDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/SpecificationLoaderDefault.java index be0a24ebda4..f05d4135fe1 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/SpecificationLoaderDefault.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/SpecificationLoaderDefault.java @@ -78,6 +78,7 @@ import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutor; import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutor.Substitution; import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutorRegistry; +import org.apache.causeway.core.metamodel.spec.IntrospectionTrigger; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.ObjectAction; import org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationMutable.IntrospectionRequest; @@ -265,25 +266,25 @@ public void createMetaModel() { .concat( valueTypesFromProviders.stream(), causewayBeanTypeRegistry.streamScannedTypes()) - // prime (up to NOT_INTROSPECTED) - .map(this::primeSpecification) + // register only (no type-hierarchy nor member introspection) + .map(it->this.registerSpecification(it, IntrospectionTrigger.beanCandidate())) .forEach(specs::collect); - introspectAndLog("type hierarchies", specs.knownSpecs, IntrospectionRequest.TYPE_ONLY); - introspectAndLog("value types", specs.valueSpecs.values(), IntrospectionRequest.FULL); - introspectAndLog("mixins", specs.mixinSpecs, IntrospectionRequest.FULL); - introspectAndLog("domain services", specs.domainServiceSpecs, IntrospectionRequest.FULL); + introspectAndLog("type hierarchies", specs.knownSpecs, IntrospectionRequest.TYPE_ONLY, IntrospectionTrigger.dummy()); + introspectAndLog("value types", specs.valueSpecs.values(), IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); + introspectAndLog("mixins", specs.mixinSpecs, IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); + introspectAndLog("domain services", specs.domainServiceSpecs, IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); introspectAndLog("entities (%s)".formatted(causewayBeanTypeRegistry.persistenceStack().name()), - specs.entitySpecs(), IntrospectionRequest.FULL); - introspectAndLog("view models", specs.viewmodelSpecs(), IntrospectionRequest.FULL); + specs.entitySpecs(), IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); + introspectAndLog("view models", specs.viewmodelSpecs(), IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); serviceRegistry.lookupServiceElseFail(MenuBarsService.class).menuBars(); if(isFullIntrospect()) { var snapshot = snapshotSpecifications(); log.info(" - introspecting all {} types eagerly (FullIntrospect=true)", snapshot.size()); - introspect(snapshot.filter(x->x.getBeanSort().isMixin()), IntrospectionRequest.FULL); - introspect(snapshot.filter(x->!x.getBeanSort().isMixin()), IntrospectionRequest.FULL); + introspect(snapshot.filter(x->x.getBeanSort().isMixin()), IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); + introspect(snapshot.filter(x->!x.getBeanSort().isMixin()), IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); } log.info(" - running remaining validators"); @@ -355,7 +356,7 @@ private boolean isFullIntrospect() { @Override public void reloadSpecification(final Class<?> domainType) { invalidateCache(domainType); - loadSpecification(domainType, IntrospectionRequest.FULL); + loadSpecification(domainType, IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); } @Override @@ -381,8 +382,9 @@ public boolean loadSpecifications(final Class<?>... domainTypes) { @Override public ObjectSpecification loadSpecification( final @Nullable Class<?> type, - final @NonNull IntrospectionRequest request) { - return loadSpecificationNullable(type, this::classify, request); + final @NonNull IntrospectionRequest request, + final @NonNull IntrospectionTrigger introspectionTrigger) { + return loadSpecificationNullable(type, this::classify, request, introspectionTrigger); } @Override @@ -549,10 +551,11 @@ private CausewayBeanMetaData classify(final @Nullable Class<?> type) { } @Nullable - private ObjectSpecificationMutable primeSpecification( - final @NonNull CausewayBeanMetaData typeMeta) { + private ObjectSpecificationMutable registerSpecification( + final @NonNull CausewayBeanMetaData typeMeta, + final @NonNull IntrospectionTrigger introspectionTrigger) { return loadSpecificationNullable( - typeMeta.getCorrespondingClass(), type->typeMeta, IntrospectionRequest.REGISTER); + typeMeta.getCorrespondingClass(), type->typeMeta, IntrospectionRequest.REGISTER, introspectionTrigger); } @@ -560,7 +563,8 @@ private ObjectSpecificationMutable primeSpecification( private ObjectSpecificationMutable loadSpecificationNullable( final @Nullable Class<?> type, final @NonNull Function<Class<?>, CausewayBeanMetaData> beanClassifier, - final @NonNull IntrospectionRequest request) { + final @NonNull IntrospectionRequest request, + final @NonNull IntrospectionTrigger introspectionTrigger) { if(type==null) return null; @@ -572,9 +576,9 @@ private ObjectSpecificationMutable loadSpecificationNullable( var spec = cache.computeIfAbsent(substitutedType, _spec-> logicalTypeResolver .register( - createSpecification(beanClassifier.apply(substitutedType)))); + createSpecification(beanClassifier.apply(substitutedType), introspectionTrigger))); - spec.introspect(request); + spec.introspect(request, introspectionTrigger); if(spec.getAliases().isNotEmpty() // this bool. expr. is an optimization, not strictly required ... a bit of hack though @@ -597,32 +601,35 @@ private ObjectSpecificationMutable loadSpecificationNullable( /** * Creates the appropriate type of {@link ObjectSpecification}. + * @param introspectionTrigger */ - private ObjectSpecificationMutable createSpecification(final CausewayBeanMetaData typeMeta) { + private ObjectSpecificationMutable createSpecification( + final CausewayBeanMetaData typeMeta, + final IntrospectionTrigger introspectionTrigger) { var objectSpec = new ObjectSpecificationDefault( - typeMeta, - metaModelContext, - facetProcessor, - postProcessor, - classSubstitutorRegistry); + typeMeta, metaModelContext, + facetProcessor, postProcessor, + classSubstitutorRegistry, introspectionTrigger); return objectSpec; } private void introspectSequential( final Can<ObjectSpecificationMutable> specs, - final IntrospectionRequest request) { + final IntrospectionRequest request, + final IntrospectionTrigger introspectionTrigger) { for (var spec : specs) { - spec.introspect(request); + spec.introspect(request, introspectionTrigger); } } private void introspectParallel( final Can<ObjectSpecificationMutable> specs, - final IntrospectionRequest request) { + final IntrospectionRequest request, + final IntrospectionTrigger introspectionTrigger) { specs.parallelStream() .forEach(spec -> { try { - spec.introspect(request); + spec.introspect(request, introspectionTrigger); } catch (Throwable ex) { log.error("failure", ex); throw ex; @@ -633,20 +640,22 @@ private void introspectParallel( private void introspectAndLog( final String info, final Iterable<ObjectSpecificationMutable> specs, - final IntrospectionRequest request) { + final IntrospectionRequest request, + final IntrospectionTrigger introspectionTrigger) { var stopWatch = _Timing.now(); - introspect(Can.ofIterable(specs), request); + introspect(Can.ofIterable(specs), request, introspectionTrigger); stopWatch.stop(); log.info(" - introspecting {} {} took {}ms", _NullSafe.sizeAutodetect(specs), info, stopWatch.getMillis()); } private void introspect( final Can<ObjectSpecificationMutable> specs, - final IntrospectionRequest request) { + final IntrospectionRequest request, + final IntrospectionTrigger introspectionTrigger) { if(parallel) { - introspectParallel(specs, request); + introspectParallel(specs, request, introspectionTrigger); } else { - introspectSequential(specs, request); + introspectSequential(specs, request, introspectionTrigger); } } @@ -655,7 +664,7 @@ private void invalidateCache(final Class<?> cls) { if(substitute.isNeverIntrospect()) return; var objSpec = - loadSpecification(substitute.apply(cls), IntrospectionRequest.FULL); + loadSpecification(substitute.apply(cls), IntrospectionRequest.FULL, IntrospectionTrigger.dummy()); while(objSpec != null) { var type = objSpec.getCorrespondingClass(); diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/SpecificationLoaderInternal.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/SpecificationLoaderInternal.java index 47235a2515e..4a47aea794c 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/SpecificationLoaderInternal.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/SpecificationLoaderInternal.java @@ -32,6 +32,7 @@ import org.apache.causeway.commons.internal.base._Strings; import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutor; +import org.apache.causeway.core.metamodel.spec.IntrospectionTrigger; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationMutable.IntrospectionRequest; import org.apache.causeway.core.metamodel.specloader.SpecificationLoader; @@ -47,7 +48,10 @@ interface SpecificationLoaderInternal extends SpecificationLoader { * @return {@code null} if {@code domainType==null}, or if the type should be ignored. */ @Nullable - ObjectSpecification loadSpecification(@Nullable Class<?> domainType, @NonNull IntrospectionRequest request); + ObjectSpecification loadSpecification( + @Nullable Class<?> domainType, + @NonNull IntrospectionRequest request, + @NonNull IntrospectionTrigger introspectionTrigger); // -- SUPPORT FOR LOOKUP BY LOGICAL TYPE NAME @@ -59,14 +63,15 @@ interface SpecificationLoaderInternal extends SpecificationLoader { @Nullable default ObjectSpecification loadSpecification( final @Nullable String logicalTypeName, - final @NonNull IntrospectionRequest request) { + final @NonNull IntrospectionRequest request, + final @NonNull IntrospectionTrigger introspectionTrigger) { if(_Strings.isNullOrEmpty(logicalTypeName)) { return null; } return lookupLogicalType(logicalTypeName) .map(logicalType-> - loadSpecification(logicalType.correspondingClass(), request)) + loadSpecification(logicalType.correspondingClass(), request, introspectionTrigger)) .orElse(null); } @@ -76,7 +81,7 @@ default ObjectSpecification loadSpecification( default Optional<ObjectSpecification> specForLogicalTypeName( final @Nullable String logicalTypeName) { return Optional.ofNullable( - loadSpecification(logicalTypeName, IntrospectionRequest.FULL)); + loadSpecification(logicalTypeName, IntrospectionRequest.FULL, IntrospectionTrigger.dummy())); } @Override @@ -91,7 +96,7 @@ default Optional<ObjectSpecification> specForLogicalType( default Optional<ObjectSpecification> specForType( final @Nullable Class<?> domainType) { return Optional.ofNullable( - loadSpecification(domainType, IntrospectionRequest.FULL)); + loadSpecification(domainType, IntrospectionRequest.FULL, IntrospectionTrigger.dummy())); } @Override @@ -145,13 +150,13 @@ default ObjectSpecification specForBookmarkElseFail( @Override default @Nullable ObjectSpecification loadSpecification( final @Nullable Class<?> domainType) { - return loadSpecification(domainType, IntrospectionRequest.TYPE_ONLY); + return loadSpecification(domainType, IntrospectionRequest.TYPE_ONLY, IntrospectionTrigger.dummy()); } @Override default Optional<BeanSort> lookupBeanSort(final @Nullable LogicalType logicalType) { if(logicalType==null) return Optional.empty(); - var spec = loadSpecification(logicalType.correspondingClass(), IntrospectionRequest.REGISTER); + var spec = loadSpecification(logicalType.correspondingClass(), IntrospectionRequest.REGISTER, IntrospectionTrigger.dummy()); return spec != null ? Optional.of(spec.getBeanSort()) : Optional.empty();
