You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2020/01/05 10:17:45 UTC
[isis] 01/02: ISIS-2250: adds config properties to lock down
metamodel, or to incrementally validate otherwise.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git
commit 61e4f55704028ca1ae777e80af48d1cdf49b7142
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Sun Jan 5 10:09:37 2020 +0000
ISIS-2250: adds config properties to lock down metamodel, or to incrementally validate otherwise.
Also:
- adds validator to ensure that there are no actions except on known types
- no longer inject into Interaction, instead pass in the required services (ClockService, MetricsService).
---
.../isis/applib/services/command/Command.java | 2 +-
.../isis/applib/services/iactn/Interaction.java | 56 +++++++++++++---------
.../commons/internal/exceptions/_Exceptions.java | 12 ++++-
.../org/apache/isis/config/IsisConfiguration.java | 48 ++++++++++++++++++-
.../isis/config/beans/IsisBeanTypeRegistry.java | 11 +++--
.../isis/metamodel/facets/DomainEventHelper.java | 2 +-
...ctionInvocationFacetForDomainEventAbstract.java | 9 ++--
...tySetterOrClearFacetForDomainEventAbstract.java | 13 +++--
.../isis/metamodel/progmodel/ProgrammingModel.java | 10 ++++
.../dflt/ProgrammingModelFacetsJava8.java | 22 +++++++++
.../isis/metamodel/spec/ObjectSpecification.java | 1 +
.../metamodel/specloader/SpecificationLoader.java | 5 +-
.../specloader/SpecificationLoaderDefault.java | 56 +++++++++++++++++-----
.../specimpl/ObjectSpecificationAbstract.java | 13 ++++-
.../specimpl/dflt/ObjectSpecificationDefault.java | 2 +
.../validator/MetaModelValidatorVisiting.java | 46 +++++++++++++++---
.../metamodel/JdoProgrammingModelPlugin.java | 2 +
.../persistence/IsisTransactionJdo.java | 7 +--
.../persistence/PersistenceSession5.java | 2 +-
19 files changed, 250 insertions(+), 69 deletions(-)
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/command/Command.java b/core/applib/src/main/java/org/apache/isis/applib/services/command/Command.java
index fe83936..1eb9ff2 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/command/Command.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/command/Command.java
@@ -390,7 +390,7 @@ public interface Command extends HasUniqueId {
* </p>
*
* See also {@link Interaction#getCurrentExecution()} and
- * {@link Interaction.Execution#setStartedAt(Timestamp)}.
+ * {@link #setStartedAt(org.apache.isis.applib.services.clock.ClockService, org.apache.isis.applib.services.metrics.MetricsService)}.
*/
void setStartedAt(Timestamp startedAt);
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java b/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
index 4cb19a9..d53e822 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/iactn/Interaction.java
@@ -27,10 +27,7 @@ import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.LongAdder;
-import javax.inject.Inject;
-
import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.annotation.Value;
import org.apache.isis.applib.events.domain.AbstractDomainEvent;
import org.apache.isis.applib.events.domain.ActionDomainEvent;
import org.apache.isis.applib.events.domain.PropertyDomainEvent;
@@ -40,7 +37,6 @@ import org.apache.isis.applib.services.command.Command;
import org.apache.isis.applib.services.eventbus.EventBusService;
import org.apache.isis.applib.services.metrics.MetricsService;
import org.apache.isis.applib.services.wrapper.WrapperFactory;
-import org.apache.isis.applib.services.xactn.Transaction;
import org.apache.isis.applib.util.schema.MemberExecutionDtoUtils;
import org.apache.isis.commons.internal.collections._Lists;
import org.apache.isis.commons.internal.collections._Maps;
@@ -54,6 +50,8 @@ import org.apache.isis.schema.ixn.v1.ObjectCountsDto;
import org.apache.isis.schema.ixn.v1.PropertyEditDto;
import org.apache.isis.schema.jaxbadapters.JavaSqlTimestampXmlGregorianCalendarAdapter;
+import lombok.val;
+
/**
* Represents an action invocation or property modification, resulting in some state change of the system. It captures
* not only the target object and arguments passed, but also builds up the call-graph, and captures metrics, eg
@@ -79,7 +77,6 @@ import org.apache.isis.schema.jaxbadapters.JavaSqlTimestampXmlGregorianCalendarA
* </p>
*
*/
-@Value
public class Interaction implements HasUniqueId {
// -- transactionId (property)
@@ -135,11 +132,13 @@ public class Interaction implements HasUniqueId {
@Programmatic
public Object execute(
final MemberExecutor<ActionInvocation> memberExecutor,
- final ActionInvocation actionInvocation) {
+ final ActionInvocation actionInvocation,
+ final ClockService clockService,
+ final MetricsService metricsService) {
push(actionInvocation);
- return executeInternal(memberExecutor, actionInvocation);
+ return executeInternal(memberExecutor, actionInvocation, clockService, metricsService);
}
/**
@@ -153,16 +152,20 @@ public class Interaction implements HasUniqueId {
@Programmatic
public Object execute(
final MemberExecutor<PropertyEdit> memberExecutor,
- final PropertyEdit propertyEdit) {
+ final PropertyEdit propertyEdit,
+ final ClockService clockService,
+ final MetricsService metricsService) {
push(propertyEdit);
- return executeInternal(memberExecutor, propertyEdit);
+ return executeInternal(memberExecutor, propertyEdit, clockService, metricsService);
}
private <T extends Execution<?,?>> Object executeInternal(
final MemberExecutor<T> memberExecutor,
- final T execution) {
+ final T execution,
+ final ClockService clockService,
+ final MetricsService metricsService) {
// as a convenience, since in all cases we want the command to start when the first
// interaction executes, we populate the command here.
@@ -190,7 +193,7 @@ public class Interaction implements HasUniqueId {
}
} finally {
final Timestamp completedAt = clockService.nowAsJavaSqlTimestamp();
- pop(completedAt);
+ pop(completedAt, metricsService);
}
}
@@ -238,12 +241,14 @@ public class Interaction implements HasUniqueId {
* </p>
*/
@Programmatic
- private Execution<?,?> pop(final Timestamp completedAt) {
+ private Execution<?,?> pop(
+ final Timestamp completedAt,
+ final MetricsService metricsService) {
if(currentExecution == null) {
throw new IllegalStateException("No current execution to pop");
}
final Execution<?,?> popped = currentExecution;
- popped.setCompletedAt(completedAt);
+ popped.setCompletedAt(completedAt, metricsService);
moveCurrentTo(currentExecution.getParent());
return popped;
@@ -301,7 +306,7 @@ public class Interaction implements HasUniqueId {
*/
PUBLISHED_EVENT,
/**
- * There may be multiple transactions within a given interaction, as per {@link Transaction#getSequence()}.
+ * There may be multiple transactions within a given interaction.
*/
TRANSACTION,
;
@@ -483,8 +488,12 @@ public class Interaction implements HasUniqueId {
}
@Programmatic
- public void setStartedAt(final Timestamp startedAt) {
- syncMetrics(When.BEFORE, startedAt);
+ public Timestamp start(
+ final ClockService clockService,
+ final MetricsService metricsService) {
+ val startedAt = clockService.nowAsJavaSqlTimestamp();
+ syncMetrics(When.BEFORE, startedAt, metricsService);
+ return startedAt;
}
@@ -499,8 +508,10 @@ public class Interaction implements HasUniqueId {
/**
* <b>NOT API</b>: intended to be called only by the framework.
*/
- void setCompletedAt(final Timestamp completedAt) {
- syncMetrics(When.AFTER, completedAt);
+ void setCompletedAt(
+ final Timestamp completedAt,
+ final MetricsService metricsService) {
+ syncMetrics(When.AFTER, completedAt, metricsService);
}
@@ -651,8 +662,10 @@ public class Interaction implements HasUniqueId {
final int numberObjectsLoaded,
final int numberObjectsDirtied);
}
- private void syncMetrics(final When when, final Timestamp timestamp) {
- final MetricsService metricsService = interaction.metricsService;
+ private void syncMetrics(
+ final When when,
+ final Timestamp timestamp,
+ final MetricsService metricsService) {
final int numberObjectsLoaded = metricsService.numberObjectsLoaded();
final int numberObjectsDirtied = metricsService.numberObjectsDirtied();
@@ -706,7 +719,4 @@ public class Interaction implements HasUniqueId {
}
}
- @Inject MetricsService metricsService;
- @Inject ClockService clockService;
-
}
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java b/core/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
index 8598579..974396d 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/exceptions/_Exceptions.java
@@ -74,11 +74,21 @@ public final class _Exceptions {
* @param _case the unmatched case to be reported
* @return
*/
- public static final IllegalArgumentException illegalArgument(String format, @Nullable Object ... args) {
+ public static final IllegalArgumentException illegalArgument(
+ final String format,
+ final @Nullable Object ... args) {
requires(format, "format");
return new IllegalArgumentException(String.format(format, args));
}
+ public static IllegalStateException illegalState(
+ final String format,
+ final @Nullable Object ... args) {
+ requires(format, "format");
+ return new IllegalStateException(String.format(format, args));
+ }
+
+
public static final NoSuchElementException noSuchElement(String msg) {
return new NoSuchElementException(msg);
}
diff --git a/core/config/src/main/java/org/apache/isis/config/IsisConfiguration.java b/core/config/src/main/java/org/apache/isis/config/IsisConfiguration.java
index 9a66511..78db370 100644
--- a/core/config/src/main/java/org/apache/isis/config/IsisConfiguration.java
+++ b/core/config/src/main/java/org/apache/isis/config/IsisConfiguration.java
@@ -501,8 +501,52 @@ public class IsisConfiguration {
private final Introspector introspector = new Introspector();
@Data
public static class Introspector {
+ /**
+ * Whether to perform introspection and metamodel validation in parallel.
+ */
private boolean parallelize = true;
+ /**
+ * Whether all known types should be fully introspected as part of the bootstrapping, or should only be
+ * partially introspected initially.
+ *
+ * <p>
+ * Leaving this as lazy means that there's a chance that metamodel validation errors will not be
+ * discovered during bootstrap. That said, metamodel validation is still run incrementally for any
+ * classes introspected lazily after initial bootstrapping (unless {@link #isValidateIncrementally()} is
+ * disabled.
+ * </p>
+ */
private IntrospectionMode mode = IntrospectionMode.LAZY_UNLESS_PRODUCTION;
+ /**
+ * If true, then no new specifications will be allowed to be loaded once introspection has been complete.
+ *
+ * <p>
+ * Only applies if the introspector is configured to perform full introspection up-front (either because of
+ * {@link IntrospectionMode#FULL} or {@link IntrospectionMode#LAZY_UNLESS_PRODUCTION} when in production);
+ * otherwise is ignored.
+ * </p>
+ */
+ private boolean lockAfterFullIntrospection = true;
+ /**
+ * If true, then metamodel validation is performed after any new specification has been loaded (after the
+ * initial bootstrapping).
+ *
+ * <p>
+ * This does <i>not</i> apply if the introspector is configured to perform full introspection up-front
+ * AND when the metamodel is {@link #isLockAfterFullIntrospection() locked} after initial bootstrapping
+ * (because in that case the lock check will simply prevent any new specs from being loaded).
+ * But it will apply otherwise.
+ * </p>
+ *
+ * <p>In particular, this setting <i>can</i> still apply even if the {@link #getMode() introspection mode}
+ * is set to {@link IntrospectionMode#FULL full}, because that in itself does not preclude some code
+ * from attempting to load some previously unknown type. For example, a fixture script could attempt to
+ * invoke an action on some new type using the
+ * {@link org.apache.isis.applib.services.wrapper.WrapperFactory} - this will cause introspection of that
+ * new type to be performed.
+ * </p>
+ */
+ private boolean validateIncrementally = true;
}
private final Validator validator = new Validator();
@@ -1122,7 +1166,7 @@ public class IsisConfiguration {
* eg: {@code isis.value.format.datetime=iso}
* <p>
* A pre-determined list of values is available, specifically 'iso_encoding', 'iso' and 'medium' (see
- * {@link org.apache.isis.metamodel.facets.value.datetimejdk8local.Jdk8LocalDateTimeValueSemanticsProvider.NAMED_TITLE_FORMATTERS}).
+ * <code>org.apache.isis.metamodel.facets.value.datetimejdk8local.Jdk8LocalDateTimeValueSemanticsProvider#NAMED_TITLE_FORMATTERS</code>).
* Alternatively, can also specify a mask, eg <tt>dd-MMM-yyyy</tt>.
*/
DATETIME,
@@ -1132,7 +1176,7 @@ public class IsisConfiguration {
* eg: {@code isis.value.format.date=iso}
* <p>
* A pre-determined list of values is available, specifically 'iso_encoding', 'iso' and 'medium' (see
- * {@link org.apache.isis.metamodel.facets.value.datejdk8local.Jdk8LocalDateValueSemanticsProvider.NAMED_TITLE_FORMATTERS}).
+ * <code>org.apache.isis.metamodel.facets.value.datejdk8local.Jdk8LocalDateValueSemanticsProvider.NAMED_TITLE_FORMATTERS</code>).
* Alternatively, can also specify a mask, eg <tt>dd-MMM-yyyy</tt>.
*/
DATE,
diff --git a/core/config/src/main/java/org/apache/isis/config/beans/IsisBeanTypeRegistry.java b/core/config/src/main/java/org/apache/isis/config/beans/IsisBeanTypeRegistry.java
index 1107a2b..073060b 100644
--- a/core/config/src/main/java/org/apache/isis/config/beans/IsisBeanTypeRegistry.java
+++ b/core/config/src/main/java/org/apache/isis/config/beans/IsisBeanTypeRegistry.java
@@ -162,15 +162,16 @@ public final class IsisBeanTypeRegistry implements IsisComponentScanInterceptor,
/**
* If given type is part of the meta-model and is available for injection,
* returns the <em>Managed Bean's</em> name (id) as
- * recognized by the IoC container, {@code null} otherwise;
+ * recognized by the IoC container.
+ *
* @param type
* @return
*/
- public String getManagedBeanNameForType(Class<?> type) {
+ public Optional<String> getManagedBeanNameForType(Class<?> type) {
if(vetoedTypes.contains(type)) { // vetos are coming from the spec-loader during init
- return null;
+ return Optional.empty();
}
- return managedBeanNamesByType.get(type);
+ return Optional.ofNullable(managedBeanNamesByType.get(type));
}
/**
@@ -179,7 +180,7 @@ public final class IsisBeanTypeRegistry implements IsisComponentScanInterceptor,
* @param type
*/
public boolean isManagedBean(Class<?> type) {
- return getManagedBeanNameForType(type)!=null;
+ return getManagedBeanNameForType(type).isPresent();
}
// -- HELPER
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java
index 1b736bd..067ecb2 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java
@@ -132,7 +132,7 @@ public class DomainEventHelper {
.map(ObjectActionParameter::getName)
.collect(_Lists.toUnmodifiable());
- val parameterTypes = parameters.stream()
+ final List<Class<?>> parameterTypes = parameters.stream()
.map(ObjectActionParameter::getSpecification)
.map(ObjectSpecification::getCorrespondingClass)
.collect(_Lists.toUnmodifiable());
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
index 6893538..d9e2900 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
@@ -44,6 +44,7 @@ import org.apache.isis.applib.services.iactn.Interaction.ActionInvocation;
import org.apache.isis.applib.services.iactn.InteractionContext;
import org.apache.isis.applib.services.metamodel.MetaModelService;
import org.apache.isis.applib.services.metamodel.MetaModelService.Mode;
+import org.apache.isis.applib.services.metrics.MetricsService;
import org.apache.isis.applib.services.queryresultscache.QueryResultsCache;
import org.apache.isis.applib.services.registry.ServiceRegistry;
import org.apache.isis.commons.collections.Can;
@@ -205,7 +206,7 @@ implements ImperativeFacet {
mixinElseRegularAdapter, mixedInAdapter, execution);
// sets up startedAt and completedAt on the execution, also manages the execution call graph
- interaction.execute(callable, execution);
+ interaction.execute(callable, execution, getClockService(), getMetricsService());
// handle any exceptions
final Interaction.Execution<ActionInvocationDto, ?> priorExecution =
@@ -407,6 +408,9 @@ implements ImperativeFacet {
private ClockService getClockService() {
return serviceRegistry.lookupServiceElseFail(ClockService.class);
}
+ private MetricsService getMetricsService() {
+ return serviceRegistry.lookupServiceElseFail(MetricsService.class);
+ }
private PublisherDispatchService getPublishingServiceInternal() {
return serviceRegistry.lookupServiceElseFail(PublisherDispatchService.class);
@@ -444,8 +448,7 @@ implements ImperativeFacet {
// set the startedAt (and update command if this is the top-most member execution)
// (this isn't done within Interaction#execute(...) because it requires the DTO
// to have been set on the current execution).
- val startedAt = getClockService().nowAsJavaSqlTimestamp();
- execution.setStartedAt(startedAt);
+ val startedAt = execution.start(getClockService(), getMetricsService());
if(command.getStartedAt() == null) {
command.internal().setStartedAt(startedAt);
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
index f3ce1e8..b8c1a7f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
@@ -19,7 +19,6 @@
package org.apache.isis.metamodel.facets.properties.property.modify;
-import java.sql.Timestamp;
import java.util.Map;
import java.util.Objects;
@@ -31,6 +30,7 @@ import org.apache.isis.applib.services.command.CommandContext;
import org.apache.isis.applib.services.command.spi.CommandService;
import org.apache.isis.applib.services.iactn.Interaction;
import org.apache.isis.applib.services.iactn.InteractionContext;
+import org.apache.isis.applib.services.metrics.MetricsService;
import org.apache.isis.commons.exceptions.IsisException;
import org.apache.isis.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.metamodel.facetapi.Facet;
@@ -50,6 +50,8 @@ import org.apache.isis.schema.ixn.v1.PropertyEditDto;
import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
+import lombok.val;
+
public abstract class PropertySetterOrClearFacetForDomainEventAbstract
extends SingleValueFacetAbstract<Class<? extends PropertyDomainEvent<?,?>>> {
@@ -222,8 +224,7 @@ extends SingleValueFacetAbstract<Class<? extends PropertyDomainEvent<?,?>>> {
// set the startedAt (and update command if this is the top-most member execution)
// (this isn't done within Interaction#execute(...) because it requires the DTO
// to have been set on the current execution).
- final Timestamp startedAt = getClockService().nowAsJavaSqlTimestamp();
- execution.setStartedAt(startedAt);
+ val startedAt = execution.start(getClockService(), getMetricsService());
if(command.getStartedAt() == null) {
command.internal().setStartedAt(startedAt);
}
@@ -274,7 +275,7 @@ extends SingleValueFacetAbstract<Class<? extends PropertyDomainEvent<?,?>>> {
};
// sets up startedAt and completedAt on the execution, also manages the execution call graph
- interaction.execute(executor, execution);
+ interaction.execute(executor, execution, getClockService(), getMetricsService());
// handle any exceptions
final Interaction.Execution<?, ?> priorExecution = interaction.getPriorExecution();
@@ -322,6 +323,10 @@ extends SingleValueFacetAbstract<Class<? extends PropertyDomainEvent<?,?>>> {
return getServiceRegistry().lookupServiceElseFail(ClockService.class);
}
+ private MetricsService getMetricsService() {
+ return getServiceRegistry().lookupServiceElseFail(MetricsService.class);
+ }
+
private PublisherDispatchService getPublishingServiceInternal() {
return getServiceRegistry().lookupServiceElseFail(PublisherDispatchService.class);
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodel/ProgrammingModel.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodel/ProgrammingModel.java
index 9e237b3..8ba832d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodel/ProgrammingModel.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodel/ProgrammingModel.java
@@ -19,14 +19,18 @@
package org.apache.isis.metamodel.progmodel;
+import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.apache.isis.commons.internal.functions._Functions;
import org.apache.isis.metamodel.facets.FacetFactory;
+import org.apache.isis.metamodel.spec.ObjectSpecification;
import org.apache.isis.metamodel.specloader.validator.MetaModelValidator;
import org.apache.isis.metamodel.specloader.validator.MetaModelValidatorVisiting;
+import lombok.NonNull;
+
public interface ProgrammingModel {
// -- ENUM TYPES
@@ -155,6 +159,12 @@ public interface ProgrammingModel {
addValidator(MetaModelValidatorVisiting.of(visitor), markers);
}
+ default void addValidator(final MetaModelValidatorVisiting.Visitor visitor,
+ final @NonNull Predicate<ObjectSpecification> specPredicate,
+ final Marker ... markers) {
+ addValidator(MetaModelValidatorVisiting.of(visitor, specPredicate), markers);
+ }
+
/** shortcut for see {@link #addPostProcessor(PostProcessingOrder, Class, Supplier, Marker...)}*/
default <T extends ObjectSpecificationPostProcessor> void addPostProcessor(
PostProcessingOrder order,
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodels/dflt/ProgrammingModelFacetsJava8.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodels/dflt/ProgrammingModelFacetsJava8.java
index bd5eb4b..77fb34d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodels/dflt/ProgrammingModelFacetsJava8.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodels/dflt/ProgrammingModelFacetsJava8.java
@@ -17,8 +17,13 @@
package org.apache.isis.metamodel.progmodels.dflt;
+import java.util.function.Predicate;
+
+import org.apache.isis.applib.Identifier;
import org.apache.isis.applib.services.inject.ServiceInjector;
+import org.apache.isis.commons.internal.ioc.BeanSort;
import org.apache.isis.metamodel.authorization.standard.AuthorizationFacetFactory;
+import org.apache.isis.metamodel.facetapi.FacetHolder;
import org.apache.isis.metamodel.facets.OrphanedSupportingMethodValidator;
import org.apache.isis.metamodel.facets.actions.action.ActionAnnotationFacetFactory;
import org.apache.isis.metamodel.facets.actions.action.ActionChoicesForCollectionParameterFacetFactory;
@@ -145,6 +150,12 @@ import org.apache.isis.metamodel.facets.value.uuid.UUIDValueFacetUsingSemanticsP
import org.apache.isis.metamodel.postprocessors.param.DeriveFacetsPostProcessor;
import org.apache.isis.metamodel.progmodel.ProgrammingModelAbstract;
import org.apache.isis.metamodel.services.title.TitlesAndTranslationsValidator;
+import org.apache.isis.metamodel.spec.ObjectSpecification;
+import org.apache.isis.metamodel.spec.feature.Contributed;
+import org.apache.isis.metamodel.specloader.validator.MetaModelValidator;
+import org.apache.isis.metamodel.specloader.validator.MetaModelValidatorVisiting;
+
+import lombok.NonNull;
public final class ProgrammingModelFacetsJava8 extends ProgrammingModelAbstract {
@@ -356,6 +367,17 @@ public final class ProgrammingModelFacetsJava8 extends ProgrammingModelAbstract
addPostProcessor(PostProcessingOrder.A1_BUILTIN, DeriveFacetsPostProcessor.class);
addValidator(new TitlesAndTranslationsValidator());
+ addValidator((objectSpec, validator) -> {
+ final long numActions = objectSpec.streamObjectActions(Contributed.INCLUDED).count();
+ if (numActions > 0) {
+ validator.onFailure(objectSpec, objectSpec.getIdentifier(),
+ "%s: is a (concrete) but UNKNOWN sort, yet has %d actions",
+ objectSpec.getCorrespondingClass().getName(),
+ numActions);
+ }
+ return false;
+ }, objectSpec -> objectSpec.getBeanSort() == BeanSort.UNKNOWN && ! objectSpec.isAbstract());
+
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/spec/ObjectSpecification.java
index 3bb1afc..8c3c24b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/spec/ObjectSpecification.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/spec/ObjectSpecification.java
@@ -434,6 +434,7 @@ ObjectAssociationContainer, Hierarchical, DefaultProvider {
/**
* Introspecting up to the level required.
* @since 2.0
+ * @return whether it's necessary to re-run validations.
*/
void introspectUpTo(IntrospectionState upTo);
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoader.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoader.java
index a409bfa..a5b132b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoader.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoader.java
@@ -133,8 +133,5 @@ public interface SpecificationLoader {
}
-
-
-
-
+ void revalidateIfNecessary();
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoaderDefault.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoaderDefault.java
index da510c2..6a743d9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoaderDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoaderDefault.java
@@ -43,6 +43,7 @@ import org.apache.isis.commons.internal.base._Lazy;
import org.apache.isis.commons.internal.base._Timing;
import org.apache.isis.commons.internal.collections._Lists;
import org.apache.isis.commons.internal.environment.IsisSystemEnvironment;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
import org.apache.isis.config.IsisConfiguration;
import org.apache.isis.config.beans.IsisBeanTypeRegistry;
import org.apache.isis.config.beans.IsisBeanTypeRegistryHolder;
@@ -110,6 +111,12 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
private final SpecificationCacheDefault<ObjectSpecification> cache =
new SpecificationCacheDefault<>();
+ /**
+ * We only ever mark the metamodel as fully introspected if in {@link #isFullIntrospect() full} introspection mode.
+ */
+ @Getter
+ private boolean metamodelFullyIntrospected = false;
+
/** JUnit Test Support */
public static SpecificationLoaderDefault getInstance (
IsisConfiguration isisConfiguration,
@@ -235,6 +242,9 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
stopWatch.stop();
log.info("Metamodel created in " + (long)stopWatch.getMillis() + " ms.");
+ if(isFullIntrospect()) {
+ metamodelFullyIntrospected = true;
+ }
}
@Override
@@ -322,9 +332,25 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
synchronized (cache) {
cachedSpec = cache.computeIfAbsent(typeName, __->createSpecification(substitutedType));
}
-
+
cachedSpec.introspectUpTo(upTo);
- return cachedSpec;
+
+ return cachedSpec;
+ }
+
+ public void revalidateIfNecessary() {
+ if(!this.isisConfiguration.getReflector().getIntrospector().isValidateIncrementally()) {
+ return;
+ }
+
+ if (this.validationResult.isMemoized()) {
+ this.validationResult.clear();
+ }
+ final ValidationFailures validationFailures = this.getValidationResult();
+
+ if(validationFailures.hasFailures()) {
+ throw _Exceptions.illegalState(String.join("\n", validationFailures.getMessages("[%d] %s")));
+ }
}
// -- LOOKUP
@@ -368,8 +394,17 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
* Creates the appropriate type of {@link ObjectSpecification}.
*/
private ObjectSpecification createSpecification(final Class<?> cls) {
-
- // ... and create the specs
+
+ if(isMetamodelFullyIntrospected() && isisConfiguration.getReflector().getIntrospector().isLockAfterFullIntrospection()) {
+ throw _Exceptions.illegalState(
+ "Cannot introspect class '%s' because the metamodel has been fully introspected and is now locked. " +
+ "One reason this can happen is if you are attempting to invoke an action through the WrapperFactory " +
+ "on a service class incorrectly annotated with Spring's @Service annotation instead of " +
+ "@DomainService.",
+ cls.getName());
+ }
+
+ // ... and create the specs
final ObjectSpecificationAbstract objectSpec;
if (FreeStandingList.class.isAssignableFrom(cls)) {
@@ -381,14 +416,13 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
val typeRegistry = isisBeanTypeRegistryHolder.getIsisBeanTypeRegistry();
val managedBeanNameIfAny = typeRegistry.getManagedBeanNameForType(cls);
-
objectSpec = new ObjectSpecificationDefault(
- cls,
- metaModelContext,
- facetProcessor,
- managedBeanNameIfAny,
- postProcessor,
- classSubstitutor);
+ cls,
+ metaModelContext,
+ facetProcessor,
+ managedBeanNameIfAny.orElse(null),
+ postProcessor,
+ classSubstitutor);
}
return objectSpec;
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index c9f8129..bad3057 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -275,7 +275,9 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
if(log.isDebugEnabled()) {
log.debug("introspectingUpTo: {}, {}", getFullIdentifier(), upTo);
}
-
+
+ boolean revalidate = false;
+
switch (introspectionState) {
case NOT_INTROSPECTED:
if(isLessThan(upTo)) {
@@ -289,6 +291,7 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
this.introspectionState = IntrospectionState.MEMBERS_BEING_INTROSPECTED;
introspectMembers();
this.introspectionState = IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED;
+ revalidate = true;
}
// set to avoid infinite loops
break;
@@ -301,6 +304,7 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
this.introspectionState = IntrospectionState.MEMBERS_BEING_INTROSPECTED;
introspectMembers();
this.introspectionState = IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED;
+ revalidate = true;
}
break;
case MEMBERS_BEING_INTROSPECTED:
@@ -308,6 +312,13 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
case TYPE_AND_MEMBERS_INTROSPECTED:
// nothing to do
break;
+
+ default:
+ throw _Exceptions.unexpectedCodeReach();
+ }
+
+ if(revalidate) {
+ getSpecificationLoader().revalidateIfNecessary();
}
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
index 66b4120..14b38aa 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
@@ -33,6 +33,8 @@ import org.apache.isis.commons.collections.Can;
import org.apache.isis.commons.internal.base._Lazy;
import org.apache.isis.commons.internal.collections._Lists;
import org.apache.isis.commons.internal.collections._Maps;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
+import org.apache.isis.commons.internal.ioc.BeanSort;
import org.apache.isis.metamodel.commons.StringExtensions;
import org.apache.isis.metamodel.commons.ToString;
import org.apache.isis.metamodel.context.MetaModelContext;
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidatorVisiting.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidatorVisiting.java
index e65f7f6..2494719 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidatorVisiting.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidatorVisiting.java
@@ -19,14 +19,14 @@
package org.apache.isis.metamodel.specloader.validator;
+import java.util.function.Predicate;
+
import org.apache.isis.metamodel.spec.ObjectSpecification;
import org.apache.isis.metamodel.specloader.SpecificationLoaderDefault;
import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
import lombok.val;
-@RequiredArgsConstructor(staticName = "of")
public class MetaModelValidatorVisiting extends MetaModelValidatorAbstract {
// -- INTERFACES
@@ -44,9 +44,37 @@ public class MetaModelValidatorVisiting extends MetaModelValidatorAbstract {
}
// -- IMPLEMENTATION
-
+
+ public static MetaModelValidatorVisiting of(
+ final @NonNull Visitor visitor) {
+ return new MetaModelValidatorVisiting(visitor);
+ }
+
+ public static MetaModelValidatorVisiting of(
+ final @NonNull Visitor visitor,
+ final @NonNull Predicate<ObjectSpecification> includeIf) {
+ return new MetaModelValidatorVisiting(visitor, includeIf);
+ }
+
+
@NonNull private final Visitor visitor;
-
+ @NonNull private final Predicate<ObjectSpecification> includeIf;
+
+ private MetaModelValidatorVisiting(
+ final @NonNull Visitor visitor,
+ final @NonNull Predicate<ObjectSpecification> includeIf) {
+ this.visitor = visitor;
+ this.includeIf = includeIf;
+ }
+
+ private MetaModelValidatorVisiting(
+ final @NonNull Visitor visitor) {
+ this(visitor,
+ // by default, exclude managed beans from validation
+ spec -> !spec.isManagedBean() && !spec.getBeanSort().isUnknown()
+ );
+ }
+
@Override
public void collectFailuresInto(@NonNull ValidationFailures validationFailures) {
validateAll();
@@ -59,9 +87,9 @@ public class MetaModelValidatorVisiting extends MetaModelValidatorAbstract {
val specLoader = (SpecificationLoaderDefault)super.getMetaModelContext().getSpecificationLoader();
specLoader.forEach(spec->{
-
- if(spec.isManagedBean() || spec.getBeanSort().isUnknown()) {
- return; // exclude managed beans from validation
+
+ if(! includeIf.test(spec)) {
+ return;
}
visitor.visit(spec, this);
@@ -69,6 +97,10 @@ public class MetaModelValidatorVisiting extends MetaModelValidatorAbstract {
}
+ private static boolean filter(ObjectSpecification spec) {
+ return spec.isManagedBean() || spec.getBeanSort().isUnknown();
+ }
+
private void summarize() {
if(visitor instanceof SummarizingVisitor) {
SummarizingVisitor summarizingVisitor = (SummarizingVisitor) visitor;
diff --git a/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/metamodel/JdoProgrammingModelPlugin.java b/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/metamodel/JdoProgrammingModelPlugin.java
index 32dc1d9..0549ee8 100644
--- a/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/metamodel/JdoProgrammingModelPlugin.java
+++ b/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/metamodel/JdoProgrammingModelPlugin.java
@@ -205,6 +205,8 @@ public class JdoProgrammingModelPlugin implements MetaModelRefiner {
}
}
+ // so can be revalidated again if necessary.
+ collidingSpecsById.clear();
}
private String asCsv(final List<ObjectSpecification> specList) {
diff --git a/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/persistence/IsisTransactionJdo.java b/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/persistence/IsisTransactionJdo.java
index 550cb40..f5de05a 100644
--- a/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/persistence/IsisTransactionJdo.java
+++ b/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/persistence/IsisTransactionJdo.java
@@ -44,6 +44,7 @@ import org.apache.isis.runtime.persistence.transaction.IsisTransactionFlushExcep
import org.apache.isis.runtime.persistence.transaction.IsisTransactionManagerException;
import lombok.Getter;
+import lombok.Setter;
import lombok.val;
import lombok.extern.log4j.Log4j2;
@@ -183,12 +184,8 @@ public class IsisTransactionJdo implements Transaction {
// -- state
+ @Getter
private State state;
-
- public State getState() {
- return state;
- }
-
private void setState(final State state) {
this.state = state;
if(state.isComplete()) {
diff --git a/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/persistence/PersistenceSession5.java b/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/persistence/PersistenceSession5.java
index 20dfc98..45d0d56 100644
--- a/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/persistence/PersistenceSession5.java
+++ b/core/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/persistence/PersistenceSession5.java
@@ -172,7 +172,7 @@ implements IsisLifecycleListener.PersistenceSessionLifecycleManagement {
}
final Command command = createCommand();
- final Interaction interaction = factoryService.instantiate(Interaction.class);
+ final Interaction interaction = new Interaction();
final Timestamp timestamp = clockService.nowAsJavaSqlTimestamp();
final String userName = userService.getUser().getName();