This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/master by this push:
new f9f9aef6e4 ISIS-3293: revert viewmodel changes from prev. commit;
support constructor inject semantics
f9f9aef6e4 is described below
commit f9f9aef6e471e886aee3f937a714017b1a5b58b7
Author: Andi Huber <[email protected]>
AuthorDate: Thu Nov 24 16:44:14 2022 +0100
ISIS-3293: revert viewmodel changes from prev. commit; support
constructor inject semantics
---
.../appfeatui/ApplicationFeatureViewModel.java | 2 +-
.../services/appfeatui/ApplicationNamespace.java | 15 ++---
.../applib/services/appfeatui/ApplicationType.java | 18 ++++--
.../services/appfeatui/ApplicationTypeAction.java | 16 ++---
.../appfeatui/ApplicationTypeCollection.java | 16 ++---
.../appfeatui/ApplicationTypeProperty.java | 17 ++---
commons/src/main/java/module-info.java | 1 +
.../commons/internal/reflection/_ClassCache.java | 9 +++
.../progmodel/ProgrammingModelConstants.java | 39 ++++++++----
.../ViewModelFacetForViewModelInterface.java | 74 ++++++++++++++++++----
.../dom/domain/_interactions/InteractionDtoVm.java | 2 +
.../feature/api/ApplicationFeatureChoices.java | 1 +
.../dom/mixins/perms/UserPermissionViewModel.java | 1 +
13 files changed, 147 insertions(+), 64 deletions(-)
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureViewModel.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureViewModel.java
index ffe4f9d99c..ed37d3dd7e 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureViewModel.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationFeatureViewModel.java
@@ -90,7 +90,7 @@ public abstract class ApplicationFeatureViewModel implements
ViewModel {
if(featureId.getSort().isNamespace()) {
_Assert.assertEquals(vmClass, ApplicationNamespace.class);
- return
factoryService.viewModel(ApplicationNamespace.of(featureId));
+ return factoryService.viewModel(new
ApplicationNamespace(featureId));
}
return factoryService.viewModel(vmClass,
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationNamespace.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationNamespace.java
index 54d4820fb3..ef2588d797 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationNamespace.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationNamespace.java
@@ -25,6 +25,7 @@ import java.lang.annotation.Target;
import java.util.List;
import java.util.SortedSet;
+import javax.inject.Inject;
import javax.inject.Named;
import org.apache.causeway.applib.CausewayModuleApplib;
@@ -48,19 +49,17 @@ public class ApplicationNamespace extends
ApplicationFeatureViewModel {
public static abstract class CollectionDomainEvent<T> extends
ApplicationFeatureViewModel.CollectionDomainEvent<ApplicationNamespace, T> {}
- // -- CONSTRUCTION
+ // -- CONSTRUCTORS
+ public ApplicationNamespace() { }
+ public ApplicationNamespace(final ApplicationFeatureId featureId) {
+ super(featureId);
+ }
+ @Inject
public ApplicationNamespace(final String memento) {
super(memento);
}
- public static ApplicationNamespace of(final ApplicationFeatureId
featureId) {
- return new ApplicationNamespace(featureId);
- }
- private ApplicationNamespace(final ApplicationFeatureId featureId) {
- super(featureId);
- }
-
// -- CONTENTS (collection, for namespaces only)
@Collection(
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationType.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationType.java
index 121774c658..a528b2ba52 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationType.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationType.java
@@ -25,6 +25,7 @@ import java.lang.annotation.Target;
import java.util.List;
import java.util.SortedSet;
+import javax.inject.Inject;
import javax.inject.Named;
import org.apache.causeway.applib.CausewayModuleApplib;
@@ -51,17 +52,20 @@ public class ApplicationType extends
ApplicationFeatureViewModel {
public static abstract class CollectionDomainEvent<T>
extends
ApplicationFeatureViewModel.CollectionDomainEvent<ApplicationType, T> {}
- // -- CONSTRUCTION
+
+ // -- constructors
+
+ public ApplicationType() { }
+ public ApplicationType(final ApplicationFeatureId featureId) {
+ super(featureId);
+ }
+ @Inject
public ApplicationType(final String memento) {
super(memento);
}
- public ApplicationType of(final ApplicationFeatureId featureId) {
- return new ApplicationType(featureId);
- }
- private ApplicationType(final ApplicationFeatureId featureId) {
- super(featureId);
- }
+
+
// -- actions (collection)
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeAction.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeAction.java
index 028884df2f..6949732a87 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeAction.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeAction.java
@@ -23,6 +23,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import javax.inject.Inject;
import javax.inject.Named;
import org.apache.causeway.applib.CausewayModuleApplib;
@@ -47,17 +48,16 @@ public class ApplicationTypeAction extends
ApplicationTypeMember {
public static abstract class PropertyDomainEvent<T> extends
ApplicationTypeMember.PropertyDomainEvent<ApplicationTypeAction, T> {}
- // -- CONSTRUCTION
-
+ // -- constructors
+ public ApplicationTypeAction() { }
+ public ApplicationTypeAction(final ApplicationFeatureId featureId) {
+ super(featureId);
+ }
+ @Inject
public ApplicationTypeAction(final String memento) {
super(memento);
}
- public ApplicationTypeAction of(final ApplicationFeatureId featureId) {
- return new ApplicationTypeAction(featureId);
- }
- private ApplicationTypeAction(final ApplicationFeatureId featureId) {
- super(featureId);
- }
+
// -- returnTypeName (property)
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeCollection.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeCollection.java
index db525c646f..85eaf3a5b1 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeCollection.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeCollection.java
@@ -22,6 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import javax.inject.Inject;
import javax.inject.Named;
import org.apache.causeway.applib.CausewayModuleApplib;
@@ -45,17 +46,16 @@ public class ApplicationTypeCollection extends
ApplicationTypeMember {
public static abstract class PropertyDomainEvent<T> extends
ApplicationTypeMember.PropertyDomainEvent<ApplicationTypeCollection, T> {}
- // -- CONSTRUCTION
-
+ // -- constructors
+ public ApplicationTypeCollection() {}
+ public ApplicationTypeCollection(final ApplicationFeatureId featureId) {
+ super(featureId);
+ }
+ @Inject
public ApplicationTypeCollection(final String memento) {
super(memento);
}
- public ApplicationTypeCollection of(final ApplicationFeatureId featureId) {
- return new ApplicationTypeCollection(featureId);
- }
- private ApplicationTypeCollection(final ApplicationFeatureId featureId) {
- super(featureId);
- }
+
// -- elementType
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeProperty.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeProperty.java
index 7fdcc2dde2..b80605b23e 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeProperty.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/appfeatui/ApplicationTypeProperty.java
@@ -23,6 +23,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import javax.inject.Inject;
import javax.inject.Named;
import org.apache.causeway.applib.CausewayModuleApplib;
@@ -50,17 +51,17 @@ public class ApplicationTypeProperty extends
ApplicationTypeMember {
public static abstract class PropertyDomainEvent<T> extends
ApplicationTypeMember.PropertyDomainEvent<ApplicationTypeProperty, T> {}
- // -- CONSTRUCTION
-
+ // -- constructors
+ public ApplicationTypeProperty() { }
+ public ApplicationTypeProperty(final ApplicationFeatureId featureId) {
+ super(featureId);
+ }
+ @Inject
public ApplicationTypeProperty(final String memento) {
super(memento);
}
- public ApplicationTypeProperty of(final ApplicationFeatureId featureId) {
- return new ApplicationTypeProperty(featureId);
- }
- private ApplicationTypeProperty(final ApplicationFeatureId featureId) {
- super(featureId);
- }
+
+
// -- returnType
diff --git a/commons/src/main/java/module-info.java
b/commons/src/main/java/module-info.java
index aebeb4b206..2491d567a3 100644
--- a/commons/src/main/java/module-info.java
+++ b/commons/src/main/java/module-info.java
@@ -71,6 +71,7 @@ module org.apache.causeway.commons {
requires transitive spring.beans;
requires transitive spring.context;
requires transitive spring.core;
+ requires java.inject;
// JAXB JUnit test
opens org.apache.causeway.commons.internal.resources to java.xml.bind;
diff --git
a/commons/src/main/java/org/apache/causeway/commons/internal/reflection/_ClassCache.java
b/commons/src/main/java/org/apache/causeway/commons/internal/reflection/_ClassCache.java
index 5acc34abdf..773cb2cb7f 100644
---
a/commons/src/main/java/org/apache/causeway/commons/internal/reflection/_ClassCache.java
+++
b/commons/src/main/java/org/apache/causeway/commons/internal/reflection/_ClassCache.java
@@ -27,6 +27,9 @@ import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
+import javax.inject.Inject;
+
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
@@ -82,6 +85,12 @@ public final class _ClassCache implements AutoCloseable {
inspectType(type).publicConstructorsByKey.values()));
}
+ public <T> Can<Constructor<T>>
getPublicConstructorsWithInjectSemantics(final Class<T> type) {
+ return getPublicConstructors(type)
+ .filter(con->_Annotations.synthesize(con,
Inject.class).isPresent()
+ || _Annotations.synthesize(con,
Autowired.class).map(annot->annot.required()).orElse(false));
+ }
+
public Optional<Constructor<?>> lookupPublicConstructor(final Class<?>
type, final Class<?>[] paramTypes) {
return Optional.ofNullable(lookupConstructor(false, type, paramTypes));
}
diff --git
a/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java
b/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java
index 8f30c8aa67..bfc6853837 100644
---
a/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java
+++
b/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java
@@ -482,8 +482,13 @@ public final class ProgrammingModelConstants {
VIEWMODEL_CONFLICTING_SERIALIZATION_STRATEGIES(
"${type}: has multiple incompatible annotations/interfaces
indicating that "
+ "it is a recreatable object of some sort (${facetA} and
${facetB})"),
+ VIEWMODEL_MULTIPLE_CONSTRUCTORS_WITH_INJECT_SEMANTICS(
+ "${type}: ViewModel contract violation: there must be at most
one public constructor that has inject semantics, "
+ + "but found ${found}. "
+ + "See " +
org.apache.causeway.applib.ViewModel.class.getName() + " java-doc for
details."),
VIEWMODEL_MISSING_OR_MULTIPLE_PUBLIC_CONSTRUCTORS(
- "${type}: ViewModel contract violation: there must be exactly
one public constructor. "
+ "${type}: ViewModel contract violation: in absence of inject
semantics there must be exactly one public constructor, "
+ + "but found ${found}. "
+ "See " +
org.apache.causeway.applib.ViewModel.class.getName() + " java-doc for
details."),
VIEWMODEL_MISSING_SERIALIZATION_STRATEGY(
"${type}: Missing ViewModel serialization strategy
encountered; "
@@ -539,22 +544,32 @@ public final class ProgrammingModelConstants {
}
}
+ /**
+ * violation of view-model contract should be covered by meta-model
validation
+ */
public static enum ViewmodelConstructor {
- PUBLIC_ANY_ARGS {
-
- @Override
- public <T> Optional<Constructor<T>> get(final Class<T> cls) {
- // violation of view-model contract should be covered by
meta-model validation
+ PUBLIC_WITH_INJECT_SEMANTICS {
+ @Override public <T> Can<Constructor<T>> getAll(final Class<T>
cls) {
return Try.call(()->
_ClassCache.getInstance()
- .getPublicConstructors(cls)
- .getSingleton().orElse(null))
- .getValue();
+ .getPublicConstructorsWithInjectSemantics(cls))
+ .getValue()
+ .orElse(Can.empty());
+ }
+ },
+ PUBLIC_ANY {
+ @Override public <T> Can<Constructor<T>> getAll(final Class<T>
cls) {
+ return Try.call(()->
+ _ClassCache.getInstance()
+ .getPublicConstructors(cls))
+ .getValue()
+ .orElse(Can.empty());
}
-
};
- public abstract <T> Optional<Constructor<T>> get(Class<T>
correspondingClass);
-
+ public <T> Optional<Constructor<T>> getFirst(final Class<T> cls) {
+ return getAll(cls).getFirst();
+ }
+ public abstract <T> Can<Constructor<T>> getAll(Class<T> cls);
}
/**
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/viewmodel/ViewModelFacetForViewModelInterface.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/viewmodel/ViewModelFacetForViewModelInterface.java
index 32af74c142..4b3da2b6bc 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/viewmodel/ViewModelFacetForViewModelInterface.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/viewmodel/ViewModelFacetForViewModelInterface.java
@@ -19,7 +19,9 @@
package org.apache.causeway.core.metamodel.facets.object.viewmodel;
import java.lang.reflect.Constructor;
+import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
import org.springframework.lang.Nullable;
@@ -28,6 +30,7 @@ import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.services.registry.ServiceRegistry;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.functional.IndexedConsumer;
+import org.apache.causeway.commons.internal.assertions._Assert;
import org.apache.causeway.core.config.progmodel.ProgrammingModelConstants;
import org.apache.causeway.core.metamodel.commons.ClassExtensions;
import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
@@ -47,8 +50,8 @@ import lombok.val;
public class ViewModelFacetForViewModelInterface
extends ViewModelFacetAbstract {
- public static Optional<ViewModelFacet> create(
- final Class<?> cls,
+ public static <T> Optional<ViewModelFacet> create(
+ final Class<T> cls,
final FacetHolder holder,
final HasPostConstructMethodCache postConstructMethodCache) {
@@ -56,23 +59,69 @@ extends ViewModelFacetAbstract {
return Optional.empty();
}
+ Constructor<?> pickedConstructor = null; // not used for abstract types
+
if(!cls.isInterface()
- && !ClassExtensions.isAbstract(cls)
- &&
!ProgrammingModelConstants.ViewmodelConstructor.PUBLIC_ANY_ARGS.get(cls).isPresent())
{
- ValidationFailure.raiseFormatted(holder,
-
ProgrammingModelConstants.Validation.VIEWMODEL_MISSING_OR_MULTIPLE_PUBLIC_CONSTRUCTORS
- .getMessageForType(cls.getName()));
+ && !ClassExtensions.isAbstract(cls)) {
+
+ val explicitInjectConstructors =
ProgrammingModelConstants.ViewmodelConstructor.PUBLIC_WITH_INJECT_SEMANTICS.getAll(cls);
+ val publicConstructors =
ProgrammingModelConstants.ViewmodelConstructor.PUBLIC_ANY.getAll(cls);
+
+ if(explicitInjectConstructors.getCardinality().isMultiple()) {
+ if(!explicitInjectConstructors.getCardinality().isOne()) {
+ ValidationFailure.raiseFormatted(holder,
+
ProgrammingModelConstants.Validation.VIEWMODEL_MULTIPLE_CONSTRUCTORS_WITH_INJECT_SEMANTICS
+ .getMessage(Map.of(
+ "type", cls.getName(),
+ "found",
explicitInjectConstructors.getCardinality().isMultiple()
+ ? "{" +
explicitInjectConstructors.stream()
+ .map(Constructor::toString)
+
.collect(Collectors.joining(", ")) + "}"
+ : "none")));
+
+ return Optional.empty();
+ }
+ }
+
+ if(explicitInjectConstructors.getCardinality().isZero()) {
+
+ // in absence of a constructor with inject semantics there
must be exactly one public to pick instead
+
+ if(!publicConstructors.getCardinality().isOne()) {
+ ValidationFailure.raiseFormatted(holder,
+
ProgrammingModelConstants.Validation.VIEWMODEL_MISSING_OR_MULTIPLE_PUBLIC_CONSTRUCTORS
+ .getMessage(Map.of(
+ "type", cls.getName(),
+ "found",
publicConstructors.getCardinality().isMultiple()
+ ? "{" + publicConstructors.stream()
+ .map(Constructor::toString)
+
.collect(Collectors.joining(", ")) + "}"
+ : "none")));
+
+ return Optional.empty();
+ }
+
+ }
+
+ // -- else happy case
+
+ pickedConstructor =
explicitInjectConstructors.getCardinality().isOne()
+ ? explicitInjectConstructors.getSingletonOrFail()
+ : publicConstructors.getSingletonOrFail();
- return Optional.empty();
}
- return Optional.of(new ViewModelFacetForViewModelInterface(holder,
postConstructMethodCache));
+ return Optional.of(new ViewModelFacetForViewModelInterface(holder,
pickedConstructor, postConstructMethodCache));
}
+ private Constructor<?> constructorAnyArgs;
+
protected ViewModelFacetForViewModelInterface(
final FacetHolder holder,
+ final @Nullable Constructor<?> constructorAnyArgs,
final HasPostConstructMethodCache postConstructMethodCache) {
super(holder, postConstructMethodCache, Precedence.HIGH);
+ this.constructorAnyArgs = constructorAnyArgs;
}
@Override
@@ -107,9 +156,9 @@ extends ViewModelFacetAbstract {
private Object deserialize(
@NonNull final ObjectSpecification viewmodelSpec,
@Nullable final String memento) {
- val constructorAnyArgs =
ProgrammingModelConstants.ViewmodelConstructor.PUBLIC_ANY_ARGS
- .get(viewmodelSpec.getCorrespondingClass())
- .orElseThrow();
+
+ _Assert.assertNotNull(constructorAnyArgs, ()->"framework bug: required
non-null, "
+ + "this can only happen, if we try to deserialize an abstract
type");
val resolvedArgs = resolveArgsForConstructor(constructorAnyArgs,
getServiceRegistry(), memento);
@@ -122,6 +171,7 @@ extends ViewModelFacetAbstract {
final Constructor<?> constructor,
final ServiceRegistry serviceRegistry,
final String memento) {
+
val params = Can.ofArray(constructor.getParameters());
val args = new Object[params.size()];
params.forEach(IndexedConsumer.zeroBased((i, param)->{
diff --git
a/examples/demo/domain/src/main/java/demoapp/dom/domain/_interactions/InteractionDtoVm.java
b/examples/demo/domain/src/main/java/demoapp/dom/domain/_interactions/InteractionDtoVm.java
index 37f2962dc4..723ab3912d 100644
---
a/examples/demo/domain/src/main/java/demoapp/dom/domain/_interactions/InteractionDtoVm.java
+++
b/examples/demo/domain/src/main/java/demoapp/dom/domain/_interactions/InteractionDtoVm.java
@@ -21,6 +21,7 @@ package demoapp.dom.domain._interactions;
import java.text.SimpleDateFormat;
import java.util.Date;
+import javax.inject.Inject;
import javax.inject.Named;
import org.apache.causeway.applib.ViewModel;
@@ -73,6 +74,7 @@ public class InteractionDtoVm implements ViewModel {
// -- VIEWMODEL CONTRACT
+ @Inject
public InteractionDtoVm(final String memento) {
interactionDto =
InteractionDtoUtils.fromXml(encodingService.decodeToString(memento));
}
diff --git
a/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/feature/api/ApplicationFeatureChoices.java
b/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/feature/api/ApplicationFeatureChoices.java
index 576d4c80c1..68d2a5164f 100644
---
a/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/feature/api/ApplicationFeatureChoices.java
+++
b/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/feature/api/ApplicationFeatureChoices.java
@@ -160,6 +160,7 @@ public class ApplicationFeatureChoices {
// -- VIEWMODEL CONTRACT
+ @Inject
public AppFeat(final String memento) {
this(ApplicationFeatureId.parseEncoded(memento)); // fail by
intention if memento is '<no id>'
}
diff --git
a/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/user/dom/mixins/perms/UserPermissionViewModel.java
b/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/user/dom/mixins/perms/UserPermissionViewModel.java
index 04f15d749b..be90c216e0 100644
---
a/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/user/dom/mixins/perms/UserPermissionViewModel.java
+++
b/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/user/dom/mixins/perms/UserPermissionViewModel.java
@@ -116,6 +116,7 @@ public class UserPermissionViewModel implements ViewModel {
// -- VIEWMODEL CONTRACT
+ @Inject
public UserPermissionViewModel(final String memento) {
val payload = _Serializables.read(String[].class,
_Bytes.ofUrlBase64.apply(memento.getBytes(StandardCharsets.US_ASCII)));