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);
+ }
+
+}