This is an automated email from the ASF dual-hosted git repository. ahuber pushed a commit to branch 3200_broken-value-choices in repository https://gitbox.apache.org/repos/asf/isis.git
commit 4174060ff1a11d87e14677bdc7fbf93894934ef0 Author: Andi Huber <[email protected]> AuthorDate: Fri Sep 2 10:04:38 2022 +0200 ISIS-3200: ManagedObjects of type VALUE should provide bookmarks themselves - also introduce URL_SAFE semantics in the context of value serializers --- .../isis/applib/services/bookmark/Bookmark.java | 14 +- .../images/managed-object-diagram.drawio.svg | 2 +- .../facets/object/entity/EntityFacet.java | 36 ++++++ .../facets/object/value/ValueFacetAbstract.java | 10 +- .../facets/object/value/ValueSerializer.java | 9 +- .../object/value/ValueSerializerDefault.java | 32 +++-- .../object/viewmodel/ViewModelFacetAbstract.java | 4 +- .../isis/core/metamodel/object/ManagedObject.java | 14 +- .../isis/core/metamodel/object/ManagedObjects.java | 11 +- .../metamodel/object/_ManagedObjectService.java | 4 +- .../core/metamodel/object/_ManagedObjectValue.java | 9 +- .../metamodel/objectmanager/ObjectManager.java | 18 +-- .../objectmanager/identify/ObjectBookmarker.java | 1 + .../identify/ObjectBookmarker_builtinHandlers.java | 70 +++++----- .../metamodel/spec/HasObjectSpecification.java | 60 +++++++++ .../core/metamodel/util/snapshot/XmlSnapshot.java | 26 ++-- .../value/JavaTimeValueSemanticsProviderTest.java | 11 +- .../ValueSemanticsProviderAbstractTestCase.java | 41 +++--- .../isis/core/runtime/IsisModuleCoreRuntime.java | 4 +- ...ervice.java => IdStringifierLookupService.java} | 42 ++---- .../command/CommandDtoFactoryDefault.java | 3 +- .../interaction/InteractionDtoFactoryDefault.java | 13 +- .../memento/ObjectMementoServiceDefault.java | 6 +- .../runtimeservices/memento/_ObjectMemento.java | 144 +++++++++------------ .../entities/DnEntityStateProvider.java | 10 +- .../metamodel/facets/entity/JdoEntityFacet.java | 41 ++++-- .../jpa/integration/entity/JpaEntityFacet.java | 32 +++-- .../integration/entity/JpaEntityFacetFactory.java | 2 +- .../JsonValueEncoderServiceDefault.java | 4 +- .../wicket/model/util/PageParameterUtils.java | 16 --- 30 files changed, 377 insertions(+), 312 deletions(-) diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/Bookmark.java b/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/Bookmark.java index c23d21eb6c..5d7036628a 100644 --- a/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/Bookmark.java +++ b/api/applib/src/main/java/org/apache/isis/applib/services/bookmark/Bookmark.java @@ -75,19 +75,19 @@ public final class Bookmark implements Oid { public static Bookmark forLogicalTypeNameAndIdentifier( final @NonNull String logicalTypeName, - final @NonNull String identifier) { + final @NonNull String urlSafeIdentifier) { return new Bookmark( logicalTypeName, - identifier, + urlSafeIdentifier, /*hintId*/null); } public static Bookmark forLogicalTypeAndIdentifier( final @NonNull LogicalType logicalType, - final @NonNull String identifier) { + final @NonNull String urlSafeIdentifier) { return Bookmark.forLogicalTypeNameAndIdentifier( logicalType.getLogicalTypeName(), - identifier); + urlSafeIdentifier); } public static Bookmark forOidDto(final @NonNull OidDto oidDto) { @@ -104,12 +104,12 @@ public final class Bookmark implements Oid { private Bookmark( final String logicalTypeName, - final String identifier, + final String urlSafeIdentifier, final String hintId) { this.logicalTypeName = logicalTypeName; - this.identifier = identifier; + this.identifier = urlSafeIdentifier; this.hintId = hintId; - this.hashCode = Objects.hash(logicalTypeName, identifier); + this.hashCode = Objects.hash(logicalTypeName, urlSafeIdentifier); } // -- PARSE diff --git a/core/metamodel/src/main/adoc/modules/metamodel/images/managed-object-diagram.drawio.svg b/core/metamodel/src/main/adoc/modules/metamodel/images/managed-object-diagram.drawio.svg index bd01036df0..55aa26123a 100644 --- a/core/metamodel/src/main/adoc/modules/metamodel/images/managed-object-diagram.drawio.svg +++ b/core/metamodel/src/main/adoc/modules/metamodel/images/managed-object-diagram.drawio.svg @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Do not edit this file with editors other than diagrams.net --> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1391px" height="591px" viewBox="-0.5 -0.5 1391 591" content="<mxfile host="Electron" modified="2022-09-01T14:29:56.898Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.2.3 Chrome/102.0.5005.167 Electron/19.0.11 Safari/537.36" etag="cwu7jfOeW0fUMNdx5SSP" version="20.2.3" type="device&qu [...] \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1391px" height="591px" viewBox="-0.5 -0.5 1391 591" content="<mxfile host="Electron" modified="2022-09-02T03:02:42.988Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.2.3 Chrome/102.0.5005.167 Electron/19.0.11 Safari/537.36" etag="lW1AIfm3Z-gm0gO0WKht" version="20.2.3" type="device&qu [...] \ No newline at end of file diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/entity/EntityFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/entity/EntityFacet.java index f3537fb078..9daafec0df 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/entity/EntityFacet.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/entity/EntityFacet.java @@ -20,13 +20,17 @@ package org.apache.isis.core.metamodel.facets.object.entity; import java.lang.reflect.Method; import java.util.Optional; +import java.util.function.Function; import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; import org.apache.isis.applib.query.Query; import org.apache.isis.applib.services.bookmark.Bookmark; +import org.apache.isis.applib.services.bookmark.IdStringifier; import org.apache.isis.applib.services.repository.EntityState; import org.apache.isis.commons.collections.Can; +import org.apache.isis.commons.internal.base._Casts; import org.apache.isis.commons.internal.exceptions._Exceptions; import org.apache.isis.core.config.beans.PersistenceStack; import org.apache.isis.core.metamodel.facetapi.Facet; @@ -35,12 +39,44 @@ import org.apache.isis.core.metamodel.object.ManagedObject; import org.apache.isis.core.metamodel.object.MmSpecUtil; import org.apache.isis.core.metamodel.spec.ObjectSpecification; +import lombok.NonNull; + /** * Indicates that this class is managed by a persistence context. * @since 2.0 */ public interface EntityFacet extends Facet { + @lombok.Value(staticConstructor = "of") + static class PrimaryKeyType<T> { + private final @NonNull Class<?> owningEntityClass; + private final @NonNull IdStringifier<T> idStringifier; + private final @NonNull Class<T> primaryKeyClass; + public String enstring(final T primaryKey) { + return idStringifier.enstring(primaryKey); + } + public String enstringWithCast(final Object primaryKey) { + return _Casts.castTo(primaryKeyClass, primaryKey) + .map(idStringifier::enstring) + .orElseThrow(()->_Exceptions.illegalArgument( + "failed to cast primary-key '%s' to expected type %s", + ""+primaryKey, + primaryKeyClass.getName())); + } + public T destring(final String stringifiedPrimaryKey) { + return idStringifier.destring(owningEntityClass, stringifiedPrimaryKey); + } + public static <T> PrimaryKeyType<T> getInstance( + final @NonNull Class<?> owningEntityClass, + final @NonNull Function<Class<T>, IdStringifier<T>> stringifierLookup, + final @NonNull Class<T> primaryKeyClass){ + return of( + owningEntityClass, + stringifierLookup.apply(primaryKeyClass), + _Casts.uncheckedCast(ClassUtils.resolvePrimitiveIfNecessary(primaryKeyClass))); + } + } + /** * The {@link ObjectSpecification} of the entity type this * facet is associated with. diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java index 3c402b246a..9ba54f5acb 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java @@ -132,16 +132,16 @@ implements ValueFacet<T> { iaProvider.currentInteractionContext().orElse(null)); } - // -- TO STRING SERIALIZATION + // -- TO/FROM STRING SERIALIZATION @Override - public T fromEncodedString(final Format format, final String encodedData) { - return valueSerializer.fromEncodedString(format, encodedData); + public final T destring(final Format format, final String encodedData) { + return valueSerializer.destring(format, encodedData); } @Override - public String toEncodedString(final Format format, final T value) { - return valueSerializer.toEncodedString(format, value); + public final String enstring(final Format format, final T value) { + return valueSerializer.enstring(format, value); } // -- ORDER RELATION diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializer.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializer.java index a3ee0d05fc..772e8d3429 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializer.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializer.java @@ -18,21 +18,26 @@ */ package org.apache.isis.core.metamodel.facets.object.value; +import org.springframework.lang.Nullable; + +import lombok.NonNull; + public interface ValueSerializer<T> { public enum Format { JSON, + URL_SAFE, //XML } /** * Converts a string of provided {@link Format} to an instance of the object. */ - T fromEncodedString(Format format, String encodedData); + T destring(@NonNull Format format, @NonNull String encodedData); /** * Converts the provided object into provided {@link Format}. */ - String toEncodedString(Format format, T value); + String enstring(@NonNull Format format, @Nullable T value); } diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializerDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializerDefault.java index 8d07fd0212..94f3c644db 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializerDefault.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueSerializerDefault.java @@ -18,10 +18,13 @@ */ package org.apache.isis.core.metamodel.facets.object.value; +import org.springframework.lang.Nullable; + import org.apache.isis.applib.value.semantics.ValueDecomposition; import org.apache.isis.applib.value.semantics.ValueSemanticsProvider; -import org.apache.isis.commons.internal.assertions._Assert; import org.apache.isis.commons.internal.base._Casts; +import org.apache.isis.commons.internal.base._Strings; +import org.apache.isis.commons.internal.exceptions._Exceptions; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -35,21 +38,34 @@ implements ValueSerializer<T> { private final @NonNull ValueSemanticsProvider<T> semantics; @Override - public T fromEncodedString(final Format format, final String encodedData) { - _Assert.assertNotNull(encodedData); + public T destring(final @NonNull Format format, final @NonNull String encodedData) { if (ENCODED_NULL.equals(encodedData)) { return null; - } else { + } + switch(format) { + case JSON: return semantics.compose( ValueDecomposition.fromJson(semantics.getSchemaValueType(), encodedData)); + case URL_SAFE: + //TODO could use IdStringifiers instead + return destring(Format.JSON, _Strings.base64UrlDecode(encodedData)); } + throw _Exceptions.unmatchedCase(format); } @Override - public String toEncodedString(final Format format, final T value) { - return value == null - ? ENCODED_NULL - : semantics.decompose(_Casts.uncheckedCast(value)).toJson(); + public String enstring(final @NonNull Format format, final @Nullable T value) { + if(value == null) { + return ENCODED_NULL; + } + switch(format) { + case JSON: + return semantics.decompose(_Casts.uncheckedCast(value)).toJson(); + case URL_SAFE: + //TODO could use IdStringifiers instead + return _Strings.base64UrlEncode(enstring(Format.JSON, value)); + } + throw _Exceptions.unmatchedCase(format); } } diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/viewmodel/ViewModelFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/viewmodel/ViewModelFacetAbstract.java index 8c3c1d8328..32c153b06e 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/viewmodel/ViewModelFacetAbstract.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/viewmodel/ViewModelFacetAbstract.java @@ -101,9 +101,7 @@ implements ViewModelFacet { @Override public final Bookmark serializeToBookmark(final @NonNull ManagedObject managedObject) { - return Bookmark.forLogicalTypeAndIdentifier( - managedObject.getSpecification().getLogicalType(), - serialize(managedObject)); + return managedObject.createBookmark(serialize(managedObject)); } protected abstract @NonNull String serialize(@NonNull ManagedObject managedObject); diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObject.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObject.java index 8997ef491a..ef2646d3af 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObject.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObject.java @@ -30,6 +30,7 @@ import org.apache.isis.commons.internal.exceptions._Exceptions; import org.apache.isis.core.metamodel.context.HasMetaModelContext; import org.apache.isis.core.metamodel.facets.object.icon.ObjectIcon; import org.apache.isis.core.metamodel.object.ManagedObject.Specialization.BookmarkPolicy; +import org.apache.isis.core.metamodel.spec.HasObjectSpecification; import org.apache.isis.core.metamodel.spec.ObjectSpecification; import org.apache.isis.core.metamodel.specloader.SpecificationLoader; @@ -50,7 +51,8 @@ import lombok.extern.log4j.Log4j2; public interface ManagedObject extends Bookmarkable, - HasMetaModelContext { + HasMetaModelContext, + HasObjectSpecification { /** * ManagedObject specializations have varying contract/behavior. @@ -316,6 +318,7 @@ extends /** * Returns the specification that details the structure (meta-model) of this object. */ + @Override ObjectSpecification getSpecification(); /** @@ -351,15 +354,6 @@ extends */ String getTitle(); - // -- SHORTCUT - ELEMENT SPECIFICATION - - /** - * As used for the element type of collections. - */ - default Optional<ObjectSpecification> getElementSpecification() { - return getSpecification().getElementSpecification(); - } - // -- SHORTCUT - ICON /** diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObjects.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObjects.java index ee6f7b2be3..f642afe7db 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObjects.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/ManagedObjects.java @@ -22,6 +22,7 @@ import java.lang.reflect.Method; import java.util.Comparator; import java.util.Objects; import java.util.Optional; +import java.util.UUID; import java.util.function.Supplier; import org.springframework.lang.Nullable; @@ -161,6 +162,14 @@ public final class ManagedObjects { .orElseThrow(()->_Exceptions.illegalArgument("cannot identify %s", managedObject)); } + /** + * eg. transient entities have no bookmark, so can fallback to UUID + */ + public static Bookmark bookmarkElseUUID(final @Nullable ManagedObject managedObject) { + return bookmark(managedObject) + .orElseGet(()->managedObject.createBookmark(UUID.randomUUID().toString())); + } + /** * @param managedObject * @return optionally a String representing a reference to the <em>identifiable</em> @@ -368,7 +377,7 @@ public final class ManagedObjects { .collect(Can.toCan()); } - + // -- IMPERATIVE TEXT UTILITY diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectService.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectService.java index fd1f168a9f..57e409f01c 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectService.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectService.java @@ -80,9 +80,7 @@ extends _ManagedObjectSpecified { // -- HELPER private Bookmark createBookmark() { - return Bookmark.forLogicalTypeAndIdentifier( - getSpecification().getLogicalType(), - "1"); + return createBookmark("1"); } } \ No newline at end of file diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectValue.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectValue.java index f6795095af..4bcabc9681 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectValue.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/object/_ManagedObjectValue.java @@ -77,15 +77,12 @@ extends _ManagedObjectSpecified { // -- HELPER private ValueFacet<?> valueFacet() { - return getSpecification().valueFacet().orElseThrow(); + return getSpecification().valueFacetElseFail(); } private Bookmark createBookmark() { - //TODO if value semantics providers are enforced to provide an IdStringifier, - // we could use that instead (to generate the second argument)! - return Bookmark.forLogicalTypeAndIdentifier( - getSpecification().getLogicalType(), - valueFacet().toEncodedString(Format.JSON, _Casts.uncheckedCast(getPojo()))); + return createBookmark( + valueFacet().enstring(Format.URL_SAFE, _Casts.uncheckedCast(getPojo()))); } } \ No newline at end of file diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java index 165b141cfa..bf1229edf1 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java @@ -142,7 +142,7 @@ public interface ObjectManager { return ManagedObject.unspecified(); } return spec.isScalar() - ? managedObjectEagerlyBookmarkedIfRequired(spec, pojo) + ? ManagedObject.adaptScalar(spec, pojo) : ManagedObject.packed( spec.getElementSpecification().orElseGet(fallbackElementType), _NullSafe.streamAutodetect(pojo) @@ -173,25 +173,11 @@ public interface ObjectManager { || pojo.getClass().equals(proposedSpec.getCorrespondingClass())) // if actual type matches spec's, we assume, that we don't need to reload, // so this is a shortcut for performance reasons - ? managedObjectEagerlyBookmarkedIfRequired( - proposedSpec, pojo) + ? ManagedObject.adaptScalar(proposedSpec, pojo) // fallback, ignoring proposedSpec : adapt(pojo); return adapter; } - // -- HELPER - - /** - * {@link ManagedObject} factory, that in case of given pojo representing an entity - * and the entityAdaptingMode equals {@link EntityAdaptingMode#isBookmarkable()}, - * then tries to memoize its {@link Bookmark} eagerly - * (otherwise its {@link Bookmark} is lazily resolved). - */ - private static ManagedObject managedObjectEagerlyBookmarkedIfRequired( - final ObjectSpecification spec, - final Object pojo) { - return ManagedObject.adaptScalar(spec, pojo); - } } diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker.java index 1a527d69b0..a3ca1217da 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker.java @@ -42,6 +42,7 @@ public interface ObjectBookmarker { "ObjectBookmarker", _Lists.of( new ObjectBookmarker_builtinHandlers.GuardAgainstOid(), + new ObjectBookmarker_builtinHandlers.BookmarkForNonScalar(), new ObjectBookmarker_builtinHandlers.BookmarkForServices(), new ObjectBookmarker_builtinHandlers.BookmarkForValues(), new ObjectBookmarker_builtinHandlers.BookmarkForViewModels(), diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java index 9130d1b88d..41a2579403 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java @@ -18,16 +18,11 @@ */ package org.apache.isis.core.metamodel.objectmanager.identify; -import java.nio.charset.StandardCharsets; import java.util.UUID; import org.apache.isis.applib.services.bookmark.Bookmark; import org.apache.isis.applib.services.bookmark.Oid; -import org.apache.isis.applib.value.semantics.ValueSemanticsProvider; -import org.apache.isis.commons.internal.base._Bytes; -import org.apache.isis.commons.internal.base._Strings; -import org.apache.isis.commons.internal.exceptions._Exceptions; -import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet; +import org.apache.isis.commons.internal.assertions._Assert; import org.apache.isis.core.metamodel.object.ManagedObject; import org.apache.isis.core.metamodel.object.PackedManagedObject; import org.apache.isis.core.metamodel.objectmanager.identify.ObjectBookmarker.Handler; @@ -37,8 +32,6 @@ import lombok.val; class ObjectBookmarker_builtinHandlers { - public static final String SERVICE_IDENTIFIER = "1"; - static class GuardAgainstOid implements Handler { @Override @@ -55,6 +48,20 @@ class ObjectBookmarker_builtinHandlers { } + static class BookmarkForNonScalar implements Handler { + + @Override + public boolean isHandling(final ManagedObject managedObject) { + return managedObject instanceof PackedManagedObject; + } + + @Override + public Bookmark handle(final ManagedObject managedObject) { + return bookmarkWithRandomUUID(managedObject); + } + + } + static class BookmarkForServices implements Handler { @Override @@ -77,7 +84,8 @@ class ObjectBookmarker_builtinHandlers { @Override public Bookmark handle(final ManagedObject managedObject) { - return managedObject.getBookmark().orElseThrow(); + return managedObject.getBookmark() + .orElseGet(()->bookmarkWithRandomUUID(managedObject)); // transient } } @@ -86,33 +94,14 @@ class ObjectBookmarker_builtinHandlers { @Override public boolean isHandling(final ManagedObject managedObject) { - return managedObject.getSpecification().isValue(); + return managedObject.getSpecialization().isValue(); } @SneakyThrows @Override public Bookmark handle(final ManagedObject managedObject) { - val spec = managedObject.getSpecification(); - val valuePojo = managedObject.getPojo(); - if(valuePojo==null) { - return Bookmark.forLogicalTypeAndIdentifier(spec.getLogicalType(), "{}"); - } - - val valueFacet = spec.valueFacet().orElse(null); - ValueSemanticsProvider<Object> composer = (ValueSemanticsProvider) valueFacet.selectDefaultSemantics() - .orElseThrow(()->_Exceptions.illegalArgument( - "Cannot create a bookmark for the value type %s, " - + "as no appropriate ValueSemanticsProvider could be found.", - managedObject.getSpecification().getCorrespondingClass().getName())); - - val valueAsJson = composer.decompose(managedObject.getPojo()) - .toJson(); - - val identifier = _Strings.ofBytes( - _Bytes.asUrlBase64.apply(valueAsJson.getBytes()), - StandardCharsets.UTF_8); - - return Bookmark.forLogicalTypeAndIdentifier(spec.getLogicalType(), identifier); + _Assert.assertTrue(managedObject.isBookmarkSupported(), ()->"is bookmarkable"); + return managedObject.getBookmark().orElseThrow(); } } @@ -121,9 +110,7 @@ class ObjectBookmarker_builtinHandlers { @Override public boolean isHandling(final ManagedObject managedObject) { - return (managedObject instanceof PackedManagedObject) - ? false - : managedObject.getSpecification().containsFacet(ViewModelFacet.class); + return managedObject.getSpecialization().isViewmodel(); } @Override @@ -134,8 +121,7 @@ class ObjectBookmarker_builtinHandlers { } val spec = managedObject.getSpecification(); - val recreatableObjectFacet = spec.getFacet(ViewModelFacet.class); - return recreatableObjectFacet.serializeToBookmark(managedObject); + return spec.viewmodelFacetElseFail().serializeToBookmark(managedObject); } } @@ -149,10 +135,16 @@ class ObjectBookmarker_builtinHandlers { @Override public Bookmark handle(final ManagedObject managedObject) { - val spec = managedObject.getSpecification(); - val identifier = UUID.randomUUID().toString(); - return Bookmark.forLogicalTypeAndIdentifier(spec.getLogicalType(), identifier); + return bookmarkWithRandomUUID(managedObject); } } + // -- HELPER + + private static Bookmark bookmarkWithRandomUUID(final ManagedObject managedObject) { + val uuid = UUID.randomUUID().toString(); + System.err.printf("called bookmarkWithRandomUUID %s [%s]%n", managedObject.getSpecification(), uuid); + return managedObject.createBookmark(UUID.randomUUID().toString()); + } + } diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/HasObjectSpecification.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/HasObjectSpecification.java new file mode 100644 index 0000000000..c1c33fab84 --- /dev/null +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/HasObjectSpecification.java @@ -0,0 +1,60 @@ +/* + * 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.isis.core.metamodel.spec; + +import java.util.Optional; + +import org.apache.isis.applib.id.LogicalType; +import org.apache.isis.applib.services.bookmark.Bookmark; + +import lombok.NonNull; + +/** + * Introduced as a shortcut provider. + */ +public interface HasObjectSpecification { + + ObjectSpecification getSpecification(); + + // -- SHORTCUTS + + default Class<?> getCorrespondingClass() { + return getSpecification().getCorrespondingClass(); + } + + default LogicalType getLogicalType() { + return getSpecification().getLogicalType(); + } + + default String getLogicalTypeName() { + return getSpecification().getLogicalTypeName(); + } + + /** + * As used for the element type of collections. + */ + default Optional<ObjectSpecification> getElementSpecification() { + return getSpecification().getElementSpecification(); + } + + default Bookmark createBookmark(final @NonNull String urlSafeIdentifier) { + return Bookmark.forLogicalTypeAndIdentifier(getLogicalType(), urlSafeIdentifier); + } + +} diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/snapshot/XmlSnapshot.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/snapshot/XmlSnapshot.java index 93a2ff4cb9..15f5876e44 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/snapshot/XmlSnapshot.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/snapshot/XmlSnapshot.java @@ -41,6 +41,7 @@ import org.apache.isis.applib.ViewModel; import org.apache.isis.applib.exceptions.UnrecoverableException; import org.apache.isis.applib.services.xmlsnapshot.XmlSnapshotService.Snapshot; import org.apache.isis.applib.snapshot.SnapshottableWithInclusions; +import org.apache.isis.commons.internal.base._Strings; import org.apache.isis.commons.internal.codec._DocumentFactories; import org.apache.isis.commons.internal.collections._Maps; import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy; @@ -682,12 +683,12 @@ public class XmlSnapshot implements Snapshot { log.debug("objectToElement(NO): {} is value", log("field", fieldName)); } - final ObjectSpecification fieldNos = field.getElementType(); + final ObjectSpecification fieldSpec = field.getElementType(); // skip fields of type XmlValue - if (fieldNos == null) { + if (fieldSpec == null) { continue eachField; } - if (fieldNos.getFullIdentifier() != null && fieldNos.getFullIdentifier().endsWith("XmlValue")) { + if (fieldSpec.getFullIdentifier() != null && fieldSpec.getFullIdentifier().endsWith("XmlValue")) { continue eachField; } @@ -701,22 +702,17 @@ public class XmlSnapshot implements Snapshot { try { value = valueAssociation.get(adapter, InteractionInitiatedBy.FRAMEWORK); - final ObjectSpecification valueNos = value.getSpecification(); + val valueSpec = value.getSpecification(); // XML - isisMetaModel.setAttributesForValue(xmlValueElement, valueNos.getShortIdentifier()); + isisMetaModel.setAttributesForValue(xmlValueElement, valueSpec.getShortIdentifier()); - // return encoded string, else title. - String valueStr; - val valueFacet = fieldNos.valueFacet().orElse(null); - if (valueFacet != null) { - valueStr = valueFacet.toEncodedString(Format.JSON, value.getPojo()); - } else { - valueStr = value.getTitle(); - } + // value as JSON + @SuppressWarnings("unchecked") + val valueStr = fieldSpec.valueFacetElseFail() + .enstring(Format.JSON, value.getPojo()); - final boolean notEmpty = (valueStr.length() > 0); - if (notEmpty) { + if (_Strings.isNotEmpty(valueStr)) { xmlValueElement.appendChild(getXmlDocument().createTextNode(valueStr)); } else { isisMetaModel.setIsEmptyAttribute(xmlValueElement, true); diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaTimeValueSemanticsProviderTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaTimeValueSemanticsProviderTest.java index 694c732de7..bff7dfaa3b 100644 --- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaTimeValueSemanticsProviderTest.java +++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/JavaTimeValueSemanticsProviderTest.java @@ -25,6 +25,9 @@ import java.util.Locale; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.apache.isis.applib.annotation.TimePrecision; import org.apache.isis.applib.exceptions.recoverable.TextEntryParseException; import org.apache.isis.applib.locale.UserLocale; @@ -35,9 +38,6 @@ import org.apache.isis.applib.value.semantics.ValueSemanticsProvider.Context; import org.apache.isis.core.metamodel.valuesemantics.temporal.LocalDateTimeValueSemantics; import org.apache.isis.core.metamodel.valuesemantics.temporal.legacy.JavaUtilDateValueSemantics; -import static org.junit.Assert.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - import lombok.NonNull; import lombok.val; @@ -146,11 +146,6 @@ extends ValueSemanticsProviderAbstractTestCase<java.util.Date> { return date; } - @Override - public void testValueSerializer_usingJson() { - // TODO fails with NPE - } - @Override protected void assertValueEncodesToJsonAs(final Date a, final String json) { // TODO diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java index 04d5323baa..71581794b1 100644 --- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java +++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java @@ -29,6 +29,14 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import org.apache.isis.applib.services.iactn.InteractionProvider; import org.apache.isis.applib.value.semantics.Parser; @@ -41,16 +49,10 @@ import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting; import org.apache.isis.core.metamodel.context.MetaModelContext; import org.apache.isis.core.metamodel.facets.object.value.ValueSerializer; import org.apache.isis.core.metamodel.facets.object.value.ValueSerializer.Format; -import org.apache.isis.core.metamodel.object.ManagedObject; import org.apache.isis.core.metamodel.facets.object.value.ValueSerializerDefault; +import org.apache.isis.core.metamodel.object.ManagedObject; import org.apache.isis.core.metamodel.valuesemantics.StringValueSemantics; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - import lombok.Getter; public abstract class ValueSemanticsProviderAbstractTestCase<T> { @@ -135,14 +137,17 @@ public abstract class ValueSemanticsProviderAbstractTestCase<T> { } - @Test - public void testValueSerializer_usingJson() { + @ParameterizedTest + @EnumSource(Format.class) + public void testValueSerializer(final Format format) { + assumeValueSemanticsProviderIsSetup(); + final T value = getSample(); - final String encoded = getValueSerializer().toEncodedString(Format.JSON, value); + final String encoded = getValueSerializer().enstring(format, value); assertValueEncodesToJsonAs(value, encoded); - T decoded = getValueSerializer().fromEncodedString(Format.JSON, encoded); + T decoded = getValueSerializer().destring(format, encoded); Optional.ofNullable(semantics.getOrderRelation()) .ifPresentOrElse(rel->Assertions.assertTrue(rel.equals(value, decoded)), @@ -153,21 +158,23 @@ public abstract class ValueSemanticsProviderAbstractTestCase<T> { protected abstract void assertValueEncodesToJsonAs(T a, String json); - @Test - public void testDecodeNULL() throws Exception { + @ParameterizedTest + @EnumSource(Format.class) + public void testDecodeNULL(final Format format) throws Exception { assumeValueSemanticsProviderIsSetup(); final Object newValue = getValueSerializer() - .fromEncodedString(Format.JSON, ValueSerializerDefault.ENCODED_NULL); + .destring(format, ValueSerializerDefault.ENCODED_NULL); assertNull(newValue); } - @Test - public void testEmptyEncoding() { + @ParameterizedTest + @EnumSource(Format.class) + public void testEmptyEncoding(final Format format) { assumeValueSemanticsProviderIsSetup(); assertEquals(ValueSerializerDefault.ENCODED_NULL, getValueSerializer() - .toEncodedString(Format.JSON, null)); + .enstring(format, null)); } @Test diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java index dd5a2de831..063ea58bd9 100644 --- a/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java @@ -25,7 +25,7 @@ import org.apache.isis.core.interaction.IsisModuleCoreInteraction; import org.apache.isis.core.metamodel.IsisModuleCoreMetamodel; import org.apache.isis.core.runtime.events.MetamodelEventService; import org.apache.isis.core.runtime.events.TransactionEventEmitter; -import org.apache.isis.core.runtime.idstringifier.IdStringifierService; +import org.apache.isis.core.runtime.idstringifier.IdStringifierLookupService; import org.apache.isis.core.transaction.IsisModuleCoreTransaction; import org.apache.isis.valuetypes.jodatime.integration.IsisModuleValJodatimeIntegration; @@ -42,7 +42,7 @@ import org.apache.isis.valuetypes.jodatime.integration.IsisModuleValJodatimeInte // @Service's MetamodelEventService.class, TransactionEventEmitter.class, - IdStringifierService.class, + IdStringifierLookupService.class, // @Configuration's diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierService.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierLookupService.java similarity index 68% rename from core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierService.java rename to core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierLookupService.java index dcece158bf..b5101a87e6 100644 --- a/core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierService.java +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/idstringifier/IdStringifierLookupService.java @@ -35,14 +35,13 @@ import org.springframework.stereotype.Service; import org.springframework.util.ClassUtils; import org.apache.isis.applib.annotation.PriorityPrecedence; -import org.apache.isis.applib.annotation.ValueSemantics; import org.apache.isis.applib.services.bookmark.IdStringifier; import org.apache.isis.commons.collections.Can; import org.apache.isis.commons.internal.base._Casts; import org.apache.isis.commons.internal.exceptions._Exceptions; +import org.apache.isis.core.metamodel.facets.object.entity.EntityFacet.PrimaryKeyType; import org.apache.isis.core.runtime.IsisModuleCoreRuntime; -import lombok.NonNull; import lombok.val; /** @@ -51,24 +50,19 @@ import lombok.val; * <p> * This is intended for framework use, there is little reason to call it or override it. * - * @implNote yet does not support per member ValueSemantics selection; - * future work would look for {@link ValueSemantics} annotations on primary key members and - * would then honor {@link ValueSemantics#provider()} attribute, - * to narrow the {@link IdStringifier} search - * * @since 2.0 */ @Service -@Named(IsisModuleCoreRuntime.NAMESPACE + ".IdStringifierService") +@Named(IsisModuleCoreRuntime.NAMESPACE + ".IdStringifierLookupService") @Priority(PriorityPrecedence.MIDPOINT) @Qualifier("Default") -public class IdStringifierService { +public class IdStringifierLookupService { private final Can<IdStringifier<?>> idStringifiers; private final Map<Class<?>, IdStringifier<?>> stringifierByClass = new ConcurrentHashMap<>(); @Inject - public IdStringifierService( + public IdStringifierLookupService( final List<IdStringifier<?>> idStringifiers, final Optional<IdStringifier<Serializable>> idStringifierForSerializableIfAny) { // IdStringifierForSerializable is enforced to go last, so any custom IdStringifier(s) @@ -82,31 +76,23 @@ public class IdStringifierService { this.idStringifiers = Can.ofCollection(idStringifiers); } - public <T> String enstringPrimaryKey(final @NonNull Class<T> primaryKeyType, final @NonNull Object primaryKey) { - val idStringifier = lookupElseFail(ClassUtils.resolvePrimitiveIfNecessary(primaryKeyType)); - return idStringifier.enstring(_Casts.uncheckedCast(primaryKey)); - } - - public <T> T destringPrimaryKey( - final @NonNull Class<T> primaryKeyType, - final @NonNull Class<?> entityClass, - final @NonNull String stringifiedId) { - val idStringifier = lookupElseFail(ClassUtils.resolvePrimitiveIfNecessary(primaryKeyType)); - val primaryKey = idStringifier.destring(entityClass, stringifiedId); - return _Casts.uncheckedCast(primaryKey); + public <T> PrimaryKeyType<T> primaryKeyTypeFor( + final Class<?> entityClass, final Class<T> primaryKeyType) { + return PrimaryKeyType.getInstance(entityClass, + this::lookupIdStringifierElseFail, + primaryKeyType); } - // -- HELPER - - private <T> IdStringifier<T> lookupElseFail(final Class<T> candidateValueClass) { - return lookup(candidateValueClass) + public <T> IdStringifier<T> lookupIdStringifierElseFail(final Class<T> candidateValueClass) { + return lookupIdStringifier(candidateValueClass) .orElseThrow(() -> _Exceptions.noSuchElement( "Could not locate an IdStringifier to handle '%s'", candidateValueClass)); } - private <T> Optional<IdStringifier<T>> lookup(final Class<T> candidateValueClass) { - val idStringifier = stringifierByClass.computeIfAbsent(candidateValueClass, aClass -> { + public <T> Optional<IdStringifier<T>> lookupIdStringifier(final Class<T> candidateValueClass) { + val idStringifier = stringifierByClass.computeIfAbsent( + ClassUtils.resolvePrimitiveIfNecessary(candidateValueClass), aClass -> { for (val candidateStringifier : idStringifiers) { if (candidateStringifier.getCorrespondingClass().isAssignableFrom(candidateValueClass)) { return candidateStringifier; diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java index 2112294f9c..a11ae430f2 100644 --- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandDtoFactoryDefault.java @@ -169,7 +169,8 @@ public class CommandDtoFactoryDefault implements CommandDtoFactory { dto.setUsername(userService.currentUserNameElseNobody()); dto.setTimestamp(clockService.getClock().nowAsXmlGregorianCalendar()); - final Bookmark bookmark = ManagedObjects.bookmarkElseFail(targetHead.getOwner()); + // transient entities have no bookmark, so fallback to UUID + final Bookmark bookmark = ManagedObjects.bookmarkElseUUID(targetHead.getOwner()); final OidsDto targetOids = CommandDtoUtils.targetsFor(dto); targetOids.getOid().add(bookmark.toOidDto()); diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/interaction/InteractionDtoFactoryDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/interaction/InteractionDtoFactoryDefault.java index a3201a6aac..96816d0ea5 100644 --- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/interaction/InteractionDtoFactoryDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/interaction/InteractionDtoFactoryDefault.java @@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.apache.isis.applib.annotation.PriorityPrecedence; -import org.apache.isis.applib.services.bookmark.Bookmark; import org.apache.isis.applib.services.iactn.Interaction; import org.apache.isis.applib.services.iactn.InteractionProvider; import org.apache.isis.applib.services.user.UserService; @@ -36,7 +35,6 @@ import org.apache.isis.applib.util.schema.CommandDtoUtils; import org.apache.isis.applib.util.schema.InteractionDtoUtils; import org.apache.isis.commons.collections.Can; import org.apache.isis.commons.internal.assertions._Assert; -import org.apache.isis.commons.internal.exceptions._Exceptions; import org.apache.isis.core.metamodel.execution.InteractionInternal; import org.apache.isis.core.metamodel.interactions.InteractionHead; import org.apache.isis.core.metamodel.object.ManagedObject; @@ -86,8 +84,10 @@ public class InteractionDtoFactoryDefault implements InteractionDtoFactory { final int nextEventSequence = ((InteractionInternal) interaction).getThenIncrementExecutionSequence(); val owner = head.getOwner(); - final Bookmark targetBookmark = owner.getBookmark() - .orElseThrow(()->_Exceptions.noSuchElement("Object provides no Bookmark: %s", owner)); + + // transient entities have no bookmark, so fallback to UUID + val targetBookmark = ManagedObjects.bookmarkElseUUID(owner); + //.orElseThrow(()->_Exceptions.noSuchElement("Object provides no Bookmark: %s", owner)); final String currentUser = userService.currentUserNameElseNobody(); @@ -132,8 +132,9 @@ public class InteractionDtoFactoryDefault implements InteractionDtoFactory { final Interaction interaction = interactionProviderProvider.get().currentInteractionElseFail(); final int nextEventSequence = ((InteractionInternal) interaction).getThenIncrementExecutionSequence(); - final Bookmark targetBookmark = targetAdapter.getBookmark() - .orElseThrow(()->_Exceptions.noSuchElement("Object provides no Bookmark: %s", targetAdapter)); + // transient entities have no bookmark, so fallback to UUID + val targetBookmark = ManagedObjects.bookmarkElseUUID(targetAdapter); + //.orElseThrow(()->_Exceptions.noSuchElement("Object provides no Bookmark: %s", owner)); final String currentUser = userService.currentUserNameElseNobody(); diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/ObjectMementoServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/ObjectMementoServiceDefault.java index d398c0e8ed..cc7edb7368 100644 --- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/ObjectMementoServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/ObjectMementoServiceDefault.java @@ -82,7 +82,7 @@ public class ObjectMementoServiceDefault implements ObjectMementoService { if(mementoAdapter==null) { // sonar-ignore-on (fails to detect this as null guard) return ManagedObjects.isSpecified(adapter) - ? new ObjectMementoForEmpty(adapter.getSpecification().getLogicalType()) + ? new ObjectMementoForEmpty(adapter.getLogicalType()) : null; // sonar-ignore-on } @@ -96,7 +96,7 @@ public class ObjectMementoServiceDefault implements ObjectMementoService { .collect(Collectors.toCollection(ArrayList::new)); // ArrayList is serializable return ObjectMementoCollection.of( listOfMementos, - packedAdapter.getSpecification().getLogicalType()); + packedAdapter.getLogicalType()); } @Override @@ -106,7 +106,7 @@ public class ObjectMementoServiceDefault implements ObjectMementoService { } val mementoAdapter = _ObjectMemento.createOrNull(paramAdapter); if(mementoAdapter==null) { - return new ObjectMementoForEmpty(paramAdapter.getSpecification().getLogicalType()); + return new ObjectMementoForEmpty(paramAdapter.getLogicalType()); } return ObjectMementoAdapter.of(mementoAdapter); } diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/_ObjectMemento.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/_ObjectMemento.java index 4cf1ed7816..80714a4f9b 100644 --- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/_ObjectMemento.java +++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/memento/_ObjectMemento.java @@ -22,7 +22,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; -import java.util.function.Function; import org.springframework.lang.Nullable; @@ -45,9 +44,7 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification; import org.apache.isis.core.metamodel.specloader.SpecificationLoader; import org.apache.isis.core.metamodel.util.Facets; -import lombok.AccessLevel; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.val; import lombok.extern.log4j.Log4j2; @@ -112,55 +109,56 @@ final class _ObjectMemento implements HasLogicalType, Serializable { return memento.recreateStrategy.equals(memento, otherMemento); } }, - /** - * represents a list of objects - */ - VECTOR { - - @Override - public ManagedObject asAdapter( - final _ObjectMemento memento, - final MetaModelContext mmc) { - - // I believe this code path is no longer reachable - throw _Exceptions.unexpectedCodeReach(); - -// final Can<ManagedObject> managedObjects = -// _NullSafe.stream(memento.list) -// .map(Functions.toManagedObject(mmc)) -// .collect(Can.toCan()); +// /** +// * represents a list of objects +// */ +// VECTOR { // -// val commonSpec = ManagedObjects.commonSpecification(managedObjects) -// .orElseGet(()->mmc.getSpecificationLoader().loadSpecification(Object.class)); +// @Override +// public ManagedObject asAdapter( +// final _ObjectMemento memento, +// final MetaModelContext mmc) { // -// return ManagedObject.packed(commonSpec, managedObjects); - } - - @Override - public Bookmark asPseudoBookmark(final _ObjectMemento memento) { - return Bookmark.forLogicalTypeNameAndIdentifier( - memento.getLogicalTypeName(), - memento.list.toString()); - } - - @Override - public int hashCode(final _ObjectMemento memento) { - return memento.list.hashCode(); - } - - @Override - public boolean equals(final _ObjectMemento memento, final Object other) { - if (!(other instanceof _ObjectMemento)) { - return false; - } - final _ObjectMemento otherMemento = (_ObjectMemento) other; - if(otherMemento.cardinality != VECTOR) { - return false; - } - return memento.list.equals(otherMemento.list); - } - - }; +// // I believe this code path is no longer reachable +// throw _Exceptions.unexpectedCodeReach(); +// +//// final Can<ManagedObject> managedObjects = +//// _NullSafe.stream(memento.list) +//// .map(Functions.toManagedObject(mmc)) +//// .collect(Can.toCan()); +//// +//// val commonSpec = ManagedObjects.commonSpecification(managedObjects) +//// .orElseGet(()->mmc.getSpecificationLoader().loadSpecification(Object.class)); +//// +//// return ManagedObject.packed(commonSpec, managedObjects); +// } +// +// @Override +// public Bookmark asPseudoBookmark(final _ObjectMemento memento) { +// return Bookmark.forLogicalTypeNameAndIdentifier( +// memento.getLogicalTypeName(), +// memento.list.toString()); +// } +// +// @Override +// public int hashCode(final _ObjectMemento memento) { +// return memento.list.hashCode(); +// } +// +// @Override +// public boolean equals(final _ObjectMemento memento, final Object other) { +// if (!(other instanceof _ObjectMemento)) { +// return false; +// } +// final _ObjectMemento otherMemento = (_ObjectMemento) other; +// if(otherMemento.cardinality != VECTOR) { +// return false; +// } +// return memento.list.equals(otherMemento.list); +// } +// +// } + ; void ensure(final Cardinality sort) { if(this == sort) { @@ -192,14 +190,17 @@ final class _ObjectMemento implements HasLogicalType, Serializable { final _ObjectMemento memento, final MetaModelContext mmc) { - val valueSerializer = mmc.getSpecificationLoader() + val valueSpec = mmc.getSpecificationLoader() .specForLogicalType(memento.logicalType) - .flatMap(spec->Facets.valueSerializer(spec, spec.getCorrespondingClass())) .orElseThrow(()->_Exceptions.unrecoverable( - "logical type %s is expected to have a ValueFacet", memento.logicalType)); + "logical type %s is not recognized", memento.logicalType)); - return mmc.getObjectManager().adapt( - valueSerializer.fromEncodedString(Format.JSON, memento.encodableValue)); + val valueSerializer = Facets.valueSerializer(valueSpec, valueSpec.getCorrespondingClass()) + .orElseThrow(()->_Exceptions.unrecoverable( + "logical type %s is expected to have a value semantics", memento.logicalType)); + + return ManagedObject.value(valueSpec, + valueSerializer.destring(Format.URL_SAFE, memento.encodableValue)); } @Override @@ -413,9 +414,11 @@ final class _ObjectMemento implements HasLogicalType, Serializable { final ArrayList<_ObjectMemento> list, final LogicalType logicalType) { - this.cardinality = Cardinality.VECTOR; - this.list = list; - this.logicalType = logicalType; + throw _Exceptions.unexpectedCodeReach(); + + //this.cardinality = Cardinality.VECTOR; +// this.list = list; +// this.logicalType = logicalType; } private _ObjectMemento(final Bookmark bookmark, final SpecificationLoader specificationLoader) { @@ -482,7 +485,7 @@ final class _ObjectMemento implements HasLogicalType, Serializable { .orElse(null); val isEncodable = valueSerializer != null; if (isEncodable) { - encodableValue = valueSerializer.toEncodedString(Format.JSON, _Casts.uncheckedCast(adapter.getPojo())); + encodableValue = valueSerializer.enstring(Format.URL_SAFE, _Casts.uncheckedCast(adapter.getPojo())); recreateStrategy = RecreateStrategy.VALUE; return; } @@ -578,29 +581,6 @@ final class _ObjectMemento implements HasLogicalType, Serializable { return cardinality.equals(this, obj); } - // -- FUNCTIONS - - @NoArgsConstructor(access = AccessLevel.PRIVATE) - private static final class Functions { - - private static Function<_ObjectMemento, ManagedObject> toManagedObject( - final MetaModelContext mmc) { - - return memento->{ - if(memento == null) { - return ManagedObject.unspecified(); - } - val objectAdapter = memento - .reconstructObject(mmc); - if(objectAdapter == null) { - return ManagedObject.unspecified(); - } - return objectAdapter; - }; - } - - } - private void ensureScalar() { getCardinality().ensure(Cardinality.SCALAR); } diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnEntityStateProvider.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnEntityStateProvider.java index 0e2fa29a70..25a86cc6e3 100644 --- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnEntityStateProvider.java +++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/entities/DnEntityStateProvider.java @@ -67,7 +67,8 @@ public class DnEntityStateProvider implements JdoFacetContext { return EntityState.NOT_PERSISTABLE; } - if (pojo!=null && pojo instanceof Persistable) { + if (pojo!=null + && pojo instanceof Persistable) { val persistable = (Persistable) pojo; val isDeleted = persistable.dnIsDeleted(); if(isDeleted) { @@ -75,8 +76,11 @@ public class DnEntityStateProvider implements JdoFacetContext { } val isPersistent = persistable.dnIsPersistent(); if(isPersistent) { - return persistable.dnGetObjectId()!=null - ? EntityState.PERSISTABLE_ATTACHED + val oid = persistable.dnGetObjectId(); + return oid!=null + ? persistable.dnGetStateManager().isNew(persistable) + ? EntityState.PERSISTABLE_NEW + : EntityState.PERSISTABLE_ATTACHED : EntityState.PERSISTABLE_NEW; } return EntityState.PERSISTABLE_DETACHED; diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java index 6865d76cd1..b86cbe86f1 100644 --- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java +++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java @@ -55,14 +55,17 @@ import org.apache.isis.core.metamodel.facets.object.entity.EntityFacet; import org.apache.isis.core.metamodel.object.ManagedObject; import org.apache.isis.core.metamodel.objectmanager.ObjectManager; import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher; -import org.apache.isis.core.runtime.idstringifier.IdStringifierService; +import org.apache.isis.core.runtime.idstringifier.IdStringifierLookupService; import org.apache.isis.persistence.jdo.datanucleus.entities.DnEntityStateProvider; import org.apache.isis.persistence.jdo.metamodel.facets.object.persistencecapable.JdoPersistenceCapableFacetFactory; import org.apache.isis.persistence.jdo.provider.entities.JdoFacetContext; import org.apache.isis.persistence.jdo.spring.integration.TransactionAwarePersistenceManagerFactoryProxy; +import lombok.AccessLevel; +import lombok.Getter; import lombok.NonNull; import lombok.val; +import lombok.experimental.Accessors; import lombok.extern.log4j.Log4j2; /** @@ -80,10 +83,18 @@ implements EntityFacet { @Inject private ObjectManager objectManager; @Inject private ExceptionRecognizerService exceptionRecognizerService; @Inject private JdoFacetContext jdoFacetContext; - @Inject private IdStringifierService idStringifierService; + @Inject private ObjectLifecyclePublisher objectLifecyclePublisher; + + @Getter(value = AccessLevel.PROTECTED) @Accessors(fluent = true) + @Inject private IdStringifierLookupService idStringifierLookupService; private final Class<?> entityClass; + // lazily looks up the primaryKeyTypeFor (needs a PersistenceManager) + @Getter(lazy=true, value = AccessLevel.PROTECTED) @Accessors(fluent = true) + private final PrimaryKeyType<?> primaryKeyTypeForDecoding = idStringifierLookupService() + .primaryKeyTypeFor(entityClass, primaryKeyTypeFor(entityClass)); + public JdoEntityFacet( final FacetHolder holder, final Class<?> entityClass) { super(EntityFacet.class, holder); @@ -95,6 +106,19 @@ implements EntityFacet { return PersistenceStack.JDO; } + /* OPTIMIZATION + * even though the IdStringifierLookupService already holds a lookup map, + * for performance reasons, + * we define another one local to the context of the associated entity class */ + private final Map<Class<?>, PrimaryKeyType<?>> primaryKeyTypesForEncoding = new ConcurrentHashMap<>(); + private final PrimaryKeyType<?> primaryKeyTypeForEncoding(final @NonNull Object oid) { + val actualPrimaryKeyClass = oid.getClass(); + val primaryKeyType = primaryKeyTypesForEncoding.computeIfAbsent(actualPrimaryKeyClass, key-> + idStringifierLookupService() + .primaryKeyTypeFor(entityClass, actualPrimaryKeyClass)); + return primaryKeyType; + } + @Override public Optional<String> identifierFor(final Object pojo) { @@ -105,9 +129,10 @@ implements EntityFacet { val pm = getPersistenceManager(); var primaryKeyIfAny = pm.getObjectId(pojo); - return Optional.ofNullable(primaryKeyIfAny) + val idIfAny = Optional.ofNullable(primaryKeyIfAny) .map(primaryKey-> - idStringifierService.enstringPrimaryKey(primaryKey.getClass(), primaryKey)); + primaryKeyTypeForEncoding(primaryKey).enstringWithCast(primaryKey)); + return idIfAny; } @Override @@ -119,8 +144,7 @@ implements EntityFacet { try { val persistenceManager = getPersistenceManager(); - val primaryKey = idStringifierService - .destringPrimaryKey(primaryKeyTypeFor(entityClass), entityClass, bookmark.getIdentifier()); + val primaryKey = primaryKeyTypeForDecoding().destring(bookmark.getIdentifier()); val fetchPlan = persistenceManager.getFetchPlan(); fetchPlan.addGroup(FetchGroup.DEFAULT); @@ -364,13 +388,11 @@ implements EntityFacet { private Can<ManagedObject> fetchWithinTransaction(final Supplier<List<?>> fetcher) { - val objectLifecyclePublisher = getFacetHolder().getServiceRegistry() - .lookupServiceElseFail(ObjectLifecyclePublisher.class); - return getTransactionalProcessor().callWithinCurrentTransactionElseCreateNew( ()->_NullSafe.stream(fetcher.get()) .map(fetchedObject->adopt(objectLifecyclePublisher, fetchedObject)) .collect(Can.toCan())) + .ifFailureFail() .getValue().orElseThrow(); } @@ -393,5 +415,4 @@ implements EntityFacet { } } - } diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacet.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacet.java index 7c764ec931..1eb57800fa 100644 --- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacet.java +++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacet.java @@ -21,6 +21,7 @@ package org.apache.isis.persistence.jpa.integration.entity; import java.lang.reflect.Method; import java.util.Optional; +import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceException; import javax.persistence.PersistenceUnitUtil; @@ -34,7 +35,6 @@ import org.apache.isis.applib.query.AllInstancesQuery; import org.apache.isis.applib.query.NamedQuery; import org.apache.isis.applib.query.Query; import org.apache.isis.applib.services.bookmark.Bookmark; -import org.apache.isis.applib.services.registry.ServiceRegistry; import org.apache.isis.applib.services.repository.EntityState; import org.apache.isis.commons.collections.Can; import org.apache.isis.commons.internal.base._Casts; @@ -45,7 +45,7 @@ import org.apache.isis.core.metamodel.facetapi.FacetAbstract; import org.apache.isis.core.metamodel.facetapi.FacetHolder; import org.apache.isis.core.metamodel.facets.object.entity.EntityFacet; import org.apache.isis.core.metamodel.object.ManagedObject; -import org.apache.isis.core.runtime.idstringifier.IdStringifierService; +import org.apache.isis.core.runtime.idstringifier.IdStringifierLookupService; import lombok.NonNull; import lombok.val; @@ -56,19 +56,22 @@ public class JpaEntityFacet extends FacetAbstract implements EntityFacet { + // self managed injections via constructor + @Inject private JpaContext jpaContext; + @Inject private IdStringifierLookupService idStringifierLookupService; + private final Class<?> entityClass; - private final ServiceRegistry serviceRegistry; - private final IdStringifierService idStringifierService; + private PrimaryKeyType<?> primaryKeyType; protected JpaEntityFacet( final FacetHolder holder, - final Class<?> entityClass, - final @NonNull ServiceRegistry serviceRegistry) { - + final Class<?> entityClass) { super(EntityFacet.class, holder, Precedence.HIGH); + getServiceInjector().injectServicesInto(this); + this.entityClass = entityClass; - this.serviceRegistry = serviceRegistry; - this.idStringifierService = serviceRegistry.lookupServiceElseFail(IdStringifierService.class); + this.primaryKeyType = idStringifierLookupService + .primaryKeyTypeFor(entityClass, getPrimaryKeyType()); } // -- ENTITY FACET @@ -91,7 +94,7 @@ public class JpaEntityFacet return Optional.ofNullable(primaryKeyIfAny) .map(primaryKey-> - idStringifierService.enstringPrimaryKey(getPrimaryKeyType(), primaryKey)); + primaryKeyType.enstringWithCast(primaryKey)); } @Override @@ -99,8 +102,7 @@ public class JpaEntityFacet log.debug("fetchEntity; bookmark={}", bookmark); - val primaryKey = idStringifierService - .destringPrimaryKey(getPrimaryKeyType(), entityClass, bookmark.getIdentifier()); + val primaryKey = primaryKeyType.destring(bookmark.getIdentifier()); val entityManager = getEntityManager(); val entityPojo = entityManager.find(entityClass, primaryKey); @@ -311,12 +313,8 @@ public class JpaEntityFacet // -- DEPENDENCIES - protected JpaContext getJpaContext() { - return serviceRegistry.lookupServiceElseFail(JpaContext.class); - } - protected EntityManager getEntityManager() { - return getJpaContext().getEntityManagerByManagedType(entityClass); + return jpaContext.getEntityManagerByManagedType(entityClass); } protected PersistenceUnitUtil getPersistenceUnitUtil(final EntityManager entityManager) { diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java index ca1ed4ed6d..cf482fab56 100644 --- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java +++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/entity/JpaEntityFacetFactory.java @@ -49,7 +49,7 @@ extends FacetFactoryAbstract { } addFacet( - new JpaEntityFacet(facetHolder, cls, getServiceRegistry())); + new JpaEntityFacet(facetHolder, cls)); } } diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java index 6608afeb62..72d006b5d1 100644 --- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java +++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/valuerender/JsonValueEncoderServiceDefault.java @@ -127,7 +127,7 @@ public class JsonValueEncoderServiceDefault implements JsonValueEncoderService { final ValueSerializer<?> valueSerializer) { if (valueRepr.isString()) { val recoveredValue = Try.call(()-> - valueSerializer.fromEncodedString(Format.JSON, valueRepr.asString())) + valueSerializer.destring(Format.JSON, valueRepr.asString())) .mapFailure(ex->_Exceptions .illegalArgument(ex, "Unable to parse value %s as String", valueRepr)) .ifFailureFail() @@ -235,7 +235,7 @@ public class JsonValueEncoderServiceDefault implements JsonValueEncoderService { // else return Facets.valueSerializerElseFail(objectSpec, cls) - .toEncodedString(Format.JSON, _Casts.uncheckedCast(adapter.getPojo())); + .enstring(Format.JSON, _Casts.uncheckedCast(adapter.getPojo())); } /** diff --git a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/util/PageParameterUtils.java b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/util/PageParameterUtils.java index 44075d5f0b..3fe8b00bfe 100644 --- a/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/util/PageParameterUtils.java +++ b/viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/util/PageParameterUtils.java @@ -33,10 +33,8 @@ import org.springframework.lang.Nullable; import org.apache.isis.applib.Identifier; import org.apache.isis.applib.services.bookmark.Bookmark; import org.apache.isis.commons.collections.Can; -import org.apache.isis.commons.internal.base._Casts; import org.apache.isis.commons.internal.base._Strings; import org.apache.isis.core.metamodel.context.MetaModelContext; -import org.apache.isis.core.metamodel.facets.object.value.ValueSerializer.Format; import org.apache.isis.core.metamodel.object.ManagedObject; import org.apache.isis.core.metamodel.object.ManagedObjects; import org.apache.isis.core.metamodel.spec.ObjectSpecification; @@ -210,13 +208,6 @@ public class PageParameterUtils { if(adapter == null) { return NULL_ARG; } - - final ObjectSpecification objSpec = adapter.getSpecification(); - if(objSpec.isValue()) { - return Facets.valueSerializerElseFail(objSpec, objSpec.getCorrespondingClass()) - .toEncodedString(Format.JSON, _Casts.uncheckedCast(adapter.getPojo())); - } - return ManagedObjects.stringify(adapter).orElse(null); } @@ -227,13 +218,6 @@ public class PageParameterUtils { if(NULL_ARG.equals(encoded)) { return null; } - - if(objSpec.isValue()) { - return ManagedObject.value(objSpec, - Facets.valueSerializerElseFail(objSpec, objSpec.getCorrespondingClass()) - .fromEncodedString(Format.JSON, encoded)); - } - try { return Bookmark.parseUrlEncoded(encoded) .flatMap(mmc::loadObject)
