You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2023/07/01 09:21:04 UTC

[causeway] branch master updated: CAUSEWAY-2297: convenience: makes entity ORM metadata available through MM

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/causeway.git


The following commit(s) were added to refs/heads/master by this push:
     new 8dcc71fdb6 CAUSEWAY-2297: convenience: makes entity ORM metadata available through MM
8dcc71fdb6 is described below

commit 8dcc71fdb62495963eb10db3944950ce2b0a7d17
Author: andi-huber <ah...@apache.org>
AuthorDate: Sat Jul 1 11:20:58 2023 +0200

    CAUSEWAY-2297: convenience: makes entity ORM metadata available through
    MM
    
    - given an ObjectSpecification one can now access ORM metadata via the
    EntityFacet
---
 .../facets/object/entity/EntityFacet.java          |   5 +
 .../facets/object/entity/EntityOrmMetadata.java    |  54 ++++++++++
 .../object/entity/_EntityFacetForTesting.java      |   5 +
 .../metamodel/facets/entity/JdoEntityFacet.java    |  47 ++-------
 .../metamodel/facets/entity/_MetadataUtil.java     | 114 +++++++++++++++++++++
 .../jpa/integration/entity/JpaEntityFacet.java     |  30 ++----
 .../jpa/integration/entity/_MetadataUtil.java      |  83 +++++++++++++++
 7 files changed, 275 insertions(+), 63 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/EntityFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/EntityFacet.java
index b90372c98e..0d910321d7 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/EntityFacet.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/EntityFacet.java
@@ -162,6 +162,11 @@ public interface EntityFacet extends Facet {
 
     PersistenceStack getPersistenceStack();
 
+    /**
+     * Provides associated table and column names.
+     */
+    EntityOrmMetadata getOrmMetadata();
+
     // -- JUNIT SUPPORT
 
     static EntityFacet forTesting(
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/EntityOrmMetadata.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/EntityOrmMetadata.java
new file mode 100644
index 0000000000..c3cf95192a
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/EntityOrmMetadata.java
@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.core.metamodel.facets.object.entity;
+
+import java.util.Optional;
+
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.core.config.beans.PersistenceStack;
+
+import lombok.NonNull;
+import lombok.experimental.Accessors;
+
+// record candidate
+@lombok.Value @Accessors(fluent=true)
+public class EntityOrmMetadata {
+
+    @lombok.Value @Accessors(fluent=true)
+    public static class ColumnOrmMetadata {
+
+        final @NonNull String name;
+        final @NonNull String memberId;
+        final @NonNull String javaType;
+        //final @NonNull String sqlType;
+
+        final @NonNull Object vendorColumnMetadata;
+
+    }
+
+    final @NonNull PersistenceStack persistenceStack;
+    final @NonNull Optional<String> table;
+    final @NonNull Optional<String> schema;
+    final @NonNull Class<?> primaryKeyClass;
+
+    final @NonNull Can<ColumnOrmMetadata> columns;
+
+    final @NonNull Object vendorEntityMetadata;
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/_EntityFacetForTesting.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/_EntityFacetForTesting.java
index 7b197c7a3c..b94dc4026a 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/_EntityFacetForTesting.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/entity/_EntityFacetForTesting.java
@@ -106,4 +106,9 @@ class _EntityFacetForTesting implements EntityFacet {
         throw _Exceptions.unsupportedOperation();
     }
 
+    @Override
+    public EntityOrmMetadata getOrmMetadata() {
+        throw _Exceptions.unsupportedOperation();
+    }
+
 }
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
index bb7d638e72..6e09b8a921 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
@@ -29,10 +29,8 @@ import javax.inject.Inject;
 import javax.jdo.FetchGroup;
 import javax.jdo.PersistenceManager;
 
-import org.datanucleus.api.jdo.JDOPersistenceManagerFactory;
 import org.datanucleus.api.jdo.JDOQuery;
 import org.datanucleus.enhancement.Persistable;
-import org.datanucleus.identity.SCOID;
 import org.datanucleus.store.rdbms.RDBMSPropertyNames;
 import org.springframework.lang.Nullable;
 
@@ -55,6 +53,7 @@ import org.apache.causeway.core.config.beans.PersistenceStack;
 import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.object.entity.EntityFacet;
+import org.apache.causeway.core.metamodel.facets.object.entity.EntityOrmMetadata;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
 import org.apache.causeway.core.metamodel.services.idstringifier.IdStringifierLookupService;
@@ -99,7 +98,12 @@ implements EntityFacet {
     // 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));
+            .primaryKeyTypeFor(entityClass, getOrmMetadata().primaryKeyClass());
+
+    // lazily looks up the ORM metadata (needs a PersistenceManager)
+    @Getter(lazy=true)
+    private final EntityOrmMetadata ormMetadata =
+            _MetadataUtil.ormMetadataFor(getPersistenceManager(), entityClass);
 
     public JdoEntityFacet(
             final FacetHolder holder, final Class<?> entityClass) {
@@ -208,43 +212,6 @@ implements EntityFacet {
         return Optional.ofNullable(entityPojo);
     }
 
-    private Map<Class<?>, Class<?>> primaryKeyClassByEntityClass = new ConcurrentHashMap<>();
-
-    private Class<?> primaryKeyTypeFor(final Class<?> entityClass) {
-        return primaryKeyClassByEntityClass.computeIfAbsent(entityClass, this::lookupPrimaryKeyTypeFor);
-    }
-
-    private Class<?> lookupPrimaryKeyTypeFor(final Class<?> entityClass) {
-
-        val persistenceManager = getPersistenceManager();
-        val pmf = (JDOPersistenceManagerFactory) persistenceManager.getPersistenceManagerFactory();
-        val nucleusContext = pmf.getNucleusContext();
-
-        val contextLoader = Thread.currentThread().getContextClassLoader();
-        val clr = nucleusContext.getClassLoaderResolver(contextLoader);
-
-        val typeMetadata = pmf.getMetadata(entityClass.getName());
-
-        val identityType = typeMetadata.getIdentityType();
-        switch (identityType) {
-            case APPLICATION:
-                String objectIdClass = typeMetadata.getObjectIdClass();
-                return clr.classForName(objectIdClass);
-            case DATASTORE:
-                return nucleusContext.getIdentityManager().getDatastoreIdClass();
-            case NONDURABLE:
-                return SCOID.class;
-            case UNSPECIFIED:
-            default:
-                throw new IllegalStateException(String.format(
-                        "JdoEntityFacet was installed on '%s', "
-                        + "yet this entity has IdentityType '%s', "
-                        + "which is not supported by the framework's "
-                        + "JDO implementation.",
-                        entityClass.getName(), identityType));
-        }
-    }
-
     @Override
     public Can<ManagedObject> fetchByQuery(final Query<?> query) {
 
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/_MetadataUtil.java b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/_MetadataUtil.java
new file mode 100644
index 0000000000..c65cb46570
--- /dev/null
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/_MetadataUtil.java
@@ -0,0 +1,114 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.persistence.jdo.datanucleus.metamodel.facets.entity;
+
+import java.util.Optional;
+
+import javax.jdo.PersistenceManager;
+import javax.jdo.metadata.MemberMetadata;
+import javax.jdo.metadata.TypeMetadata;
+
+import org.datanucleus.api.jdo.JDOPersistenceManagerFactory;
+import org.datanucleus.identity.SCOID;
+
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.internal.base._NullSafe;
+import org.apache.causeway.core.config.beans.PersistenceStack;
+import org.apache.causeway.core.metamodel.facets.object.entity.EntityOrmMetadata;
+import org.apache.causeway.core.metamodel.facets.object.entity.EntityOrmMetadata.ColumnOrmMetadata;
+
+import lombok.NonNull;
+import lombok.val;
+import lombok.experimental.UtilityClass;
+import lombok.extern.log4j.Log4j2;
+
+@UtilityClass
+@Log4j2
+class _MetadataUtil {
+
+    EntityOrmMetadata ormMetadataFor(
+            final @NonNull PersistenceManager persistenceManager,
+            final @NonNull Class<?> entityClass) {
+
+        val pmf = (JDOPersistenceManagerFactory) persistenceManager.getPersistenceManagerFactory();
+        val typeMetadata = pmf.getMetadata(entityClass.getName());
+
+        return new EntityOrmMetadata(
+                PersistenceStack.JDO,
+                Optional.ofNullable(typeMetadata.getTable()),
+                Optional.ofNullable(typeMetadata.getSchema()),
+                primaryKeyTypeFor(pmf, typeMetadata, entityClass),
+                columns(typeMetadata),
+                typeMetadata);
+    }
+
+    // -- HELPER
+
+    private Can<ColumnOrmMetadata> columns(final TypeMetadata typeMetadata) {
+        return _NullSafe.stream(typeMetadata.getMembers())
+                .map(_MetadataUtil::column)
+                .collect(Can.toCan());
+    }
+
+    private ColumnOrmMetadata column(final MemberMetadata c) {
+        return Optional.ofNullable(c.getColumn()) // null if is plural
+                .map(colName->new ColumnOrmMetadata(colName, c.getName(), fieldType(c), c))
+                .orElse(null);
+    }
+
+    private String fieldType(final MemberMetadata c) {
+        return Optional.ofNullable(c.getFieldType())
+                .orElseGet(()->{
+                    log.warn("failed to get java field type from JDO MemberMetadata in {}", c.getParent());
+                    return Object.class.getName();
+                });
+    }
+
+    private Class<?> primaryKeyTypeFor(
+            final @NonNull JDOPersistenceManagerFactory pmf,
+            final @NonNull TypeMetadata typeMetadata,
+            final @NonNull Class<?> entityClass) {
+
+        val identityType = typeMetadata.getIdentityType();
+        switch (identityType) {
+            case APPLICATION: {
+                val contextLoader = Thread.currentThread().getContextClassLoader();
+                val nucleusContext = pmf.getNucleusContext();
+                val clr = nucleusContext.getClassLoaderResolver(contextLoader);
+                final String objectIdClass = typeMetadata.getObjectIdClass();
+                return clr.classForName(objectIdClass);
+            }
+            case DATASTORE: {
+                val nucleusContext = pmf.getNucleusContext();
+                return nucleusContext.getIdentityManager().getDatastoreIdClass();
+            }
+            case NONDURABLE:
+                return SCOID.class;
+            case UNSPECIFIED:
+            default:
+                throw new IllegalStateException(String.format(
+                        "JdoEntityFacet was installed on '%s', "
+                        + "yet this entity has IdentityType '%s', "
+                        + "which is not supported by the framework's "
+                        + "JDO implementation.",
+                        entityClass.getName(), identityType));
+        }
+    }
+
+}
diff --git a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/JpaEntityFacet.java b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/JpaEntityFacet.java
index 82cfb7ea22..af8083f23c 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/JpaEntityFacet.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/JpaEntityFacet.java
@@ -25,7 +25,6 @@ import javax.inject.Inject;
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceException;
 import javax.persistence.PersistenceUnitUtil;
-import javax.persistence.metamodel.EntityType;
 
 import org.eclipse.persistence.exceptions.DescriptorException;
 import org.springframework.data.jpa.repository.JpaContext;
@@ -39,15 +38,16 @@ import org.apache.causeway.applib.services.repository.EntityState;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.internal.assertions._Assert;
 import org.apache.causeway.commons.internal.base._Casts;
-import org.apache.causeway.commons.internal.base._Lazy;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.core.config.beans.PersistenceStack;
 import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.object.entity.EntityFacet;
+import org.apache.causeway.core.metamodel.facets.object.entity.EntityOrmMetadata;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.services.idstringifier.IdStringifierLookupService;
 
+import lombok.Getter;
 import lombok.NonNull;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
@@ -117,7 +117,7 @@ public class JpaEntityFacet
     }
 
     private Class<?> getPrimaryKeyType() {
-        return getJpaEntityType().getIdType().getJavaType();
+        return getOrmMetadata().primaryKeyClass();
     }
 
     @Override
@@ -292,26 +292,10 @@ public class JpaEntityFacet
 
     // -- JPA METAMODEL
 
-    private final _Lazy<Optional<EntityType<?>>> jpaEntityTypeRef = _Lazy.threadSafe(this::queryJpaMetamodel);
-
-    /**
-     * get the JPA meta-model associated with this (corresponding) entity
-     */
-    private EntityType<?> getJpaEntityType() {
-        return jpaEntityTypeRef.get().orElseThrow(_Exceptions::noSuchElement);
-    }
-
-    /**
-     * find the JPA meta-model associated with this (corresponding) entity
-     */
-    private Optional<EntityType<?>> queryJpaMetamodel() {
-        return getEntityManager().getMetamodel().getEntities()
-                .stream()
-                .filter(type -> type.getJavaType().equals(entityClass))
-                .findFirst();
-    }
-
-
+    // lazily looks up the ORM metadata (needs an EntityManager)
+    @Getter(lazy=true)
+    private final EntityOrmMetadata ormMetadata =
+            _MetadataUtil.ormMetadataFor(getEntityManager(), entityClass);
 
     // -- DEPENDENCIES
 
diff --git a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/_MetadataUtil.java b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/_MetadataUtil.java
new file mode 100644
index 0000000000..37432cc01b
--- /dev/null
+++ b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/_MetadataUtil.java
@@ -0,0 +1,83 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.persistence.jpa.integration.entity;
+
+import java.util.Optional;
+
+import javax.persistence.EntityManager;
+import javax.persistence.metamodel.EntityType;
+import javax.persistence.metamodel.SingularAttribute;
+
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.internal.base._NullSafe;
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.core.config.beans.PersistenceStack;
+import org.apache.causeway.core.metamodel.facets.object.entity.EntityOrmMetadata;
+import org.apache.causeway.core.metamodel.facets.object.entity.EntityOrmMetadata.ColumnOrmMetadata;
+
+import lombok.NonNull;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+class _MetadataUtil {
+
+    EntityOrmMetadata ormMetadataFor(
+            final @NonNull EntityManager entityManager,
+            final @NonNull Class<?> entityClass) {
+
+        final EntityType<?> typeMetadata = lookupJpaMetamodel(entityManager, entityClass)
+                .orElseThrow(()->
+                    _Exceptions.noSuchElement("cannot find JPA metadata for entity %s", entityClass));
+
+
+        return new EntityOrmMetadata(
+                PersistenceStack.JPA,
+                Optional.empty(), // if somebody knows how to implement this, feel free to inform us
+                Optional.empty(), // if somebody knows how to implement this, feel free to inform us
+                typeMetadata.getIdType().getJavaType(),
+                columns(typeMetadata),
+                typeMetadata);
+    }
+
+    // -- HELPER
+
+    private Can<ColumnOrmMetadata> columns(final EntityType<?> typeMetadata) {
+        return _NullSafe.stream(typeMetadata.getSingularAttributes())
+                .map(_MetadataUtil::column)
+                .collect(Can.toCan());
+    }
+
+    private ColumnOrmMetadata column(final SingularAttribute sa) {
+        // if somebody knows how to implement this, feel free to inform us
+        return null;
+    }
+
+    /**
+     * find the JPA meta-model associated with this (corresponding) entity
+     */
+    private Optional<EntityType<?>> lookupJpaMetamodel(
+            final EntityManager entityManager,
+            final Class<?> entityClass) {
+        return entityManager.getMetamodel().getEntities()
+                .stream()
+                .filter(type -> type.getJavaType().equals(entityClass))
+                .findFirst();
+    }
+
+}