public record ObjectManager()

in core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectManager.java [63:262]


public record ObjectManager(
        MetaModelContext mmc,
        ChainOfResponsibility<ObjectSpecification, ManagedObject> objectCreator,
        ChainOfResponsibility<ProtoObject, ManagedObject> objectLoader,
        ChainOfResponsibility<BulkLoadRequest, Can<ManagedObject>> objectBulkLoader,
        ChainOfResponsibility<MementoRecreateRequest, ManagedObject> objectDementifier
        ) implements HasMetaModelContext {

    public record BulkLoadRequest(
            ObjectSpecification objectSpecification,
            Query<?> query) {
    }

    public record MementoRecreateRequest(
            @NonNull ObjectSpecification objectSpecification,
            @NonNull ObjectMemento memento) {
    }

    public ObjectManager(final MetaModelContext mmc) {
        this(mmc,
                ObjectCreatorFactory.createChain(mmc),
                ObjectLoaderFactory.createChain(),
                ObjectBulkLoaderFactory.createChain(),
                ObjectDementifierFactory.createChain());
    }

    /**
     * Creates and initializes an instance conforming to given request parameters.
     * <p>
     * Resolves injection-points for the result. (Handles service injection.)
     */
    public ManagedObject createObject(final ObjectSpecification objectCreateRequest) {
        return objectCreator().handleElseFail(objectCreateRequest);
    }

    /**
     * Loads an instance identified with given request parameters.
     * <p>
     * Resolves injection-points for the result. (Handles service injection.)
     */
    public ManagedObject loadObject(final ProtoObject objectLoadRequest) {
        return objectLoader().handleElseFail(objectLoadRequest);
    }

    /**
     * Recovers an object (graph) from given {@code bookmark}.
     * <p>
     * Resolves injection-points for the result. (Handles service injection.)
     * <p>
     * Supports alias lookup.
     */
    public Optional<ManagedObject> loadObject(final @Nullable Bookmark bookmark) {
        if(bookmark==null) {
            return Optional.empty();
        }
        var specLoader = getMetaModelContext().getSpecificationLoader();
        return ProtoObject.resolve(specLoader, bookmark)
                .map(this::loadObject);
    }

    /**
     * Introduced for serializing action parameter values to bookmarks and vice versa.
     * <p>
     * Does NOT handle {@link PackedManagedObject}. (Needs to be handled by the caller.)
     * @see #debookmark(Bookmark)
     */
    public Bookmark bookmark(final @NonNull ManagedObject managedObj) {
        return ManagedObjects.bookmark(managedObj)
                .orElseGet(()->Bookmark.empty(managedObj.logicalType()));
    }
    /**
     * Introduced for de-serializing action parameter values from bookmarks and vice versa.
     * <p>
     * Does NOT handle {@link PackedManagedObject}. (Needs to be handled by the caller.)
     * @see #bookmark(ManagedObject)
     */
    public ManagedObject debookmark(final @NonNull Bookmark bookmark) {
        return bookmark.isEmpty()
            ? ManagedObject.empty(getSpecificationLoader().specForBookmarkElseFail(bookmark))
            : loadObjectElseFail(bookmark);
    }

    /**
     * @see #loadObject(Bookmark)
     */
    public ManagedObject loadObjectElseFail(final @NonNull Bookmark bookmark) {
        var adapter = loadObject(bookmark)
                .orElseThrow(() -> new BookmarkNotFoundException(String.format("Bookmark %s was not found.", bookmark)));
        if(adapter.specialization().isEntity()) {
            _Assert.assertEquals(bookmark, adapter.getBookmark().orElse(null),
                    ()->"object loaded from bookmark must itself return an equal bookmark");
        }
        return adapter;
    }

    /**
     * Resolves injection-points for the result. (Handles service injection.)
     */
    public Can<ManagedObject> queryObjects(final BulkLoadRequest objectQuery) {
        return objectBulkLoader().handleElseFail(objectQuery);
    }

    public Optional<ObjectSpecification> specForPojo(final @Nullable Object pojo) {
        if(pojo==null) return Optional.empty();

        return specForType(pojo.getClass());
    }

    @Override
    public Optional<ObjectSpecification> specForType(final @Nullable Class<?> domainType) {
        return getMetaModelContext().getSpecificationLoader().specForType(domainType);
    }

    // -- ADAPTING POJOS

    /**
     * Not suitable for adapting a plural.
     * If {@code pojo} is an entity, automatically memoizes its bookmark.
     * <p>
     * Resolves injection-points for the result. (Handles service injection.)
     * <p>
     * see also {@link #adapt(Object, Supplier)},
     *      where the 2nd arg supplies the {@link ObjectSpecification} if known (eg for null args).
     *
     * @see ManagedObject#adaptSingular(ObjectSpecification, Object)
     * @see ManagedObject#adaptParameter(ObjectActionParameter, Object)
     * @see ManagedObject#adaptProperty(OneToOneAssociation, Object)
     */
    public ManagedObject adapt(final @Nullable Object pojo) {
        return adapt(pojo, ()->specForType(Object.class).orElseThrow());
    }

    /**
     * Suitable for adapting a plural.
     * If {@code pojo} is an entity, automatically memoizes its bookmark.
     * <p>
     * Resolves injection-points for the result. (Handles service injection.)
     */
    public ManagedObject adapt(
            final @Nullable Object pojo,
            final @NonNull Supplier<ObjectSpecification> fallbackElementType) {
        if(pojo==null) {
            ObjectSpecification objectSpecification = fallbackElementType.get();
            if (objectSpecification.isSingular()) {
                return ManagedObject.empty(objectSpecification);
            }
            // best we can do?
            return ManagedObject.unspecified();
        }
        if(pojo instanceof ManagedObject) {
            // yet ignoring any bookmarking policy, assuming this is not required here
            return (ManagedObject) pojo;
        }
        // could be any pojo, even of a type, that is vetoed for introspection (spec==null)
        var spec = specForType(pojo.getClass()).orElse(null);
        if(spec==null) {
            // best we can do?
            return ManagedObject.unspecified();
        }
        return spec.isSingular()
                ? ManagedObject.adaptSingular(spec, pojo)
                : ManagedObject.packed(
                        spec.getElementSpecification().orElseGet(fallbackElementType),
                        _NullSafe.streamAutodetect(pojo)
                        .map(element->adapt(element))
                        .collect(Can.toCan()));
    }

    // -- OBJECT MEMENTOS

    public Optional<ObjectMemento> mementify(final @Nullable ManagedObject object) {
        return Optional.ofNullable(object)
        .flatMap(ManagedObject::getMemento);
    }

    public ObjectMemento mementifyElseFail(final @NonNull ManagedObject object) {
        return object.getMemento()
                .orElseThrow(()->
                    _Exceptions.unrecoverable("failed to create memento for  %s", object.objSpec()));
    }

    public ManagedObject demementify(final @Nullable ObjectMemento memento) {
        if(memento==null) return null;
        var spec = mmc.getSpecificationLoader()
                        .specForLogicalType(memento.logicalType())
                .orElse(null);
        if(spec==null) {
            return memento.isEmpty()
                    ? ManagedObject.unspecified()
                    : null;
        }
        return objectDementifier().handleElseFail(new MementoRecreateRequest(spec, memento));
    }

    @Override
    public MetaModelContext getMetaModelContext() {
        return mmc;
    }

}