You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2020/09/01 15:10:53 UTC

[isis] branch master updated: ISIS-2332: JPA entity facet factory (naive first draft)

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new e14e8fd  ISIS-2332: JPA entity facet factory (naive first draft)
e14e8fd is described below

commit e14e8fda15350bc886d2228ea4b78d35944e373f
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Sep 1 17:10:10 2020 +0200

    ISIS-2332: JPA entity facet factory (naive first draft)
---
 .../core/metamodel/progmodel/ProgrammingModel.java |   1 +
 examples/smoketests/pom.xml                        |   6 +-
 .../testdomain/conf/Configuration_usingJpa.java    |   3 +-
 mavendeps/webapp/pom.xml                           |   5 +
 .../metamodel/JdoProgrammingModelPlugin.java       |  15 +-
 persistence/jpa/model/pom.xml                      |   7 +-
 .../apache/isis/persistence/jpa/IsisModuleJpa.java |  58 +++++
 .../jpa/metamodel/JpaEntityFacetFactory.java       | 289 +++++++++++++++++++++
 .../jpa/metamodel/JpaProgrammingModelPlugin.java   |  45 ++++
 9 files changed, 413 insertions(+), 16 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/progmodel/ProgrammingModel.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/progmodel/ProgrammingModel.java
index 97cbfd2..42701a2 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/progmodel/ProgrammingModel.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/progmodel/ProgrammingModel.java
@@ -39,6 +39,7 @@ public interface ProgrammingModel {
         DEPRECATED, 
         INCUBATING,
         JDO,
+        JPA,
     }
     
     /**
diff --git a/examples/smoketests/pom.xml b/examples/smoketests/pom.xml
index 8912afb3..cef1ca7 100644
--- a/examples/smoketests/pom.xml
+++ b/examples/smoketests/pom.xml
@@ -350,11 +350,7 @@
 			<artifactId>lombok</artifactId>
 			<scope>provided</scope>
 		</dependency>
-
-		<dependency>
-			<groupId>org.springframework.data</groupId>
-			<artifactId>spring-data-jpa</artifactId>
-		</dependency>
+		
 	</dependencies>
 	
 	<modules>
diff --git a/examples/smoketests/stable/src/main/java/org/apache/isis/testdomain/conf/Configuration_usingJpa.java b/examples/smoketests/stable/src/main/java/org/apache/isis/testdomain/conf/Configuration_usingJpa.java
index 3dc1583..4e91b18 100644
--- a/examples/smoketests/stable/src/main/java/org/apache/isis/testdomain/conf/Configuration_usingJpa.java
+++ b/examples/smoketests/stable/src/main/java/org/apache/isis/testdomain/conf/Configuration_usingJpa.java
@@ -28,6 +28,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
 
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.core.runtimeservices.IsisModuleCoreRuntimeServices;
+import org.apache.isis.persistence.jpa.IsisModuleJpa;
 import org.apache.isis.security.bypass.IsisModuleSecurityBypass;
 import org.apache.isis.testdomain.jpa.JpaTestDomainModule;
 
@@ -39,7 +40,7 @@ import org.apache.isis.testdomain.jpa.JpaTestDomainModule;
 @Import({
     IsisModuleCoreRuntimeServices.class
     ,IsisModuleSecurityBypass.class
-    //,IsisModuleJdoDataNucleus5.class,
+    ,IsisModuleJpa.class,
     //,IsisModuleTestingFixturesApplib.class
 })
 @ComponentScan(
diff --git a/mavendeps/webapp/pom.xml b/mavendeps/webapp/pom.xml
index b52985b..a34fb34 100644
--- a/mavendeps/webapp/pom.xml
+++ b/mavendeps/webapp/pom.xml
@@ -129,6 +129,11 @@
 
 		<dependency>
 			<groupId>org.apache.isis.persistence</groupId>
+			<artifactId>isis-persistence-jpa-model</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.apache.isis.persistence</groupId>
 			<artifactId>isis-persistence-jdo-datanucleus5</artifactId>
 		</dependency>
 
diff --git a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/metamodel/JdoProgrammingModelPlugin.java b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/metamodel/JdoProgrammingModelPlugin.java
index e860449..0b833c7 100644
--- a/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/metamodel/JdoProgrammingModelPlugin.java
+++ b/persistence/jdo/datanucleus-5/src/main/java/org/apache/isis/persistence/jdo/datanucleus5/metamodel/JdoProgrammingModelPlugin.java
@@ -62,13 +62,10 @@ import lombok.val;
 public class JdoProgrammingModelPlugin implements MetaModelRefiner {
     
     @Inject private IsisConfiguration config;
-    private ProgrammingModel pm;
 
     @Override
     public void refineProgrammingModel(ProgrammingModel pm) {
         
-        this.pm = pm;
-
         val step1 = ProgrammingModel.FacetProcessingOrder.C2_AFTER_METHOD_REMOVING;
         
         // come what may, we have to ignore the PersistenceCapable supertype.
@@ -102,16 +99,16 @@ public class JdoProgrammingModelPlugin implements MetaModelRefiner {
 
         // -- validators
         
-        addValidatorToEnsureIdentityType();
-        addValidatorToCheckForUnsupportedAnnotations();
+        addValidatorToEnsureIdentityType(pm);
+        addValidatorToCheckForUnsupportedAnnotations(pm);
         
         if(config.getCore().getMetaModel().getValidator().isEnsureUniqueObjectTypes()) {
-            addValidatorToEnsureUniqueObjectIds();
+            addValidatorToEnsureUniqueObjectIds(pm);
         }
 
     }
 
-    private void addValidatorToEnsureIdentityType() {
+    private void addValidatorToEnsureIdentityType(ProgrammingModel pm) {
 
         pm.addValidator((objSpec, validation) -> {
 
@@ -146,7 +143,7 @@ public class JdoProgrammingModelPlugin implements MetaModelRefiner {
 
     }
 
-    private void addValidatorToCheckForUnsupportedAnnotations() {
+    private void addValidatorToCheckForUnsupportedAnnotations(ProgrammingModel pm) {
 
         pm.addValidator((objSpec, validation) -> {
             if (objSpec.containsNonFallbackFacet(ParentedCollectionFacet.class) && !objSpec.containsNonFallbackFacet(CollectionFacet.class)) {
@@ -161,7 +158,7 @@ public class JdoProgrammingModelPlugin implements MetaModelRefiner {
 
     }
 
-    private void addValidatorToEnsureUniqueObjectIds() {
+    private void addValidatorToEnsureUniqueObjectIds(ProgrammingModel pm) {
 
         final ListMultimap<ObjectSpecId, ObjectSpecification> collidingSpecsById = 
                 _Multimaps.newConcurrentListMultimap();
diff --git a/persistence/jpa/model/pom.xml b/persistence/jpa/model/pom.xml
index 929c52f..9f51554 100644
--- a/persistence/jpa/model/pom.xml
+++ b/persistence/jpa/model/pom.xml
@@ -72,11 +72,16 @@
 			<groupId>org.apache.isis.persistence</groupId>
 			<artifactId>isis-persistence-jpa-applib</artifactId>
 		</dependency>
-
+		
 		<dependency>
 			<groupId>org.apache.isis.core</groupId>
 			<artifactId>isis-core-runtime</artifactId>
 		</dependency>
+		
+		<dependency>
+			<groupId>org.springframework.data</groupId>
+			<artifactId>spring-data-jpa</artifactId>
+		</dependency>
 
 		<!-- TESTING -->
 		
diff --git a/persistence/jpa/model/src/main/java/org/apache/isis/persistence/jpa/IsisModuleJpa.java b/persistence/jpa/model/src/main/java/org/apache/isis/persistence/jpa/IsisModuleJpa.java
new file mode 100644
index 0000000..3182efe
--- /dev/null
+++ b/persistence/jpa/model/src/main/java/org/apache/isis/persistence/jpa/IsisModuleJpa.java
@@ -0,0 +1,58 @@
+/*
+ *  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.persistence.jpa;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+import org.apache.isis.core.runtime.IsisModuleCoreRuntime;
+import org.apache.isis.persistence.jpa.metamodel.JpaProgrammingModelPlugin;
+
+@Configuration
+@Import({
+        // modules
+        IsisModuleCoreRuntime.class,
+//        IsisModulePersistenceJpaApplib.class,
+
+        // @Component's
+        JpaProgrammingModelPlugin.class,
+        
+//        // @Service's
+//        DataNucleusSettings.class,
+//        ExceptionRecognizerForSQLIntegrityConstraintViolationUniqueOrIndexException.class,
+//        ExceptionRecognizerForJDODataStoreExceptionIntegrityConstraintViolationForeignKeyNoActionException.class,
+//        ExceptionRecognizerForJDOObjectNotFoundException.class,
+//        ExceptionRecognizerForJDODataStoreException.class,
+//        
+//        IsisJdoSupportDN5.class,
+//        IsisPlatformTransactionManagerForJdo.class,
+//        JdoPersistenceLifecycleService.class,
+//        MetricsServiceDefault.class,
+//        PersistenceSessionFactory5.class,
+//        JdoMetamodelMenu.class,
+//
+//        // @Mixin's
+//        Persistable_datanucleusIdLong.class,
+//        Persistable_datanucleusVersionLong.class,
+//        Persistable_datanucleusVersionTimestamp.class,
+//        Persistable_downloadJdoMetadata.class,
+})
+public class IsisModuleJpa {
+
+}
diff --git a/persistence/jpa/model/src/main/java/org/apache/isis/persistence/jpa/metamodel/JpaEntityFacetFactory.java b/persistence/jpa/model/src/main/java/org/apache/isis/persistence/jpa/metamodel/JpaEntityFacetFactory.java
new file mode 100644
index 0000000..39f9494
--- /dev/null
+++ b/persistence/jpa/model/src/main/java/org/apache/isis/persistence/jpa/metamodel/JpaEntityFacetFactory.java
@@ -0,0 +1,289 @@
+package org.apache.isis.persistence.jpa.metamodel;
+
+import java.lang.reflect.Method;
+
+import javax.persistence.Entity;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceUnitUtil;
+
+import org.springframework.data.jpa.repository.JpaContext;
+
+import org.apache.isis.applib.query.Query;
+import org.apache.isis.applib.query.QueryFindAllInstances;
+import org.apache.isis.applib.services.registry.ServiceRegistry;
+import org.apache.isis.applib.services.repository.EntityState;
+import org.apache.isis.applib.services.urlencoding.UrlEncodingService;
+import org.apache.isis.core.commons.collections.Can;
+import org.apache.isis.core.commons.collections.ImmutableEnumSet;
+import org.apache.isis.core.commons.internal.base._Strings;
+import org.apache.isis.core.commons.internal.exceptions._Exceptions;
+import org.apache.isis.core.commons.internal.memento._Mementos;
+import org.apache.isis.core.commons.internal.memento._Mementos.SerializingAdapter;
+import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facets.Annotations;
+import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.isis.core.metamodel.facets.object.entity.EntityFacet;
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
+public class JpaEntityFacetFactory extends FacetFactoryAbstract {
+
+    public JpaEntityFacetFactory() {
+        super(ImmutableEnumSet.of(FeatureType.OBJECT));
+    }
+
+    @Override
+    public void process(ProcessClassContext processClassContext) {
+        val cls = processClassContext.getCls();
+
+        val entityAnnotation = Annotations.getAnnotation(cls, Entity.class);
+        if (entityAnnotation == null) {
+            return;
+        }
+        
+        val facetHolder = processClassContext.getFacetHolder();
+        val serviceRegistry = super.getMetaModelContext().getServiceRegistry();
+        val jpaEntityFacet = new JpaEntityFacet(facetHolder, cls, serviceRegistry);
+            
+        addFacet(jpaEntityFacet);
+    }
+    
+    // -- 
+    
+    public static class JpaEntityFacet
+    extends FacetAbstract
+    implements EntityFacet {
+
+        private final Class<?> entityClass;
+        private final ServiceRegistry serviceRegistry;
+        
+        protected JpaEntityFacet(
+                final FacetHolder holder,
+                final Class<?> entityClass, 
+                final @NonNull ServiceRegistry serviceRegistry) {
+            
+            super(EntityFacet.class, holder);
+            this.entityClass = entityClass;
+            this.serviceRegistry = serviceRegistry;
+        }
+        
+        @Override public boolean isDerived() { return false;}
+        @Override public boolean isFallback() { return false;}
+        @Override public boolean alwaysReplace() { return true;}
+        
+        // -- ENTITY FACET 
+
+        @Override
+        public String identifierFor(ObjectSpecification spec, Object pojo) {
+
+            if(pojo==null) {
+                throw _Exceptions.illegalArgument(
+                        "The persistence layer cannot identify a pojo that is null (given type %s)",
+                        spec.getCorrespondingClass().getName());
+            }
+            
+            if(!spec.isEntity()) {
+                throw _Exceptions.illegalArgument(
+                        "The persistence layer does not recognize given type %s",
+                        pojo.getClass().getName());
+            }
+
+            val entityManager = getEntityManager();
+            val persistenceUnitUtil = getPersistenceUnitUtil(entityManager);
+            val primaryKey = persistenceUnitUtil.getIdentifier(pojo);
+            
+            if(primaryKey==null) {
+                throw _Exceptions.illegalArgument(
+                        "The persistence layer does not recognize given object of type %s, "
+                        + "meaning the object has no identifier that associates it with the persistence layer. "
+                        + "(most likely, because the object is detached, eg. was not persisted after being new-ed up)", 
+                        pojo.getClass().getName());
+            }
+            
+            return getObjectIdSerializer().stringify(primaryKey);
+
+        }
+
+        @Override
+        public ManagedObject fetchByIdentifier(ObjectSpecification spec, String identifier) {
+            
+            val primaryKey = getObjectIdSerializer().parse(identifier);
+            val entityManager = getEntityManager();
+            val entity = entityManager.find(entityClass, primaryKey);
+            
+            return ManagedObject.of(spec, entity);
+        }
+
+        @Override
+        public Can<ManagedObject> fetchByQuery(ObjectSpecification spec, Query<?> query) {
+            
+            if(!(query instanceof QueryFindAllInstances)) {
+                throw _Exceptions.notImplemented();
+            }
+            
+            val queryFindAllInstances = (QueryFindAllInstances<?>) query;
+            val queryEntityType = queryFindAllInstances.getResultType();
+            
+            // guard against misuse
+            if(!entityClass.isAssignableFrom(queryEntityType)) {
+                throw _Exceptions.unexpectedCodeReach();
+            }
+            
+            val entityManager = getEntityManager();
+            
+            val typedQuery = entityManager
+                    .createQuery("SELECT t FROM " + entityClass.getSimpleName() + " t", entityClass);
+            
+            final int startPosition = Math.toIntExact(queryFindAllInstances.getStart());
+            final int maxResult = Math.toIntExact(queryFindAllInstances.getCount());
+            typedQuery.setFirstResult(startPosition);
+            typedQuery.setMaxResults(maxResult);
+            
+            return Can.ofStream(
+                typedQuery.getResultStream()
+                .map(entity->ManagedObject.of(spec, entity)));
+        }
+
+        @Override
+        public void persist(ObjectSpecification spec, Object pojo) {
+            if(pojo==null) {
+                return; // nothing to do
+            }
+            
+            // guard against misuse
+            if(!entityClass.isAssignableFrom(pojo.getClass())) {
+                throw _Exceptions.unexpectedCodeReach();
+            }
+            
+            val entityManager = getEntityManager();
+            entityManager.persist(pojo);
+        }
+
+        @Override
+        public void refresh(Object pojo) {
+            if(pojo==null) {
+                return; // nothing to do
+            }
+            
+            // guard against misuse
+            if(!entityClass.isAssignableFrom(pojo.getClass())) {
+                throw _Exceptions.unexpectedCodeReach();
+            }
+            
+            val entityManager = getEntityManager();
+            entityManager.refresh(pojo);
+        }
+
+        @Override
+        public void delete(ObjectSpecification spec, Object pojo) {
+            
+            if(pojo==null) {
+                return; // nothing to do
+            }
+            
+            // guard against misuse
+            if(!entityClass.isAssignableFrom(pojo.getClass())) {
+                throw _Exceptions.unexpectedCodeReach();
+            }
+            
+            val entityManager = getEntityManager();
+            entityManager.remove(pojo);
+        }
+
+        @Override
+        public EntityState getEntityState(Object pojo) {
+            
+            if(pojo==null) {
+                return EntityState.NOT_PERSISTABLE;
+            }
+            
+            // guard against misuse
+            if(!entityClass.isAssignableFrom(pojo.getClass())) {
+                //throw _Exceptions.unexpectedCodeReach();
+                return EntityState.NOT_PERSISTABLE;
+            }
+            
+            val entityManager = getEntityManager();
+            val persistenceUnitUtil = getPersistenceUnitUtil(entityManager);
+            
+            if(persistenceUnitUtil.isLoaded(pojo)) {
+                return EntityState.PERSISTABLE_ATTACHED;
+            }
+            //TODO how to determine whether deleted? (even relevant?)
+//            if(isDeleted) {
+//                return EntityState.PERSISTABLE_DESTROYED;
+//            }
+            return EntityState.PERSISTABLE_DETACHED;
+        }
+
+        @Override
+        public boolean isProxyEnhancement(Method method) {
+            return false;
+        }
+
+        @Override
+        public <T> T detach(T pojo) {
+            getEntityManager().detach(pojo);
+            return pojo;
+        }
+        
+        // -- DEPENDENCIES
+        
+        protected JpaContext getJpaContext() {
+            return serviceRegistry.lookupServiceElseFail(JpaContext.class);
+        }
+        
+        protected EntityManager getEntityManager() {
+            return getJpaContext().getEntityManagerByManagedType(entityClass);
+        }
+        
+        protected PersistenceUnitUtil getPersistenceUnitUtil(EntityManager entityManager) {
+            return entityManager.getEntityManagerFactory().getPersistenceUnitUtil();
+        }
+        
+        protected JpaObjectIdSerializer getObjectIdSerializer() {
+            val codec = serviceRegistry.lookupServiceElseFail(UrlEncodingService.class);
+            val serializer = serviceRegistry.lookupServiceElseFail(SerializingAdapter.class);
+            return new JpaObjectIdSerializer(codec, serializer);
+        }
+        
+    }
+
+    @RequiredArgsConstructor
+    private static class JpaObjectIdSerializer {
+        
+        private final UrlEncodingService codec;
+        private final SerializingAdapter serializer;
+        
+        public String stringify(Object id) {
+            return newMemento().put("id", id).asString();
+        }
+        
+        public Object parse(String input) {
+            if(_Strings.isEmpty(input)) {
+                return null;
+            }
+            return parseMemento(input).get("id", Object.class);
+        }
+       
+        // -- HELPER
+
+        private _Mementos.Memento newMemento(){
+            return _Mementos.create(codec, serializer);
+        }
+
+        private _Mementos.Memento parseMemento(String input){
+            return _Mementos.parse(codec, serializer, input);
+        }
+        
+    }
+    
+
+
+}
diff --git a/persistence/jpa/model/src/main/java/org/apache/isis/persistence/jpa/metamodel/JpaProgrammingModelPlugin.java b/persistence/jpa/model/src/main/java/org/apache/isis/persistence/jpa/metamodel/JpaProgrammingModelPlugin.java
new file mode 100644
index 0000000..b31962e
--- /dev/null
+++ b/persistence/jpa/model/src/main/java/org/apache/isis/persistence/jpa/metamodel/JpaProgrammingModelPlugin.java
@@ -0,0 +1,45 @@
+/*
+ *  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.persistence.jpa.metamodel;
+
+import javax.inject.Inject;
+
+import org.springframework.stereotype.Component;
+
+import org.apache.isis.core.config.IsisConfiguration;
+import org.apache.isis.core.metamodel.facetapi.MetaModelRefiner;
+import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
+import org.apache.isis.core.metamodel.progmodel.ProgrammingModel.Marker;
+
+import lombok.val;
+
+@Component
+public class JpaProgrammingModelPlugin implements MetaModelRefiner {
+    
+    @Inject private IsisConfiguration config;
+
+    @Override
+    public void refineProgrammingModel(ProgrammingModel pm) {
+        
+        val step1 = ProgrammingModel.FacetProcessingOrder.A2_AFTER_FALLBACK_DEFAULTS;
+
+        pm.addFactory(step1, JpaEntityFacetFactory.class, Marker.JPA);        
+    }
+
+}