You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/05/22 20:34:24 UTC
(cayenne) 01/03: CAY-2947 Merge "cayenne-commitlog" into the core
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit c2e3c28608602f75822ed9fe7825fb7efaf8320a
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Fri May 22 16:12:51 2026 -0400
CAY-2947 Merge "cayenne-commitlog" into the core
---
RELEASE-NOTES.txt | 1 +
UPGRADE.txt | 15 +
.../apache/cayenne/commitlog/CommitLogModule.java | 32 +-
.../cayenne/commitlog/CommitLogModuleExtender.java | 92 ++----
.../cayenne/commitlog/CommitLogModuleProvider.java | 2 +
.../org/apache/cayenne/annotation}/CommitLog.java | 33 +-
.../org/apache/cayenne/commitlog/CommitLog.java | 2 +
.../apache/cayenne/commitlog/CommitLogFilter.java | 0
.../cayenne/commitlog/CommitLogListener.java | 0
.../org/apache/cayenne/commitlog/Confidential.java | 0
.../cayenne/commitlog/DeletedDiffProcessor.java | 0
.../org/apache/cayenne/commitlog/DiffFilter.java | 0
.../apache/cayenne/commitlog/DiffProcessor.java | 0
.../meta/AnnotationCommitLogEntityFactory.java | 39 ++-
.../cayenne/commitlog/meta/CommitLogEntity.java | 0
.../commitlog/meta/CommitLogEntityFactory.java | 0
.../meta/IncludeAllCommitLogEntityFactory.java | 0
.../commitlog/meta/MutableCommitLogLogEntity.java | 0
.../cayenne/commitlog/model/AttributeChange.java | 0
.../apache/cayenne/commitlog/model/ChangeMap.java | 0
.../commitlog/model/MutableAttributeChange.java | 0
.../cayenne/commitlog/model/MutableChangeMap.java | 0
.../commitlog/model/MutableObjectChange.java | 0
.../model/MutableToManyRelationshipChange.java | 0
.../model/MutableToOneRelationshipChange.java | 0
.../cayenne/commitlog/model/ObjectChange.java | 0
.../cayenne/commitlog/model/ObjectChangeType.java | 0
.../cayenne/commitlog/model/PropertyChange.java | 0
.../commitlog/model/PropertyChangeVisitor.java | 0
.../commitlog/model/ToManyRelationshipChange.java | 0
.../commitlog/model/ToOneRelationshipChange.java | 0
.../cayenne/configuration/runtime/CoreModule.java | 6 +
.../configuration/runtime/CoreModuleExtender.java | 87 ++++++
.../cayenne/commitlog/CommitLogFilterIT.java | 336 +++++++++++++++++++++
.../commitlog/CommitLogFilter_All_FlattenedIT.java | 133 ++++++++
.../commitlog/CommitLogFilter_FilteredIT.java | 287 ++++++++++++++++++
.../CommitLogFilter_ListenerInducedChangesIT.java | 240 +++++++++++++++
.../commitlog/CommitLogFilter_OutsideTxIT.java | 83 +++++
.../commitlog/CommitLogFilter_PreUpdate_IT.java | 101 +++++++
.../CommitLogFilter_PreUpdate_OutsideTxIT.java | 101 +++++++
.../cayenne/commitlog/CommitLogFilter_TxIT.java | 80 +++++
.../org/apache/cayenne/commitlog/db/AuditLog.java | 12 +-
.../apache/cayenne/commitlog/db/Auditable1.java | 17 +-
.../apache/cayenne/commitlog/db/Auditable2.java | 14 +-
.../apache/cayenne/commitlog/db/Auditable3.java | 14 +-
.../apache/cayenne/commitlog/db/Auditable4.java | 14 +-
.../apache/cayenne/commitlog/db/Auditable5.java | 15 +-
.../cayenne/commitlog/db/AuditableChild1.java | 12 +-
.../cayenne/commitlog/db/AuditableChild1x.java | 12 +-
.../cayenne/commitlog/db/AuditableChild3.java | 12 +-
.../cayenne/commitlog/db/AuditableChild5.java | 16 +-
.../java/org/apache/cayenne/commitlog/db/E1.java | 12 +-
.../java/org/apache/cayenne/commitlog/db/E2.java | 12 +-
.../java/org/apache/cayenne/commitlog/db/E3.java | 14 +-
.../java/org/apache/cayenne/commitlog/db/E4.java | 14 +-
.../apache/cayenne/commitlog/db/LifecycleMap.java | 22 +-
.../cayenne/commitlog/db/auto/_AuditLog.java | 91 ++++++
.../cayenne/commitlog/db/auto/_Auditable1.java | 116 +++++++
.../cayenne/commitlog/db/auto/_Auditable2.java | 135 +++++++++
.../cayenne/commitlog/db/auto/_Auditable3.java | 135 +++++++++
.../cayenne/commitlog/db/auto/_Auditable4.java | 129 ++++++++
.../cayenne/commitlog/db/auto/_Auditable5.java | 116 +++++++
.../commitlog/db/auto/_AuditableChild1.java | 110 +++++++
.../commitlog/db/auto/_AuditableChild1x.java | 110 +++++++
.../commitlog/db/auto/_AuditableChild3.java | 129 ++++++++
.../commitlog/db/auto/_AuditableChild5.java | 110 +++++++
.../org/apache/cayenne/commitlog/db/auto/_E1.java | 71 +++++
.../org/apache/cayenne/commitlog/db/auto/_E2.java | 71 +++++
.../org/apache/cayenne/commitlog/db/auto/_E3.java | 96 ++++++
.../org/apache/cayenne/commitlog/db/auto/_E4.java | 96 ++++++
.../cayenne/commitlog/db/auto/_LifecycleMap.java | 12 +
.../commitlog/unit/AuditableRuntimeCase.java | 101 +++++++
.../commitlog/unit/FlattenedRuntimeCase.java | 62 ++++
cayenne/src/test/resources/cayenne-lifecycle.xml | 18 ++
cayenne/src/test/resources/lifecycle-map.map.xml | 179 +++++++++++
.../src/docs/asciidoc/_cayenne-guide/part2.adoc | 2 +
.../asciidoc/_cayenne-guide/part2/commitLog.adoc | 140 +++++++++
.../src/docs/asciidoc/_cayenne-guide/part5.adoc | 2 -
.../asciidoc/_cayenne-guide/part5/commitLog.adoc | 81 -----
79 files changed, 3671 insertions(+), 323 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 5f298bc8b..d1ae3bafb 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -44,6 +44,7 @@ CAY-2942 CayenneModeler MCP: cgen_run tool
CAY-2943 CayenneModeler MCP: open_project tool
CAY-2945 CayenneModeler universal "main"
CAY-2946 Claude Code "plugin" for agentic coding with Cayenne
+CAY-2947 Merge "cayenne-commitlog" into the core
Bug Fixes:
diff --git a/UPGRADE.txt b/UPGRADE.txt
index 980b0a24d..8ac2292f2 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -7,6 +7,21 @@ IMPORTANT: be sure to read all notes for the intermediate releases between your
UPGRADING TO 5.0.M2
+* Per CAY-2947 commit log support has moved from the separate `cayenne-commitlog` artifact into the core
+`cayenne` artifact. No extra dependency is needed. Replace:
+```
+binder -> CommitLogModule.extend(binder).addListener(myListener)
+```
+with:
+```
+binder -> CoreModule.extend(binder).addCommitLogListener(myListener)
+```
+The `excludeFromTransaction()` method is now `excludeCommitLogFromTransaction()` on `CoreModuleExtender`.
+The `@CommitLog` annotation has moved to `org.apache.cayenne.annotation.CommitLog`. The old
+`org.apache.cayenne.commitlog.CommitLog` is deprecated and still recognized at runtime, but will log a
+warning for each entity class that uses it.
+The `cayenne-commitlog` artifact is retained with deprecated shim classes for backward compatibility.
+
* Per CAY-2935 Minimum required Java version for Apache Cayenne 5.0 is 21
* Per CAY-2937 the visual graph feature (entity layout diagrams) has been removed from CayenneModeler.
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModule.java b/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModule.java
index 12785b9d9..0ff7f4793 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModule.java
+++ b/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModule.java
@@ -19,45 +19,31 @@
package org.apache.cayenne.commitlog;
-import org.apache.cayenne.commitlog.meta.IncludeAllCommitLogEntityFactory;
+import org.apache.cayenne.configuration.runtime.CoreModule;
import org.apache.cayenne.di.Binder;
-import org.apache.cayenne.di.ListBuilder;
import org.apache.cayenne.di.Module;
/**
- * Auto-loadable module that enables gathering of commit log information for Cayenne stack. To add custom listeners to
- * receive commit log events, implement {@link CommitLogListener} and register it using {@link CommitLogModule#extend(Binder)}
+ * Commit log support is now part of the core {@code cayenne} module. This class is retained for backward
+ * compatibility only. Replace usages with {@link CoreModule#extend(Binder)} and its
+ * {@code addCommitLogListener} / {@code commitLogAnnotationEntitiesOnly} methods.
*
+ * @deprecated use {@link CoreModule#extend(Binder)} commit log methods.
* @since 4.0
*/
+@Deprecated(since = "5.0")
public class CommitLogModule implements Module {
/**
- * @deprecated use {@link #extend(Binder)} instead
+ * @deprecated use {@link CoreModule#extend(Binder)} commit log methods.
*/
@Deprecated(since = "5.0")
- static ListBuilder<CommitLogListener> contributeListeners(Binder binder) {
- return binder.bindList(CommitLogListener.class);
- }
-
- /**
- * Starts an extension module builder to add listeners and/or other customizations for {@link CommitLogModule}.
- *
- * @return a new builder of {@link CommitLogModule} extensions.
- * @since 5.0
- */
public static CommitLogModuleExtender extend(Binder binder) {
- return new CommitLogModuleExtender(binder);
+ return new CommitLogModuleExtender(CoreModule.extend(binder));
}
@Override
public void configure(Binder binder) {
-
- extend(binder)
- .initAllExtensions()
- .entityFactory(IncludeAllCommitLogEntityFactory.class)
- .includeInTransaction();
-
- binder.bind(CommitLogFilter.class).to(CommitLogFilter.class);
+ // no-op: commit log support is now wired by CoreModule
}
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModuleExtender.java b/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModuleExtender.java
index a95921984..c687e7d83 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModuleExtender.java
+++ b/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModuleExtender.java
@@ -18,102 +18,74 @@
****************************************************************/
package org.apache.cayenne.commitlog;
-import org.apache.cayenne.DataChannelSyncFilter;
-import org.apache.cayenne.commitlog.meta.AnnotationCommitLogEntityFactory;
-import org.apache.cayenne.commitlog.meta.CommitLogEntity;
import org.apache.cayenne.commitlog.meta.CommitLogEntityFactory;
-import org.apache.cayenne.configuration.runtime.CoreModule;
-import org.apache.cayenne.di.Binder;
-import org.apache.cayenne.di.ListBuilder;
-import org.apache.cayenne.graph.GraphChangeHandler;
+import org.apache.cayenne.configuration.runtime.CoreModuleExtender;
/**
- * A builder of a custom extensions module for {@link CommitLogModule} that customizes its services and installs
- * application-specific commit log listeners.
- *
+ * @deprecated use {@link CoreModuleExtender} commitlog methods directly via {@link org.apache.cayenne.configuration.runtime.CoreModule#extend(org.apache.cayenne.di.Binder)}.
* @since 4.0
*/
+@Deprecated(since = "5.0")
public class CommitLogModuleExtender {
- private final Binder binder;
- private ListBuilder<CommitLogListener> commitLogListeners;
+ private final CoreModuleExtender coreExtender;
- protected CommitLogModuleExtender(Binder binder) {
- this.binder = binder;
+ CommitLogModuleExtender(CoreModuleExtender coreExtender) {
+ this.coreExtender = coreExtender;
}
- protected CommitLogModuleExtender initAllExtensions() {
- contributeCommitLogListeners();
+ /**
+ * @deprecated use {@link CoreModuleExtender#addCommitLogListener(CommitLogListener)}
+ */
+ @Deprecated(since = "5.0")
+ public CommitLogModuleExtender addListener(CommitLogListener instance) {
+ coreExtender.addCommitLogListener(instance);
return this;
}
+ /**
+ * @deprecated use {@link CoreModuleExtender#addCommitLogListener(Class)}
+ */
+ @Deprecated(since = "5.0")
public CommitLogModuleExtender addListener(Class<? extends CommitLogListener> type) {
- contributeCommitLogListeners().add(type);
- return this;
- }
-
- public CommitLogModuleExtender addListener(CommitLogListener instance) {
- contributeCommitLogListeners().add(instance);
+ coreExtender.addCommitLogListener(type);
return this;
}
/**
- * If called, events will be dispatched outside the main commit transaction. By default, events are dispatched
- * within the transaction, so listeners can commit their code together with the main commit.
+ * @deprecated use {@link CoreModuleExtender#excludeCommitLogFromTransaction()}
*/
+ @Deprecated(since = "5.0")
public CommitLogModuleExtender excludeFromTransaction() {
- CoreModule.extend(binder).addSyncFilter(createDiffInitFilter(), true);
- return registerFilter(false);
+ coreExtender.excludeCommitLogFromTransaction();
+ return this;
}
/**
- * @since 5.0
+ * No-op. Commit log listeners are now included in the transaction by default.
+ *
+ * @deprecated the default behavior is now to include in the transaction
*/
+ @Deprecated(since = "5.0")
public CommitLogModuleExtender includeInTransaction() {
- return registerFilter(true);
- }
-
- protected CommitLogModuleExtender registerFilter(boolean inTx) {
- CoreModule.extend(binder).addSyncFilter(CommitLogFilter.class, inTx);
return this;
}
/**
- * Installs entity filter that would only include entities annotated with
- * {@link CommitLog} on the callbacks. Also {@link CommitLog#confidential()}
- * properties will be obfuscated and {@link CommitLog#ignoredProperties()} -
- * excluded from the change collection.
+ * @deprecated use {@link CoreModuleExtender#commitLogAnnotationEntitiesOnly()}
*/
+ @Deprecated(since = "5.0")
public CommitLogModuleExtender commitLogAnnotationEntitiesOnly() {
- return entityFactory(AnnotationCommitLogEntityFactory.class);
+ coreExtender.commitLogAnnotationEntitiesOnly();
+ return this;
}
/**
- * Installs a custom factory for {@link CommitLogEntity} objects that allows implementors to use their own
- * annotations, etc.
+ * @deprecated use {@link CoreModuleExtender#commitLogEntityFactory(Class)}
*/
+ @Deprecated(since = "5.0")
public CommitLogModuleExtender entityFactory(Class<? extends CommitLogEntityFactory> entityFactoryType) {
- binder.bind(CommitLogEntityFactory.class).to(entityFactoryType);
+ coreExtender.commitLogEntityFactory(entityFactoryType);
return this;
}
-
- private ListBuilder<CommitLogListener> contributeCommitLogListeners() {
- if (commitLogListeners == null) {
- commitLogListeners = binder.bindList(CommitLogListener.class);
- }
- return commitLogListeners;
- }
-
- /**
- * @return the filter that just initializes incoming Diff
- */
- private static DataChannelSyncFilter createDiffInitFilter() {
- GraphChangeHandler noopHandler = new GraphChangeHandler() {};
- return (originatingContext, changes, syncType, filterChain)
- -> {
- // see ObjectStoreGraphDiff.resolveDiff()
- changes.apply(noopHandler);
- return filterChain.onSync(originatingContext, changes, syncType);
- };
- }
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModuleProvider.java b/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModuleProvider.java
index bd61f1f37..beb8b872a 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModuleProvider.java
+++ b/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogModuleProvider.java
@@ -27,8 +27,10 @@ import org.apache.cayenne.configuration.runtime.CoreModule;
import org.apache.cayenne.di.Module;
/**
+ * @deprecated the commit log module is now part of core Cayenne; this provider is a no-op.
* @since 4.0
*/
+@Deprecated(since = "5.0")
public class CommitLogModuleProvider implements CayenneRuntimeModuleProvider {
@Override
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLog.java b/cayenne/src/main/java/org/apache/cayenne/annotation/CommitLog.java
similarity index 64%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLog.java
copy to cayenne/src/main/java/org/apache/cayenne/annotation/CommitLog.java
index b42f32329..fec7f08ab 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLog.java
+++ b/cayenne/src/main/java/org/apache/cayenne/annotation/CommitLog.java
@@ -16,8 +16,7 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-
-package org.apache.cayenne.commitlog;
+package org.apache.cayenne.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@@ -27,9 +26,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * An annotation that adds commit log behavior to Persistent objects.
+ * An annotation that enables commit log tracking for a Persistent entity class.
+ * Place on entity classes to include them in commit log notifications delivered to
+ * {@link org.apache.cayenne.commitlog.CommitLogListener} implementations.
*
- * @since 4.0
+ * @since 5.0
+ * @see org.apache.cayenne.commitlog.CommitLogListener
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@@ -38,38 +40,29 @@ import java.lang.annotation.Target;
public @interface CommitLog {
/**
- * Returns an array of entity properties that should be excluded from commit log.
+ * Properties to exclude from commit log records.
*/
String[] ignoredProperties() default {};
/**
- * Returns whether all attributes should be excluded from commit log.
- *
- * @since 4.0
+ * When {@code true}, all attribute changes are excluded from commit log records.
*/
boolean ignoreAttributes() default false;
/**
- * Returns whether all to-one relationships should be excluded from commit log.
- *
- * @since 4.0
+ * When {@code true}, all to-one relationship changes are excluded from commit log records.
*/
boolean ignoreToOneRelationships() default false;
/**
- * Returns whether all to-many relationships should be excluded from commit log.
- *
- * @since 4.0
+ * When {@code true}, all to-many relationship changes are excluded from commit log records.
*/
boolean ignoreToManyRelationships() default false;
/**
- * Returns an array of properties that should be treated as confidential.
- * I.e. their change should be recorded, but their values should be hidden
- * from listeners. In practice both old and new values will be set to an
- * instance of {@link Confidential}.
- *
- * @since 4.0
+ * Properties whose values should be masked in commit log records. Changes to these properties
+ * are still captured, but old and new values are replaced with
+ * {@link org.apache.cayenne.commitlog.Confidential#getInstance()}.
*/
String[] confidential() default {};
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLog.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLog.java
similarity index 96%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLog.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLog.java
index b42f32329..79fb7c234 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLog.java
+++ b/cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLog.java
@@ -30,7 +30,9 @@ import java.lang.annotation.Target;
* An annotation that adds commit log behavior to Persistent objects.
*
* @since 4.0
+ * @deprecated use {@link org.apache.cayenne.annotation.CommitLog} instead.
*/
+@Deprecated(since = "5.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogFilter.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLogFilter.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogFilter.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLogFilter.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogListener.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLogListener.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/CommitLogListener.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/CommitLogListener.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/Confidential.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/Confidential.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/Confidential.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/Confidential.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/DeletedDiffProcessor.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/DeletedDiffProcessor.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/DeletedDiffProcessor.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/DeletedDiffProcessor.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/DiffFilter.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/DiffFilter.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/DiffFilter.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/DiffFilter.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/DiffProcessor.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/DiffProcessor.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/DiffProcessor.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/DiffProcessor.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/AnnotationCommitLogEntityFactory.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/meta/AnnotationCommitLogEntityFactory.java
similarity index 66%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/AnnotationCommitLogEntityFactory.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/meta/AnnotationCommitLogEntityFactory.java
index 3eb227861..6193b5e05 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/AnnotationCommitLogEntityFactory.java
+++ b/cayenne/src/main/java/org/apache/cayenne/commitlog/meta/AnnotationCommitLogEntityFactory.java
@@ -23,20 +23,24 @@ import java.util.concurrent.ConcurrentMap;
import org.apache.cayenne.DataChannel;
import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.annotation.CommitLog;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.di.Provider;
-import org.apache.cayenne.commitlog.CommitLog;
import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.reflect.ClassDescriptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Compiles {@link CommitLogEntity}'s based on {@link CommitLog} annotation.
- *
+ *
* @since 4.0
*/
public class AnnotationCommitLogEntityFactory implements CommitLogEntityFactory {
+ private static final Logger logger = LoggerFactory.getLogger(AnnotationCommitLogEntityFactory.class);
+
private static final CommitLogEntity BLOCKED_ENTITY = new CommitLogEntity() {
@Override
@@ -78,7 +82,6 @@ public class AnnotationCommitLogEntityFactory implements CommitLogEntityFactory
}
return descriptor;
-
}
private EntityResolver getEntityResolver() {
@@ -88,17 +91,29 @@ public class AnnotationCommitLogEntityFactory implements CommitLogEntityFactory
private CommitLogEntity createDescriptor(String entityName) {
EntityResolver entityResolver = getEntityResolver();
ClassDescriptor classDescriptor = entityResolver.getClassDescriptor(entityName);
+ Class<?> objectClass = classDescriptor.getObjectClass();
+
+ CommitLog a = objectClass.getAnnotation(CommitLog.class);
+ if (a != null) {
+ ObjEntity entity = entityResolver.getObjEntity(entityName);
+ return new MutableCommitLogLogEntity(entity).setConfidential(a.confidential())
+ .setIgnoreProperties(a.ignoredProperties()).setIgnoreAttributes(a.ignoreAttributes())
+ .setIgnoreToOneRelationships(a.ignoreToOneRelationships())
+ .setIgnoreToManyRelationships(a.ignoreToManyRelationships());
+ }
- CommitLog a = classDescriptor.getObjectClass().getAnnotation(CommitLog.class);
- if (a == null) {
- return BLOCKED_ENTITY;
+ @SuppressWarnings("deprecation")
+ org.apache.cayenne.commitlog.CommitLog legacyA = objectClass.getAnnotation(org.apache.cayenne.commitlog.CommitLog.class);
+ if (legacyA != null) {
+ logger.warn("Entity class '{}' uses deprecated @org.apache.cayenne.commitlog.CommitLog annotation. " +
+ "Replace with @org.apache.cayenne.annotation.CommitLog.", objectClass.getName());
+ ObjEntity entity = entityResolver.getObjEntity(entityName);
+ return new MutableCommitLogLogEntity(entity).setConfidential(legacyA.confidential())
+ .setIgnoreProperties(legacyA.ignoredProperties()).setIgnoreAttributes(legacyA.ignoreAttributes())
+ .setIgnoreToOneRelationships(legacyA.ignoreToOneRelationships())
+ .setIgnoreToManyRelationships(legacyA.ignoreToManyRelationships());
}
- ObjEntity entity = entityResolver.getObjEntity(entityName);
- return new MutableCommitLogLogEntity(entity).setConfidential(a.confidential())
- .setIgnoreProperties(a.ignoredProperties()).setIgnoreAttributes(a.ignoreAttributes())
- .setIgnoreToOneRelationships(a.ignoreToOneRelationships())
- .setIgnoreToManyRelationships(a.ignoreToManyRelationships());
+ return BLOCKED_ENTITY;
}
-
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java
similarity index 100%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java
copy to cayenne/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntityFactory.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntityFactory.java
similarity index 100%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntityFactory.java
copy to cayenne/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntityFactory.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/IncludeAllCommitLogEntityFactory.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/meta/IncludeAllCommitLogEntityFactory.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/IncludeAllCommitLogEntityFactory.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/meta/IncludeAllCommitLogEntityFactory.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/MutableCommitLogLogEntity.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/meta/MutableCommitLogLogEntity.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/MutableCommitLogLogEntity.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/meta/MutableCommitLogLogEntity.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/AttributeChange.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/AttributeChange.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/AttributeChange.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/model/AttributeChange.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ChangeMap.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/ChangeMap.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ChangeMap.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/model/ChangeMap.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/MutableAttributeChange.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/MutableAttributeChange.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/MutableAttributeChange.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/model/MutableAttributeChange.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/MutableChangeMap.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/MutableChangeMap.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/MutableChangeMap.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/model/MutableChangeMap.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/MutableObjectChange.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/MutableObjectChange.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/MutableObjectChange.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/model/MutableObjectChange.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/MutableToManyRelationshipChange.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/MutableToManyRelationshipChange.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/MutableToManyRelationshipChange.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/model/MutableToManyRelationshipChange.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/MutableToOneRelationshipChange.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/MutableToOneRelationshipChange.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/MutableToOneRelationshipChange.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/model/MutableToOneRelationshipChange.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChange.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/ObjectChange.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChange.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/model/ObjectChange.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
similarity index 100%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
copy to cayenne/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChange.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/PropertyChange.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChange.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/model/PropertyChange.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java
similarity index 100%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java
copy to cayenne/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ToManyRelationshipChange.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/ToManyRelationshipChange.java
similarity index 100%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ToManyRelationshipChange.java
rename to cayenne/src/main/java/org/apache/cayenne/commitlog/model/ToManyRelationshipChange.java
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ToOneRelationshipChange.java b/cayenne/src/main/java/org/apache/cayenne/commitlog/model/ToOneRelationshipChange.java
similarity index 100%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ToOneRelationshipChange.java
copy to cayenne/src/main/java/org/apache/cayenne/commitlog/model/ToOneRelationshipChange.java
diff --git a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
index b7ba5ee1b..bbe819e04 100644
--- a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
+++ b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java
@@ -21,6 +21,9 @@ package org.apache.cayenne.configuration.runtime;
import org.apache.cayenne.DataChannel;
import org.apache.cayenne.DataChannelQueryFilter;
import org.apache.cayenne.DataChannelSyncFilter;
+import org.apache.cayenne.commitlog.CommitLogFilter;
+import org.apache.cayenne.commitlog.meta.IncludeAllCommitLogEntityFactory;
+import org.apache.cayenne.commitlog.meta.CommitLogEntityFactory;
import org.apache.cayenne.access.DataDomain;
import org.apache.cayenne.access.DataRowStoreFactory;
import org.apache.cayenne.access.DefaultDataRowStoreFactory;
@@ -482,5 +485,8 @@ public class CoreModule implements Module {
binder.bind(DataDomainFlushActionFactory.class).to(DefaultDataDomainFlushActionFactory.class);
binder.bind(DbRowOpSorter.class).to(DefaultDbRowOpSorter.class);
+
+ binder.bind(CommitLogFilter.class).to(CommitLogFilter.class);
+ binder.bind(CommitLogEntityFactory.class).to(IncludeAllCommitLogEntityFactory.class);
}
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModuleExtender.java b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModuleExtender.java
index cf768d29c..eba9fd36a 100644
--- a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModuleExtender.java
+++ b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModuleExtender.java
@@ -24,12 +24,17 @@ import org.apache.cayenne.DataChannelSyncFilter;
import org.apache.cayenne.access.types.ExtendedType;
import org.apache.cayenne.access.types.ExtendedTypeFactory;
import org.apache.cayenne.access.types.ValueObjectType;
+import org.apache.cayenne.commitlog.CommitLogFilter;
+import org.apache.cayenne.commitlog.CommitLogListener;
+import org.apache.cayenne.commitlog.meta.AnnotationCommitLogEntityFactory;
+import org.apache.cayenne.commitlog.meta.CommitLogEntityFactory;
import org.apache.cayenne.configuration.Constants;
import org.apache.cayenne.dba.DbAdapter;
import org.apache.cayenne.dba.PkGenerator;
import org.apache.cayenne.di.Binder;
import org.apache.cayenne.di.ListBuilder;
import org.apache.cayenne.di.MapBuilder;
+import org.apache.cayenne.graph.GraphChangeHandler;
import org.apache.cayenne.tx.TransactionFilter;
/**
@@ -53,6 +58,8 @@ public class CoreModuleExtender {
private ListBuilder<ExtendedType> userExtendedTypes;
private ListBuilder<ExtendedTypeFactory> extendedTypeFactories;
private ListBuilder<ValueObjectType> valueObjectTypes;
+ private ListBuilder<CommitLogListener> commitLogListeners;
+ private boolean commitLogFilterRegistered;
protected CoreModuleExtender(Binder binder) {
this.binder = binder;
@@ -70,6 +77,7 @@ public class CoreModuleExtender {
contributeUserExtendedTypes();
contributeExtendedTypeFactories();
contributeValueObjectTypes();
+ contributeCommitLogListeners();
return this;
}
@@ -291,6 +299,85 @@ public class CoreModuleExtender {
return this;
}
+ /**
+ * Registers a commit log listener that receives post-commit change notifications.
+ *
+ * @since 5.0
+ */
+ public CoreModuleExtender addCommitLogListener(CommitLogListener listener) {
+ contributeCommitLogListeners().add(listener);
+ ensureCommitLogFilterRegistered(true);
+ return this;
+ }
+
+ /**
+ * Registers a commit log listener type that receives post-commit change notifications.
+ *
+ * @since 5.0
+ */
+ public CoreModuleExtender addCommitLogListener(Class<? extends CommitLogListener> listenerType) {
+ contributeCommitLogListeners().add(listenerType);
+ ensureCommitLogFilterRegistered(true);
+ return this;
+ }
+
+ /**
+ * Restricts commit log auditing to entities annotated with {@link org.apache.cayenne.commitlog.CommitLog}.
+ * By default all entities are audited.
+ *
+ * @since 5.0
+ */
+ public CoreModuleExtender commitLogAnnotationEntitiesOnly() {
+ binder.bind(CommitLogEntityFactory.class).to(AnnotationCommitLogEntityFactory.class);
+ return this;
+ }
+
+ /**
+ * Installs a custom factory for commit log entity metadata.
+ *
+ * @since 5.0
+ */
+ public CoreModuleExtender commitLogEntityFactory(Class<? extends CommitLogEntityFactory> factoryType) {
+ binder.bind(CommitLogEntityFactory.class).to(factoryType);
+ return this;
+ }
+
+ /**
+ * Configures commit log listeners to be notified outside the database transaction. By default listeners are called
+ * within the transaction, allowing them to participate in the same commit. Use this option when listeners may
+ * themselves write to the database and you want to avoid nested transactions.
+ *
+ * @since 5.0
+ */
+ public CoreModuleExtender excludeCommitLogFromTransaction() {
+ GraphChangeHandler noopHandler = new GraphChangeHandler() {};
+ DataChannelSyncFilter diffInitFilter = (originatingContext, changes, syncType, filterChain) -> {
+ changes.apply(noopHandler);
+ return filterChain.onSync(originatingContext, changes, syncType);
+ };
+ contributeSyncFilters().insertBefore(diffInitFilter, TransactionFilter.class);
+ ensureCommitLogFilterRegistered(false);
+ return this;
+ }
+
+ private void ensureCommitLogFilterRegistered(boolean inTx) {
+ if (!commitLogFilterRegistered) {
+ if (inTx) {
+ contributeSyncFilters().insertBefore(CommitLogFilter.class, TransactionFilter.class);
+ } else {
+ contributeSyncFilters().addAfter(CommitLogFilter.class, TransactionFilter.class);
+ }
+ commitLogFilterRegistered = true;
+ }
+ }
+
+ private ListBuilder<CommitLogListener> contributeCommitLogListeners() {
+ if (commitLogListeners == null) {
+ commitLogListeners = binder.bindList(CommitLogListener.class);
+ }
+ return commitLogListeners;
+ }
+
private ListBuilder<String> contributeProjectLocations() {
if (projectLocations == null) {
projectLocations = binder.bindList(String.class, Constants.PROJECT_LOCATIONS_LIST);
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilterIT.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilterIT.java
new file mode 100644
index 000000000..552173312
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilterIT.java
@@ -0,0 +1,336 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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.cayenne.commitlog;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.commitlog.db.Auditable1;
+import org.apache.cayenne.commitlog.db.AuditableChild1;
+import org.apache.cayenne.commitlog.db.AuditableChild1x;
+import org.apache.cayenne.commitlog.model.*;
+import org.apache.cayenne.commitlog.unit.AuditableRuntimeCase;
+import org.apache.cayenne.query.SelectById;
+import org.apache.cayenne.configuration.runtime.CoreModule;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.sql.SQLException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public class CommitLogFilterIT extends AuditableRuntimeCase {
+
+ protected ObjectContext context;
+ protected CommitLogListener mockListener;
+
+ @Override
+ protected CayenneRuntimeBuilder configureCayenne() {
+ this.mockListener = mock(CommitLogListener.class);
+ return super.configureCayenne()
+ .addModule(b -> CoreModule.extend(b).addCommitLogListener(mockListener));
+ }
+
+ @BeforeEach
+ public void before() {
+ context = runtime.newContext();
+ }
+
+ @Test
+ public void postCommit_Insert() {
+
+ Auditable1 a1 = context.newObject(Auditable1.class);
+ a1.setCharProperty1("yy");
+ ObjectId preCommitId = a1.getObjectId();
+
+ context.commitChanges();
+
+ ArgumentCaptor<ChangeMap> changeMap = ArgumentCaptor.forClass(ChangeMap.class);
+ verify(mockListener).onPostCommit(any(ObjectContext.class), changeMap.capture());
+
+ assertNotNull(changeMap.getValue());
+ assertEquals(2, changeMap.getValue().getChanges().size());
+ assertEquals(1, changeMap.getValue().getUniqueChanges().size());
+
+ ObjectChange c = changeMap.getValue().getUniqueChanges().iterator().next();
+ assertNotNull(c);
+ assertEquals(ObjectChangeType.INSERT, c.getType());
+ assertEquals(1, c.getAttributeChanges().size());
+ assertEquals("yy", c.getAttributeChanges().get(Auditable1.CHAR_PROPERTY1.getName()).getNewValue());
+
+ assertNotEquals(preCommitId, a1.getObjectId());
+ assertEquals(preCommitId, c.getPreCommitId());
+ assertEquals(a1.getObjectId(), c.getPostCommitId());
+ }
+
+ @Test
+ public void postCommit_Update() throws SQLException {
+
+ auditable1.insert(1, "xx");
+
+ Auditable1 a1 = SelectById.query(Auditable1.class, 1).selectOne(context);
+ a1.setCharProperty1("yy");
+
+ ObjectId preCommitId = a1.getObjectId();
+
+ context.commitChanges();
+
+ ArgumentCaptor<ChangeMap> changeMap = ArgumentCaptor.forClass(ChangeMap.class);
+ verify(mockListener).onPostCommit(any(ObjectContext.class), changeMap.capture());
+
+ assertNotNull(changeMap.getValue());
+ assertEquals(1, changeMap.getValue().getUniqueChanges().size());
+
+ ObjectChange c = changeMap.getValue().getChanges().get(ObjectId.of("Auditable1", Auditable1.ID_PK_COLUMN, 1));
+ assertNotNull(c);
+ assertEquals(ObjectChangeType.UPDATE, c.getType());
+ assertEquals(1, c.getAttributeChanges().size());
+ AttributeChange pc = c.getAttributeChanges().get(Auditable1.CHAR_PROPERTY1.getName());
+ assertNotNull(pc);
+ assertEquals("xx", pc.getOldValue());
+ assertEquals("yy", pc.getNewValue());
+
+ assertEquals(preCommitId, a1.getObjectId());
+ assertEquals(preCommitId, c.getPreCommitId());
+ assertEquals(preCommitId, c.getPostCommitId());
+ }
+
+ @Test
+ public void postCommit_Delete() throws SQLException {
+ auditable1.insert(1, "xx");
+ auditableChild1.insert(1, 1, "cc1");
+ auditableChild1.insert(2, 1, "cc2");
+
+ Auditable1 a1 = SelectById.query(Auditable1.class, 1).selectOne(context);
+ context.deleteObjects(a1.getChildren1());
+ context.deleteObject(a1);
+ context.commitChanges();
+
+ ArgumentCaptor<ChangeMap> changeMap = ArgumentCaptor.forClass(ChangeMap.class);
+ verify(mockListener).onPostCommit(any(ObjectContext.class), changeMap.capture());
+
+ assertNotNull(changeMap.getValue());
+ assertEquals(3, changeMap.getValue().getUniqueChanges().size());
+
+ // check from the perspective of the master object
+ ObjectChange masterChange = changeMap.getValue().getChanges().get(ObjectId.of("Auditable1", Auditable1.ID_PK_COLUMN, 1));
+ assertNotNull(masterChange);
+ assertEquals(ObjectChangeType.DELETE, masterChange.getType());
+
+ assertEquals(1, masterChange.getAttributeChanges().size());
+ assertEquals("xx", masterChange.getAttributeChanges().get(Auditable1.CHAR_PROPERTY1.getName()).getOldValue());
+ assertNull(masterChange.getAttributeChanges().get(Auditable1.CHAR_PROPERTY1.getName()).getNewValue());
+
+ assertEquals(1, masterChange.getToManyRelationshipChanges().size(),
+ "1..N was explicitly unset as a part of delete. Expected to be recorded in changes");
+ assertTrue(masterChange.getToOneRelationshipChanges().isEmpty(), "No N..1 relationships in the entity");
+
+ // check from the perspective of the child object
+ ObjectChange childChange = changeMap.getValue().getChanges().get(ObjectId.of("AuditableChild1", AuditableChild1.ID_PK_COLUMN, 2));
+ assertNotNull(childChange);
+ assertEquals(ObjectChangeType.DELETE, childChange.getType());
+
+ assertEquals(1, childChange.getAttributeChanges().size());
+ assertEquals("cc2", childChange.getAttributeChanges().get(AuditableChild1.CHAR_PROPERTY1.getName()).getOldValue());
+ assertNull(childChange.getAttributeChanges().get(AuditableChild1.CHAR_PROPERTY1.getName()).getNewValue());
+
+ assertTrue(childChange.getToManyRelationshipChanges().isEmpty(), "No 1..N relationships in the entity");
+ assertEquals(1, childChange.getToOneRelationshipChanges().size(),
+ "N..1 was explicitly unset as a part of delete. Expected to be recorded in changes");
+ }
+
+ @Test
+ public void postCommit_Delete_ToOneNullify() throws SQLException {
+ auditable1.insert(1, "xx");
+ auditableChild1.insert(1, 1, "cc1");
+ auditableChild1.insert(2, 1, "cc2");
+
+ AuditableChild1 ac1 = SelectById.query(AuditableChild1.class, 2).selectOne(context);
+ context.deleteObject(ac1);
+ context.commitChanges();
+
+ ArgumentCaptor<ChangeMap> changeMap = ArgumentCaptor.forClass(ChangeMap.class);
+ verify(mockListener).onPostCommit(any(ObjectContext.class), changeMap.capture());
+
+ assertNotNull(changeMap.getValue());
+ assertEquals(2, changeMap.getValue().getUniqueChanges().size());
+
+ ObjectChange change = changeMap.getValue().getChanges().get(ObjectId.of("AuditableChild1", AuditableChild1.ID_PK_COLUMN, 2));
+ assertNotNull(change);
+ assertEquals(ObjectChangeType.DELETE, change.getType());
+
+ assertEquals(1, change.getAttributeChanges().size());
+ assertEquals("cc2", change.getAttributeChanges().get(AuditableChild1.CHAR_PROPERTY1.getName()).getOldValue());
+ assertNull(change.getAttributeChanges().get(AuditableChild1.CHAR_PROPERTY1.getName()).getNewValue());
+
+ assertTrue(change.getToManyRelationshipChanges().isEmpty(), "No 1..N relationships in the entity");
+ assertEquals(1, change.getToOneRelationshipChanges().size(), "N..1 state was not captured");
+ assertEquals(ObjectId.of("Auditable1", Auditable1.ID_PK_COLUMN, 1),
+ change.getToOneRelationshipChanges().get(AuditableChild1.PARENT.getName()).getOldValue());
+ }
+
+ @Test
+ public void postCommit_Delete_ToOne_OneWay() throws SQLException {
+ auditable1.insert(1, "xx");
+ auditableChild1x.insert(1, 1, "cc1");
+ auditableChild1x.insert(2, 1, "cc2");
+
+ AuditableChild1x ac1 = SelectById.query(AuditableChild1x.class, 2).selectOne(context);
+ context.deleteObject(ac1);
+ context.commitChanges();
+
+ ArgumentCaptor<ChangeMap> changeMap = ArgumentCaptor.forClass(ChangeMap.class);
+ verify(mockListener).onPostCommit(any(ObjectContext.class), changeMap.capture());
+
+ assertNotNull(changeMap.getValue());
+ assertEquals(1, changeMap.getValue().getUniqueChanges().size());
+
+ ObjectChange change = changeMap.getValue().getChanges().get(ObjectId.of("AuditableChild1x", AuditableChild1x.ID_PK_COLUMN, 2));
+ assertNotNull(change);
+ assertEquals(ObjectChangeType.DELETE, change.getType());
+
+ assertEquals(1, change.getAttributeChanges().size());
+ assertEquals("cc2", change.getAttributeChanges().get(AuditableChild1x.CHAR_PROPERTY1.getName()).getOldValue());
+ assertNull(change.getAttributeChanges().get(AuditableChild1x.CHAR_PROPERTY1.getName()).getNewValue());
+
+ assertTrue(change.getToManyRelationshipChanges().isEmpty(), "No 1..N relationships in the entity");
+ assertEquals(1, change.getToOneRelationshipChanges().size(), "N..1 state was not captured");
+ assertEquals(ObjectId.of("Auditable1", Auditable1.ID_PK_COLUMN, 1),
+ change.getToOneRelationshipChanges().get(AuditableChild1x.PARENT.getName()).getOldValue());
+ }
+
+
+ @Test
+ public void postCommit_UpdateToOne() throws SQLException {
+ auditable1.insert(1, "xx");
+ auditable1.insert(2, "yy");
+
+ auditableChild1.insert(1, 1, "cc1");
+ auditableChild1.insert(2, 2, "cc2");
+ auditableChild1.insert(3, null, "cc3");
+
+ AuditableChild1 ac1 = SelectById.query(AuditableChild1.class, 1).selectOne(context);
+ AuditableChild1 ac2 = SelectById.query(AuditableChild1.class, 2).selectOne(context);
+ AuditableChild1 ac3 = SelectById.query(AuditableChild1.class, 3).selectOne(context);
+
+ Auditable1 a1 = SelectById.query(Auditable1.class, 1).selectOne(context);
+ Auditable1 a2 = SelectById.query(Auditable1.class, 2).selectOne(context);
+
+ a1.removeFromChildren1(ac1);
+ a1.addToChildren1(ac2);
+ a1.addToChildren1(ac3);
+
+ context.commitChanges();
+
+ ArgumentCaptor<ChangeMap> changeMap = ArgumentCaptor.forClass(ChangeMap.class);
+ verify(mockListener).onPostCommit(any(ObjectContext.class), changeMap.capture());
+
+ assertNotNull(changeMap.getValue());
+ assertEquals(5, changeMap.getValue().getUniqueChanges().size());
+
+ ObjectChange a1c = changeMap.getValue().getChanges().get(
+ ObjectId.of("Auditable1", Auditable1.ID_PK_COLUMN, 1));
+ assertNotNull(a1c);
+ assertEquals(ObjectChangeType.UPDATE, a1c.getType());
+ ToManyRelationshipChange a1c1 = a1c.getToManyRelationshipChanges().get(Auditable1.CHILDREN1.getName());
+ assertEquals(2, a1c1.getAdded().size());
+ assertEquals(1, a1c1.getRemoved().size());
+
+ ObjectChange a2c = changeMap.getValue().getChanges().get(
+ ObjectId.of("Auditable1", Auditable1.ID_PK_COLUMN, 2));
+ assertNotNull(a2c);
+ assertEquals(ObjectChangeType.UPDATE, a2c.getType());
+ ToManyRelationshipChange a2c1 = a2c.getToManyRelationshipChanges().get(Auditable1.CHILDREN1.getName());
+ assertEquals(0, a2c1.getAdded().size());
+ assertEquals(1, a2c1.getRemoved().size());
+
+ ObjectChange ac1c = changeMap.getValue().getChanges().get(
+ ObjectId.of("AuditableChild1", AuditableChild1.ID_PK_COLUMN, 1));
+ assertNotNull(ac1c);
+ assertEquals(ObjectChangeType.UPDATE, ac1c.getType());
+ ToOneRelationshipChange ac1c1 = ac1c.getToOneRelationshipChanges().get(AuditableChild1.PARENT.getName());
+ assertEquals(a1.getObjectId(), ac1c1.getOldValue());
+ assertNull(ac1c1.getNewValue());
+
+ ObjectChange ac2c = changeMap.getValue().getChanges().get(
+ ObjectId.of("AuditableChild1", AuditableChild1.ID_PK_COLUMN, 2));
+ assertNotNull(ac2c);
+ assertEquals(ObjectChangeType.UPDATE, ac2c.getType());
+ ToOneRelationshipChange ac2c1 = ac2c.getToOneRelationshipChanges()
+ .get(AuditableChild1.PARENT.getName());
+ assertEquals(a2.getObjectId(), ac2c1.getOldValue());
+ assertEquals(a1.getObjectId(), ac2c1.getNewValue());
+
+ ObjectChange ac3c = changeMap.getValue().getChanges().get(
+ ObjectId.of("AuditableChild1", AuditableChild1.ID_PK_COLUMN, 3));
+ assertNotNull(ac3c);
+ assertEquals(ObjectChangeType.UPDATE, ac3c.getType());
+ ToOneRelationshipChange ac3c1 = ac3c.getToOneRelationshipChanges()
+ .get(AuditableChild1.PARENT.getName());
+ assertNull(ac3c1.getOldValue());
+ assertEquals(a1.getObjectId(), ac3c1.getNewValue());
+ }
+
+ @Test
+ public void postCommit_UpdateToMany() throws SQLException {
+ auditable1.insert(1, "xx");
+ auditableChild1.insert(1, 1, "cc1");
+ auditableChild1.insert(2, null, "cc2");
+ auditableChild1.insert(3, null, "cc3");
+
+ AuditableChild1 ac1 = SelectById.query(AuditableChild1.class, 1).selectOne(context);
+ AuditableChild1 ac2 = SelectById.query(AuditableChild1.class, 2).selectOne(context);
+ AuditableChild1 ac3 = SelectById.query(AuditableChild1.class, 3).selectOne(context);
+
+ Auditable1 a1 = SelectById.query(Auditable1.class, 1).selectOne(context);
+
+ a1.removeFromChildren1(ac1);
+ a1.addToChildren1(ac2);
+ a1.addToChildren1(ac3);
+
+ context.commitChanges();
+
+ ArgumentCaptor<ChangeMap> changeMap = ArgumentCaptor.forClass(ChangeMap.class);
+ verify(mockListener).onPostCommit(any(ObjectContext.class), changeMap.capture());
+
+ assertNotNull(changeMap.getValue());
+ assertEquals(4, changeMap.getValue().getUniqueChanges().size());
+
+ ObjectChange a1c = changeMap.getValue().getChanges().get(ObjectId.of("Auditable1", Auditable1.ID_PK_COLUMN, 1));
+ assertNotNull(a1c);
+ assertEquals(ObjectChangeType.UPDATE, a1c.getType());
+ assertEquals(0, a1c.getAttributeChanges().size());
+
+ assertEquals(1, a1c.getToManyRelationshipChanges().size());
+
+ ToManyRelationshipChange a1c1 = a1c.getToManyRelationshipChanges().get(Auditable1.CHILDREN1.getName());
+ assertNotNull(a1c1);
+
+ assertEquals(2, a1c1.getAdded().size());
+ assertTrue(a1c1.getAdded().contains(ac2.getObjectId()));
+ assertTrue(a1c1.getAdded().contains(ac3.getObjectId()));
+
+ assertEquals(1, a1c1.getRemoved().size());
+ assertTrue(a1c1.getRemoved().contains(ac1.getObjectId()));
+
+ }
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_All_FlattenedIT.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_All_FlattenedIT.java
new file mode 100644
index 000000000..13eaac30c
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_All_FlattenedIT.java
@@ -0,0 +1,133 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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.cayenne.commitlog;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.commitlog.db.E3;
+import org.apache.cayenne.commitlog.db.E4;
+import org.apache.cayenne.commitlog.model.ChangeMap;
+import org.apache.cayenne.commitlog.model.ObjectChange;
+import org.apache.cayenne.commitlog.model.ObjectChangeType;
+import org.apache.cayenne.commitlog.model.ToManyRelationshipChange;
+import org.apache.cayenne.commitlog.unit.FlattenedRuntimeCase;
+import org.apache.cayenne.query.SelectById;
+import org.apache.cayenne.configuration.runtime.CoreModule;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.stubbing.Answer;
+
+import java.sql.SQLException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public class CommitLogFilter_All_FlattenedIT extends FlattenedRuntimeCase {
+
+ protected ObjectContext context;
+ protected CommitLogListener mockListener;
+
+ @Override
+ protected CayenneRuntimeBuilder configureCayenne() {
+ this.mockListener = mock(CommitLogListener.class);
+ return super.configureCayenne()
+ .addModule(b -> CoreModule.extend(b).addCommitLogListener(mockListener));
+ }
+
+ @BeforeEach
+ public void before() {
+ context = runtime.newContext();
+ }
+
+ @Test
+ public void postCommit_UpdateToMany() throws SQLException {
+ e3.insert(1);
+ e4.insert(11);
+ e4.insert(12);
+ e34.insert(1, 11);
+
+ E3 e3 = SelectById.query(E3.class, 1).selectOne(context);
+ E4 e4_1 = SelectById.query(E4.class, 11).selectOne(context);
+ E4 e4_2 = SelectById.query(E4.class, 12).selectOne(context);
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertSame(context, invocation.getArguments()[0]);
+
+ ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+ assertNotNull(changes);
+ assertEquals(3, changes.getUniqueChanges().size());
+
+ ObjectChange e3c = changes.getChanges().get(ObjectId.of("E3", E3.ID_PK_COLUMN, 1));
+ assertNotNull(e3c);
+ assertEquals(ObjectChangeType.UPDATE, e3c.getType());
+ assertEquals(0, e3c.getAttributeChanges().size());
+ assertEquals(1, e3c.getToManyRelationshipChanges().size());
+
+ ToManyRelationshipChange e3c1 = e3c.getToManyRelationshipChanges().get(E3.E4S.getName());
+ assertNotNull(e3c1);
+
+ assertEquals(1, e3c1.getAdded().size());
+ assertTrue(e3c1.getAdded().contains(e4_2.getObjectId()));
+
+ assertEquals(1, e3c1.getRemoved().size());
+ assertTrue(e3c1.getRemoved().contains(e4_1.getObjectId()));
+
+ ObjectChange e41c = changes.getChanges().get(ObjectId.of("E4", E4.ID_PK_COLUMN, 11));
+ assertNotNull(e41c);
+ assertEquals(ObjectChangeType.UPDATE, e41c.getType());
+ assertEquals(0, e41c.getAttributeChanges().size());
+ assertEquals(1, e41c.getToManyRelationshipChanges().size());
+
+ ToManyRelationshipChange e41c1 = e41c.getToManyRelationshipChanges().get(E4.E3S.getName());
+ assertNotNull(e41c);
+
+ assertEquals(0, e41c1.getAdded().size());
+
+ assertEquals(1, e41c1.getRemoved().size());
+ assertTrue(e41c1.getRemoved().contains(e3.getObjectId()));
+
+ ObjectChange e42c = changes.getChanges().get(ObjectId.of("E4", E4.ID_PK_COLUMN, 12));
+ assertNotNull(e42c);
+ assertEquals(ObjectChangeType.UPDATE, e42c.getType());
+ assertEquals(0, e42c.getAttributeChanges().size());
+ assertEquals(1, e42c.getToManyRelationshipChanges().size());
+
+ ToManyRelationshipChange e42c1 = e42c.getToManyRelationshipChanges().get(E4.E3S.getName());
+ assertNotNull(e42c);
+
+ assertEquals(0, e42c1.getRemoved().size());
+
+ assertEquals(1, e42c1.getAdded().size());
+ assertTrue(e42c1.getAdded().contains(e3.getObjectId()));
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ e3.removeFromE4s(e4_1);
+ e3.addToE4s(e4_2);
+
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_FilteredIT.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_FilteredIT.java
new file mode 100644
index 000000000..4c624a749
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_FilteredIT.java
@@ -0,0 +1,287 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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.cayenne.commitlog;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.commitlog.db.Auditable1;
+import org.apache.cayenne.commitlog.db.Auditable2;
+import org.apache.cayenne.commitlog.db.Auditable3;
+import org.apache.cayenne.commitlog.db.Auditable4;
+import org.apache.cayenne.commitlog.db.AuditableChild1;
+import org.apache.cayenne.commitlog.model.AttributeChange;
+import org.apache.cayenne.commitlog.model.ChangeMap;
+import org.apache.cayenne.commitlog.model.ObjectChange;
+import org.apache.cayenne.commitlog.model.ObjectChangeType;
+import org.apache.cayenne.commitlog.model.ToManyRelationshipChange;
+import org.apache.cayenne.commitlog.unit.AuditableRuntimeCase;
+import org.apache.cayenne.query.SelectById;
+import org.apache.cayenne.configuration.runtime.CoreModule;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.stubbing.Answer;
+
+import java.sql.SQLException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public class CommitLogFilter_FilteredIT extends AuditableRuntimeCase {
+
+ protected ObjectContext context;
+ protected CommitLogListener mockListener;
+
+ @Override
+ protected CayenneRuntimeBuilder configureCayenne() {
+ this.mockListener = mock(CommitLogListener.class);
+ return super.configureCayenne().addModule(
+ b -> CoreModule.extend(b).commitLogAnnotationEntitiesOnly().addCommitLogListener(mockListener));
+ }
+
+ @BeforeEach
+ public void before() {
+ context = runtime.newContext();
+ }
+
+ @Test
+ public void postCommit_Insert() {
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertSame(context, invocation.getArguments()[0]);
+
+ ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+ assertNotNull(changes);
+ assertEquals(2, changes.getChanges().size());
+ assertEquals(1, changes.getUniqueChanges().size());
+
+ ObjectChange c = changes.getUniqueChanges().iterator().next();
+ assertNotNull(c);
+ assertEquals(ObjectChangeType.INSERT, c.getType());
+ assertEquals(1, c.getAttributeChanges().size());
+
+ assertEquals(Confidential.getInstance(),
+ c.getAttributeChanges().get(Auditable2.CHAR_PROPERTY2.getName()).getNewValue());
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ Auditable2 a1 = context.newObject(Auditable2.class);
+ a1.setCharProperty1("yy");
+ a1.setCharProperty2("zz");
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+
+ @Test
+ public void postCommit_Update() throws SQLException {
+ auditable2.insert(1, "P1_1", "P2_1");
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertSame(context, invocation.getArguments()[0]);
+
+ ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+ assertNotNull(changes);
+ assertEquals(1, changes.getUniqueChanges().size());
+
+ ObjectChange c = changes.getChanges().get(ObjectId.of("Auditable2", Auditable2.ID_PK_COLUMN, 1));
+ assertNotNull(c);
+ assertEquals(ObjectChangeType.UPDATE, c.getType());
+ assertEquals(1, c.getAttributeChanges().size());
+ AttributeChange pc = c.getAttributeChanges().get(Auditable2.CHAR_PROPERTY2.getName());
+ assertNotNull(pc);
+ assertEquals(Confidential.getInstance(), pc.getOldValue());
+ assertEquals(Confidential.getInstance(), pc.getNewValue());
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ Auditable2 a1 = SelectById.query(Auditable2.class, 1).selectOne(context);
+ a1.setCharProperty1("P1_2");
+ a1.setCharProperty2("P2_2");
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+
+ @Test
+ public void postCommit_Delete() throws SQLException {
+ auditable2.insert(1, "P1_1", "P2_1");
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertSame(context, invocation.getArguments()[0]);
+
+ ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+ assertNotNull(changes);
+ assertEquals(1, changes.getUniqueChanges().size());
+
+ ObjectChange c = changes.getChanges().get(ObjectId.of("Auditable2", Auditable2.ID_PK_COLUMN, 1));
+ assertNotNull(c);
+ assertEquals(ObjectChangeType.DELETE, c.getType());
+ assertEquals(1, c.getAttributeChanges().size());
+ assertEquals(Confidential.getInstance(),
+ c.getAttributeChanges().get(Auditable2.CHAR_PROPERTY2.getName()).getOldValue());
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ Auditable2 a1 = SelectById.query(Auditable2.class, 1).selectOne(context);
+ context.deleteObject(a1);
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+
+ @Test
+ public void postCommit_UpdateToMany() throws SQLException {
+ auditable1.insert(1, "xx");
+ auditableChild1.insert(1, 1, "cc1");
+ auditableChild1.insert(2, null, "cc2");
+ auditableChild1.insert(3, null, "cc3");
+
+ final AuditableChild1 ac1 = SelectById.query(AuditableChild1.class, 1).selectOne(context);
+ final AuditableChild1 ac2 = SelectById.query(AuditableChild1.class, 2).selectOne(context);
+ final AuditableChild1 ac3 = SelectById.query(AuditableChild1.class, 3).selectOne(context);
+
+ final Auditable1 a1 = SelectById.query(Auditable1.class, 1).selectOne(context);
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertSame(context, invocation.getArguments()[0]);
+
+ ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+ assertNotNull(changes);
+ assertEquals(1, changes.getUniqueChanges().size());
+
+ ObjectChange a1c = changes.getChanges().get(ObjectId.of("Auditable1", Auditable1.ID_PK_COLUMN, 1));
+ assertNotNull(a1c);
+ assertEquals(ObjectChangeType.UPDATE, a1c.getType());
+ assertEquals(0, a1c.getAttributeChanges().size());
+
+ assertEquals(1, a1c.getToManyRelationshipChanges().size());
+
+ ToManyRelationshipChange a1c1 = a1c.getToManyRelationshipChanges().get(Auditable1.CHILDREN1.getName());
+ assertNotNull(a1c1);
+
+ assertEquals(2, a1c1.getAdded().size());
+ assertTrue(a1c1.getAdded().contains(ac2.getObjectId()));
+ assertTrue(a1c1.getAdded().contains(ac3.getObjectId()));
+
+ assertEquals(1, a1c1.getRemoved().size());
+ assertTrue(a1c1.getRemoved().contains(ac1.getObjectId()));
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ a1.removeFromChildren1(ac1);
+ a1.addToChildren1(ac2);
+ a1.addToChildren1(ac3);
+
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+
+ @Test
+ public void postCommit_IgnoreAttributes() throws SQLException {
+
+ auditable3.insert(1, "31", "32");
+
+ final Auditable3 a3 = SelectById.query(Auditable3.class, 1).selectOne(context);
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertSame(context, invocation.getArguments()[0]);
+
+ ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+ assertNull(changes.getChanges().get(ObjectId.of("Auditable3", Auditable3.ID_PK_COLUMN, 1)));
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ a3.setCharProperty1("33");
+ a3.setCharProperty2("34");
+
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+
+ @Test
+ public void postCommit_IgnoreToMany() throws SQLException {
+
+ auditable3.insert(1, "31", "32");
+ auditable4.insert(11, "41", "42", 1);
+ auditable4.insert(12, "43", "44", 1);
+
+ final Auditable3 a3 = SelectById.query(Auditable3.class, 1).selectOne(context);
+ final Auditable4 a41 = SelectById.query(Auditable4.class, 11).selectOne(context);
+ final Auditable4 a42 = SelectById.query(Auditable4.class, 12).selectOne(context);
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertSame(context, invocation.getArguments()[0]);
+
+ ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+ assertNull(changes.getChanges().get(ObjectId.of("Auditable3", Auditable3.ID_PK_COLUMN, 1)));
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ a3.removeFromAuditable4s(a41);
+ a3.addToAuditable4s(a42);
+
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+
+ @Test
+ public void postCommit_IgnoreToOne() throws SQLException {
+
+ auditable3.insert(1, "31", "32");
+ auditable3.insert(2, "33", "34");
+ auditable4.insert(11, "41", "41", 1);
+
+ final Auditable3 a32 = SelectById.query(Auditable3.class, 2).selectOne(context);
+
+ final Auditable4 a4 = SelectById.query(Auditable4.class, 11).selectOne(context);
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertSame(context, invocation.getArguments()[0]);
+
+ ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+ assertNull(changes.getChanges().get(ObjectId.of("Auditable4", Auditable4.ID_PK_COLUMN, 11)));
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ a4.setAuditable3(a32);
+
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_ListenerInducedChangesIT.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_ListenerInducedChangesIT.java
new file mode 100644
index 000000000..7fcf8a232
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_ListenerInducedChangesIT.java
@@ -0,0 +1,240 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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.cayenne.commitlog;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.annotation.PrePersist;
+import org.apache.cayenne.annotation.PreUpdate;
+import org.apache.cayenne.commitlog.db.Auditable1;
+import org.apache.cayenne.commitlog.db.AuditableChild1;
+import org.apache.cayenne.commitlog.model.AttributeChange;
+import org.apache.cayenne.commitlog.model.ChangeMap;
+import org.apache.cayenne.commitlog.model.ObjectChange;
+import org.apache.cayenne.commitlog.model.ObjectChangeType;
+import org.apache.cayenne.commitlog.unit.AuditableRuntimeCase;
+import org.apache.cayenne.query.SelectById;
+import org.apache.cayenne.configuration.runtime.CoreModule;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+/**
+ * Testing capturing changes introduced by the pre-commit listeners.
+ */
+public class CommitLogFilter_ListenerInducedChangesIT extends AuditableRuntimeCase {
+
+ protected ObjectContext context;
+ protected CommitLogListener mockListener;
+
+ @Override
+ protected CayenneRuntimeBuilder configureCayenne() {
+ this.mockListener = mock(CommitLogListener.class);
+ return super.configureCayenne().addModule(
+ b -> CoreModule.extend(b).addCommitLogListener(mockListener));
+ }
+
+ @BeforeEach
+ public void before() {
+ context = runtime.newContext();
+ }
+
+ @Test
+ public void postCommit_Insert() {
+
+ final InsertListener listener = new InsertListener();
+ runtime.getDataDomain().addListener(listener);
+
+ final Auditable1 a1 = context.newObject(Auditable1.class);
+ a1.setCharProperty1("yy");
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertNotNull(listener.c);
+
+ List<ObjectChange> sortedChanges = sortedChanges(invocation);
+
+ assertEquals(2, sortedChanges.size());
+
+ assertEquals(a1.getObjectId(), sortedChanges.get(0).getPostCommitId());
+ assertEquals(ObjectChangeType.INSERT, sortedChanges.get(0).getType());
+
+ assertEquals(listener.c.getObjectId(), sortedChanges.get(1).getPostCommitId());
+ assertEquals(ObjectChangeType.INSERT, sortedChanges.get(1).getType());
+
+ AttributeChange listenerInducedChange = sortedChanges.get(1).getAttributeChanges()
+ .get(AuditableChild1.CHAR_PROPERTY1.getName());
+ assertNotNull(listenerInducedChange);
+ assertEquals("c1", listenerInducedChange.getNewValue());
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+
+ @Test
+ public void postCommit_Delete() throws SQLException {
+
+ auditable1.insert(1, "yy");
+ auditableChild1.insert(31, 1, "yyc");
+
+ final DeleteListener listener = new DeleteListener();
+ runtime.getDataDomain().addListener(listener);
+
+ final Auditable1 a1 = SelectById.query(Auditable1.class, 1).prefetch(Auditable1.CHILDREN1.joint())
+ .selectFirst(context);
+ a1.setCharProperty1("zz");
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertNotNull(listener.toDelete);
+ assertEquals(1, listener.toDelete.size());
+
+ List<ObjectChange> sortedChanges = sortedChanges(invocation);
+
+ assertEquals(2, sortedChanges.size());
+
+ assertEquals(ObjectChangeType.UPDATE, sortedChanges.get(0).getType());
+ assertEquals(a1.getObjectId(), sortedChanges.get(0).getPostCommitId());
+
+ assertEquals(ObjectChangeType.DELETE, sortedChanges.get(1).getType());
+ assertEquals(listener.toDelete.get(0).getObjectId(), sortedChanges.get(1).getPostCommitId());
+
+ AttributeChange listenerInducedChange = sortedChanges.get(1).getAttributeChanges()
+ .get(AuditableChild1.CHAR_PROPERTY1.getName());
+ assertNotNull(listenerInducedChange);
+ assertEquals("yyc", listenerInducedChange.getOldValue());
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+
+ @Test
+ public void postCommit_Update() throws SQLException {
+
+ auditable1.insert(1, "yy");
+ auditableChild1.insert(31, 1, "yyc");
+
+ final UpdateListener listener = new UpdateListener();
+ runtime.getDataDomain().addListener(listener);
+
+ final Auditable1 a1 = SelectById.query(Auditable1.class, 1).prefetch(Auditable1.CHILDREN1.joint())
+ .selectFirst(context);
+ a1.setCharProperty1("zz");
+
+ doAnswer((Answer<Object>) invocation -> {
+
+ assertNotNull(listener.toUpdate);
+ assertEquals(1, listener.toUpdate.size());
+
+ List<ObjectChange> sortedChanges = sortedChanges(invocation);
+
+ assertEquals(2, sortedChanges.size());
+
+ assertEquals(ObjectChangeType.UPDATE, sortedChanges.get(0).getType());
+ assertEquals(a1.getObjectId(), sortedChanges.get(0).getPostCommitId());
+
+ assertEquals(ObjectChangeType.UPDATE, sortedChanges.get(1).getType());
+ assertEquals(listener.toUpdate.get(0).getObjectId(), sortedChanges.get(1).getPostCommitId());
+
+ AttributeChange listenerInducedChange = sortedChanges.get(1).getAttributeChanges()
+ .get(AuditableChild1.CHAR_PROPERTY1.getName());
+ assertNotNull(listenerInducedChange);
+ assertEquals("yyc", listenerInducedChange.getOldValue());
+ assertEquals("yyc_", listenerInducedChange.getNewValue());
+
+ return null;
+ }).when(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+
+ context.commitChanges();
+
+ verify(mockListener).onPostCommit(any(ObjectContext.class), any(ChangeMap.class));
+ }
+
+ private List<ObjectChange> sortedChanges(InvocationOnMock invocation) {
+ assertSame(context, invocation.getArguments()[0]);
+
+ ChangeMap changes = (ChangeMap) invocation.getArguments()[1];
+
+ List<ObjectChange> sortedChanges = new ArrayList<>(changes.getUniqueChanges());
+ sortedChanges.sort(Comparator.comparing(o -> o.getPostCommitId().getEntityName()));
+
+ return sortedChanges;
+ }
+
+ static class InsertListener {
+
+ private AuditableChild1 c;
+
+ @PrePersist(Auditable1.class)
+ public void prePersist(Auditable1 a) {
+
+ c = a.getObjectContext().newObject(AuditableChild1.class);
+ c.setCharProperty1("c1");
+ c.setParent(a);
+ }
+ }
+
+ static class DeleteListener {
+
+ private List<AuditableChild1> toDelete;
+
+ @PreUpdate(Auditable1.class)
+ public void prePersist(Auditable1 a) {
+
+ toDelete = new ArrayList<>(a.getChildren1());
+ for (AuditableChild1 c : toDelete) {
+ c.getObjectContext().deleteObject(c);
+ }
+ }
+ }
+
+ static class UpdateListener {
+
+ private List<AuditableChild1> toUpdate;
+
+ @PreUpdate(Auditable1.class)
+ public void prePersist(Auditable1 a) {
+
+ toUpdate = new ArrayList<>(a.getChildren1());
+ for (AuditableChild1 c : toUpdate) {
+ c.setCharProperty1(c.getCharProperty1() + "_");
+ }
+ }
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_OutsideTxIT.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_OutsideTxIT.java
new file mode 100644
index 000000000..25379f500
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_OutsideTxIT.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
+ *
+ * https://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.cayenne.commitlog;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.commitlog.db.AuditLog;
+import org.apache.cayenne.commitlog.db.Auditable2;
+import org.apache.cayenne.commitlog.model.ObjectChange;
+import org.apache.cayenne.commitlog.unit.AuditableRuntimeCase;
+import org.apache.cayenne.configuration.runtime.CoreModule;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+import org.apache.cayenne.tx.BaseTransaction;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class CommitLogFilter_OutsideTxIT extends AuditableRuntimeCase {
+
+ protected ObjectContext context;
+ protected CommitLogListener listener;
+
+ @Override
+ protected CayenneRuntimeBuilder configureCayenne() {
+ this.listener = (originatingContext, changes) -> {
+
+ // assert we are inside transaction
+ assertNull(BaseTransaction.getThreadTransaction());
+
+ for (ObjectChange c : changes.getUniqueChanges()) {
+ AuditLog log = runtime.newContext().newObject(AuditLog.class);
+ log.setLog("DONE: " + c.getPostCommitId());
+ log.getObjectContext().commitChanges();
+ }
+ };
+ return super.configureCayenne()
+ .addModule(b -> CoreModule.extend(b)
+ .commitLogAnnotationEntitiesOnly()
+ .excludeCommitLogFromTransaction()
+ .addCommitLogListener(listener));
+ }
+
+ @BeforeEach
+ public void before() {
+ this.context = runtime.newContext();
+ }
+
+ @Test
+ public void commitLog() throws SQLException {
+ Auditable2 a1 = context.newObject(Auditable2.class);
+ a1.setCharProperty1("yy");
+ a1.setCharProperty2("zz");
+
+ Auditable2 a2 = context.newObject(Auditable2.class);
+ a2.setCharProperty1("yy");
+ a2.setCharProperty2("zz");
+ context.commitChanges();
+
+ List<Object[]> logs = auditLog.selectAll();
+ assertEquals(2, logs.size());
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_PreUpdate_IT.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_PreUpdate_IT.java
new file mode 100644
index 000000000..907f6e24d
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_PreUpdate_IT.java
@@ -0,0 +1,101 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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.cayenne.commitlog;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.annotation.PreUpdate;
+import org.apache.cayenne.commitlog.db.AuditLog;
+import org.apache.cayenne.commitlog.db.AuditableChild5;
+import org.apache.cayenne.commitlog.model.ObjectChange;
+import org.apache.cayenne.commitlog.unit.AuditableRuntimeCase;
+import org.apache.cayenne.configuration.runtime.CoreModule;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+import org.apache.cayenne.tx.BaseTransaction;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class CommitLogFilter_PreUpdate_IT extends AuditableRuntimeCase {
+
+ protected ObjectContext context;
+ protected CommitLogListener listener;
+
+ @Override
+ protected CayenneRuntimeBuilder configureCayenne() {
+ this.listener = (originatingContext, changes) -> {
+ // assert we are not inside a transaction
+ assertNotNull(BaseTransaction.getThreadTransaction());
+
+ for (ObjectChange c : changes.getUniqueChanges()) {
+ AuditLog log = runtime.newContext().newObject(AuditLog.class);
+ log.setLog("DONE: " + c.getPostCommitId());
+ log.getObjectContext().commitChanges();
+ }
+ };
+
+ return super.configureCayenne()
+ .addModule(binder ->
+ CoreModule.extend(binder)
+ .addListenerType(MyPreUpdateListener.class)
+ )
+ .addModule(binder ->
+ CoreModule.extend(binder)
+ .commitLogAnnotationEntitiesOnly()
+ .addCommitLogListener(listener)
+ );
+ }
+
+ @BeforeEach
+ public void before() throws Exception {
+ context = runtime.newContext();
+ auditable5.insert(1, "yy");
+ auditableChild5.insert(1, 1, "zz");
+ }
+
+ @Test
+ public void commitLog() throws SQLException {
+
+ AuditableChild5 auditableChild = ObjectSelect.query(AuditableChild5.class)
+ .selectOne(context);
+ assertEquals("zz", auditableChild.getCharProperty1());
+
+ auditableChild.setCharProperty1("xx");
+
+ context.commitChanges();
+
+ List<Object[]> logs = auditLog.selectAll();
+ assertEquals(2, logs.size());
+ }
+
+ public static class MyPreUpdateListener {
+
+ @PreUpdate({ AuditableChild5.class })
+ public void preUpdate( AuditableChild5 child ) {
+ child.getParent().setCharProperty1("zz");
+ }
+ }
+
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_PreUpdate_OutsideTxIT.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_PreUpdate_OutsideTxIT.java
new file mode 100644
index 000000000..326f628c2
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_PreUpdate_OutsideTxIT.java
@@ -0,0 +1,101 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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.cayenne.commitlog;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.annotation.PreUpdate;
+import org.apache.cayenne.commitlog.db.AuditLog;
+import org.apache.cayenne.commitlog.db.AuditableChild5;
+import org.apache.cayenne.commitlog.model.ObjectChange;
+import org.apache.cayenne.commitlog.unit.AuditableRuntimeCase;
+import org.apache.cayenne.configuration.runtime.CoreModule;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+import org.apache.cayenne.tx.BaseTransaction;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CommitLogFilter_PreUpdate_OutsideTxIT extends AuditableRuntimeCase {
+
+ protected ObjectContext context;
+ protected CommitLogListener listener;
+
+ @Override
+ protected CayenneRuntimeBuilder configureCayenne() {
+ this.listener = (originatingContext, changes) -> {
+ // assert we are not inside a transaction
+ assertNull(BaseTransaction.getThreadTransaction());
+
+ for (ObjectChange c : changes.getUniqueChanges()) {
+ AuditLog log = runtime.newContext().newObject(AuditLog.class);
+ log.setLog("DONE: " + c.getPostCommitId());
+ log.getObjectContext().commitChanges();
+ }
+ };
+
+ return super.configureCayenne()
+ .addModule(binder ->
+ CoreModule.extend(binder)
+ .addListenerType(MyPreUpdateListener.class)
+ )
+ .addModule(
+ b -> CoreModule.extend(b)
+ .commitLogAnnotationEntitiesOnly()
+ .excludeCommitLogFromTransaction()
+ .addCommitLogListener(listener)
+ );
+ }
+
+ @BeforeEach
+ public void before() throws Exception {
+ context = runtime.newContext();
+ auditable5.insert(1, "yy");
+ auditableChild5.insert(1, 1, "zz");
+ }
+
+ @Test
+ public void commitLog() throws SQLException {
+
+ AuditableChild5 auditableChild = ObjectSelect.query(AuditableChild5.class)
+ .selectOne(context);
+ assertEquals("zz", auditableChild.getCharProperty1());
+
+ auditableChild.setCharProperty1("xx");
+
+ context.commitChanges();
+
+ List<Object[]> logs = auditLog.selectAll();
+ assertEquals(2, logs.size());
+ }
+
+ public static class MyPreUpdateListener {
+
+ @PreUpdate({ AuditableChild5.class })
+ public void preUpdate( AuditableChild5 child ) {
+ child.getParent().setCharProperty1("zz");
+ }
+ }
+
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_TxIT.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_TxIT.java
new file mode 100644
index 000000000..29796f472
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/CommitLogFilter_TxIT.java
@@ -0,0 +1,80 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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.cayenne.commitlog;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.commitlog.db.AuditLog;
+import org.apache.cayenne.commitlog.db.Auditable2;
+import org.apache.cayenne.commitlog.model.ObjectChange;
+import org.apache.cayenne.commitlog.unit.AuditableRuntimeCase;
+import org.apache.cayenne.configuration.runtime.CoreModule;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+import org.apache.cayenne.tx.BaseTransaction;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class CommitLogFilter_TxIT extends AuditableRuntimeCase {
+
+ protected ObjectContext context;
+ protected CommitLogListener listener;
+
+ @Override
+ protected CayenneRuntimeBuilder configureCayenne() {
+ this.listener = (originatingContext, changes) -> {
+
+ // assert we are inside transaction
+ assertNotNull(BaseTransaction.getThreadTransaction());
+
+ for (ObjectChange c : changes.getUniqueChanges()) {
+ AuditLog log = runtime.newContext().newObject(AuditLog.class);
+ log.setLog("DONE: " + c.getPostCommitId());
+ log.getObjectContext().commitChanges();
+ }
+ };
+ return super.configureCayenne().
+ addModule(b -> CoreModule.extend(b).commitLogAnnotationEntitiesOnly().addCommitLogListener(listener));
+ }
+
+ @BeforeEach
+ public void before() {
+ this.context = runtime.newContext();
+ }
+
+ @Test
+ public void commitLog() throws SQLException {
+ Auditable2 a1 = context.newObject(Auditable2.class);
+ a1.setCharProperty1("yy");
+ a1.setCharProperty2("zz");
+
+ Auditable2 a2 = context.newObject(Auditable2.class);
+ a2.setCharProperty1("yy");
+ a2.setCharProperty2("zz");
+ context.commitChanges();
+
+ List<Object[]> logs = auditLog.selectAll();
+ assertEquals(2, logs.size());
+ }
+
+}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntityFactory.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditLog.java
similarity index 83%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntityFactory.java
copy to cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditLog.java
index fe3f0d843..477da260f 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntityFactory.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditLog.java
@@ -16,14 +16,12 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.meta;
+package org.apache.cayenne.commitlog.db;
-import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.commitlog.db.auto._AuditLog;
-/**
- * @since 4.0
- */
-public interface CommitLogEntityFactory {
+public class AuditLog extends _AuditLog {
+
+ private static final long serialVersionUID = 1L;
- CommitLogEntity getEntity(ObjectId id);
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable1.java
similarity index 77%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java
copy to cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable1.java
index d83383236..62637bda5 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable1.java
@@ -16,19 +16,14 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.meta;
+package org.apache.cayenne.commitlog.db;
-/**
- * Describes post-commit behavior for a given Cayenne entity.
- *
- * @since 4.0
- */
-public interface CommitLogEntity {
+import org.apache.cayenne.annotation.CommitLog;
+import org.apache.cayenne.commitlog.db.auto._Auditable1;
- boolean isIncluded();
+@CommitLog
+public class Auditable1 extends _Auditable1 {
- boolean isConfidential(String property);
-
- boolean isIncluded(String property);
+ private static final long serialVersionUID = 8458581370578140962L;
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable2.java
similarity index 73%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java
copy to cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable2.java
index 8928ad57a..072e822af 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable2.java
@@ -16,16 +16,14 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.model;
+package org.apache.cayenne.commitlog.db;
-/**
- * @since 4.0
- */
-public interface PropertyChangeVisitor<T> {
+import org.apache.cayenne.annotation.CommitLog;
+import org.apache.cayenne.commitlog.db.auto._Auditable2;
- T visitAttribute(AttributeChange change);
+@CommitLog(ignoredProperties = "charProperty1", confidential = "charProperty2")
+public class Auditable2 extends _Auditable2 {
- T visitToOneRelationship(ToOneRelationshipChange change);
+ private static final long serialVersionUID = 5203324250911707978L;
- T visitToManyRelationship(ToManyRelationshipChange change);
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable3.java
similarity index 75%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java
copy to cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable3.java
index 8928ad57a..eb2c691eb 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable3.java
@@ -16,16 +16,14 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.model;
+package org.apache.cayenne.commitlog.db;
-/**
- * @since 4.0
- */
-public interface PropertyChangeVisitor<T> {
+import org.apache.cayenne.annotation.CommitLog;
+import org.apache.cayenne.commitlog.db.auto._Auditable3;
- T visitAttribute(AttributeChange change);
+@CommitLog(ignoreAttributes = true, ignoreToManyRelationships = true)
+public class Auditable3 extends _Auditable3 {
- T visitToOneRelationship(ToOneRelationshipChange change);
+ private static final long serialVersionUID = 1L;
- T visitToManyRelationship(ToManyRelationshipChange change);
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable4.java
similarity index 76%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java
rename to cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable4.java
index 8928ad57a..85abeaa72 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/PropertyChangeVisitor.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable4.java
@@ -16,16 +16,14 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.model;
+package org.apache.cayenne.commitlog.db;
-/**
- * @since 4.0
- */
-public interface PropertyChangeVisitor<T> {
+import org.apache.cayenne.annotation.CommitLog;
+import org.apache.cayenne.commitlog.db.auto._Auditable4;
- T visitAttribute(AttributeChange change);
+@CommitLog(ignoreToOneRelationships = true)
+public class Auditable4 extends _Auditable4 {
- T visitToOneRelationship(ToOneRelationshipChange change);
+ private static final long serialVersionUID = 1L;
- T visitToManyRelationship(ToManyRelationshipChange change);
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ToOneRelationshipChange.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable5.java
similarity index 78%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ToOneRelationshipChange.java
copy to cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable5.java
index b41ac8654..cd6967535 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ToOneRelationshipChange.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/Auditable5.java
@@ -16,16 +16,15 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.model;
-import org.apache.cayenne.ObjectId;
+package org.apache.cayenne.commitlog.db;
-/**
- * @since 4.0
- */
-public interface ToOneRelationshipChange extends PropertyChange {
+import org.apache.cayenne.annotation.CommitLog;
+import org.apache.cayenne.commitlog.db.auto._Auditable5;
- ObjectId getOldValue();
+@CommitLog
+public class Auditable5 extends _Auditable5 {
+
+ private static final long serialVersionUID = 1L;
- ObjectId getNewValue();
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ToOneRelationshipChange.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild1.java
similarity index 80%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ToOneRelationshipChange.java
rename to cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild1.java
index b41ac8654..dcc2e4894 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ToOneRelationshipChange.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild1.java
@@ -16,16 +16,12 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.model;
+package org.apache.cayenne.commitlog.db;
-import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.commitlog.db.auto._AuditableChild1;
-/**
- * @since 4.0
- */
-public interface ToOneRelationshipChange extends PropertyChange {
+public class AuditableChild1 extends _AuditableChild1 {
- ObjectId getOldValue();
+ private static final long serialVersionUID = 7967782239405764614L;
- ObjectId getNewValue();
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntityFactory.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild1x.java
similarity index 81%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntityFactory.java
rename to cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild1x.java
index fe3f0d843..baeae14ec 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntityFactory.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild1x.java
@@ -16,14 +16,12 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.meta;
+package org.apache.cayenne.commitlog.db;
-import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.commitlog.db.auto._AuditableChild1x;
-/**
- * @since 4.0
- */
-public interface CommitLogEntityFactory {
+public class AuditableChild1x extends _AuditableChild1x {
+
+ private static final long serialVersionUID = 1L;
- CommitLogEntity getEntity(ObjectId id);
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild3.java
similarity index 82%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
copy to cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild3.java
index 0c01ba275..971f302b0 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild3.java
@@ -16,14 +16,10 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.model;
+package org.apache.cayenne.commitlog.db;
-/**
- * Defines types of tracked object changes.
- *
- * @since 4.0
- */
-public enum ObjectChangeType {
+import org.apache.cayenne.commitlog.db.auto._AuditableChild3;
- INSERT, UPDATE, DELETE;
+public class AuditableChild3 extends _AuditableChild3 {
+ private static final long serialVersionUID = 1L;
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild5.java
similarity index 77%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java
copy to cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild5.java
index d83383236..b8995d259 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild5.java
@@ -16,19 +16,15 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.meta;
-/**
- * Describes post-commit behavior for a given Cayenne entity.
- *
- * @since 4.0
- */
-public interface CommitLogEntity {
+package org.apache.cayenne.commitlog.db;
- boolean isIncluded();
+import org.apache.cayenne.annotation.CommitLog;
+import org.apache.cayenne.commitlog.db.auto._AuditableChild5;
- boolean isConfidential(String property);
+@CommitLog
+public class AuditableChild5 extends _AuditableChild5 {
- boolean isIncluded(String property);
+ private static final long serialVersionUID = 1L;
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/E1.java
similarity index 84%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
copy to cayenne/src/test/java/org/apache/cayenne/commitlog/db/E1.java
index 0c01ba275..56b53fa9e 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/E1.java
@@ -16,14 +16,10 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.model;
+package org.apache.cayenne.commitlog.db;
-/**
- * Defines types of tracked object changes.
- *
- * @since 4.0
- */
-public enum ObjectChangeType {
+import org.apache.cayenne.commitlog.db.auto._E1;
- INSERT, UPDATE, DELETE;
+public class E1 extends _E1 {
+ private static final long serialVersionUID = 1L;
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/E2.java
similarity index 84%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
copy to cayenne/src/test/java/org/apache/cayenne/commitlog/db/E2.java
index 0c01ba275..8da1dc26d 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/E2.java
@@ -16,14 +16,10 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.model;
+package org.apache.cayenne.commitlog.db;
-/**
- * Defines types of tracked object changes.
- *
- * @since 4.0
- */
-public enum ObjectChangeType {
+import org.apache.cayenne.commitlog.db.auto._E2;
- INSERT, UPDATE, DELETE;
+public class E2 extends _E2 {
+ private static final long serialVersionUID = 1L;
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/E3.java
similarity index 84%
copy from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
copy to cayenne/src/test/java/org/apache/cayenne/commitlog/db/E3.java
index 0c01ba275..4f37e825f 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/E3.java
@@ -16,14 +16,12 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.model;
+package org.apache.cayenne.commitlog.db;
-/**
- * Defines types of tracked object changes.
- *
- * @since 4.0
- */
-public enum ObjectChangeType {
+import org.apache.cayenne.commitlog.db.auto._E3;
+
+public class E3 extends _E3 {
+
+ private static final long serialVersionUID = 1L;
- INSERT, UPDATE, DELETE;
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/E4.java
similarity index 84%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
rename to cayenne/src/test/java/org/apache/cayenne/commitlog/db/E4.java
index 0c01ba275..61e5a8f81 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/model/ObjectChangeType.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/E4.java
@@ -16,14 +16,12 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.model;
+package org.apache.cayenne.commitlog.db;
-/**
- * Defines types of tracked object changes.
- *
- * @since 4.0
- */
-public enum ObjectChangeType {
+import org.apache.cayenne.commitlog.db.auto._E4;
+
+public class E4 extends _E4 {
+
+ private static final long serialVersionUID = 1L;
- INSERT, UPDATE, DELETE;
}
diff --git a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/LifecycleMap.java
similarity index 71%
rename from cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java
rename to cayenne/src/test/java/org/apache/cayenne/commitlog/db/LifecycleMap.java
index d83383236..dc1f263a5 100644
--- a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/meta/CommitLogEntity.java
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/LifecycleMap.java
@@ -16,19 +16,21 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.commitlog.meta;
+package org.apache.cayenne.commitlog.db;
-/**
- * Describes post-commit behavior for a given Cayenne entity.
- *
- * @since 4.0
- */
-public interface CommitLogEntity {
+import org.apache.cayenne.commitlog.db.auto._LifecycleMap;
- boolean isIncluded();
+public class LifecycleMap extends _LifecycleMap {
- boolean isConfidential(String property);
+ private static LifecycleMap instance;
- boolean isIncluded(String property);
+ private LifecycleMap() {}
+ public static LifecycleMap getInstance() {
+ if(instance == null) {
+ instance = new LifecycleMap();
+ }
+
+ return instance;
+ }
}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditLog.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditLog.java
new file mode 100644
index 000000000..9893d7157
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditLog.java
@@ -0,0 +1,91 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.AuditLog;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+
+/**
+ * Class _AuditLog was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _AuditLog extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<AuditLog> SELF = PropertyFactory.createSelf(AuditLog.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final StringProperty<String> LOG = PropertyFactory.createString("log", String.class);
+
+ protected String log;
+
+
+ public void setLog(String log) {
+ beforePropertyWrite("log", this.log, log);
+ this.log = log;
+ }
+
+ public String getLog() {
+ beforePropertyRead("log");
+ return this.log;
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "log":
+ return this.log;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "log":
+ this.log = (String)val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.log);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.log = (String)in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable1.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable1.java
new file mode 100644
index 000000000..714cdb14e
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable1.java
@@ -0,0 +1,116 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.Auditable1;
+import org.apache.cayenne.commitlog.db.AuditableChild1;
+import org.apache.cayenne.exp.property.ListProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+
+/**
+ * Class _Auditable1 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Auditable1 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<Auditable1> SELF = PropertyFactory.createSelf(Auditable1.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final StringProperty<String> CHAR_PROPERTY1 = PropertyFactory.createString("charProperty1", String.class);
+ public static final ListProperty<AuditableChild1> CHILDREN1 = PropertyFactory.createList("children1", AuditableChild1.class);
+
+ protected String charProperty1;
+
+ protected Object children1;
+
+ public void setCharProperty1(String charProperty1) {
+ beforePropertyWrite("charProperty1", this.charProperty1, charProperty1);
+ this.charProperty1 = charProperty1;
+ }
+
+ public String getCharProperty1() {
+ beforePropertyRead("charProperty1");
+ return this.charProperty1;
+ }
+
+ public void addToChildren1(AuditableChild1 obj) {
+ addToManyTarget("children1", obj, true);
+ }
+
+ public void removeFromChildren1(AuditableChild1 obj) {
+ removeToManyTarget("children1", obj, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<AuditableChild1> getChildren1() {
+ return (List<AuditableChild1>)readProperty("children1");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "charProperty1":
+ return this.charProperty1;
+ case "children1":
+ return this.children1;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "charProperty1":
+ this.charProperty1 = (String)val;
+ break;
+ case "children1":
+ this.children1 = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.charProperty1);
+ out.writeObject(this.children1);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.charProperty1 = (String)in.readObject();
+ this.children1 = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable2.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable2.java
new file mode 100644
index 000000000..9e5a20ed5
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable2.java
@@ -0,0 +1,135 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.Auditable2;
+import org.apache.cayenne.commitlog.db.AuditableChild3;
+import org.apache.cayenne.exp.property.ListProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+
+/**
+ * Class _Auditable2 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Auditable2 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<Auditable2> SELF = PropertyFactory.createSelf(Auditable2.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final StringProperty<String> CHAR_PROPERTY1 = PropertyFactory.createString("charProperty1", String.class);
+ public static final StringProperty<String> CHAR_PROPERTY2 = PropertyFactory.createString("charProperty2", String.class);
+ public static final ListProperty<AuditableChild3> CHILDREN = PropertyFactory.createList("children", AuditableChild3.class);
+
+ protected String charProperty1;
+ protected String charProperty2;
+
+ protected Object children;
+
+ public void setCharProperty1(String charProperty1) {
+ beforePropertyWrite("charProperty1", this.charProperty1, charProperty1);
+ this.charProperty1 = charProperty1;
+ }
+
+ public String getCharProperty1() {
+ beforePropertyRead("charProperty1");
+ return this.charProperty1;
+ }
+
+ public void setCharProperty2(String charProperty2) {
+ beforePropertyWrite("charProperty2", this.charProperty2, charProperty2);
+ this.charProperty2 = charProperty2;
+ }
+
+ public String getCharProperty2() {
+ beforePropertyRead("charProperty2");
+ return this.charProperty2;
+ }
+
+ public void addToChildren(AuditableChild3 obj) {
+ addToManyTarget("children", obj, true);
+ }
+
+ public void removeFromChildren(AuditableChild3 obj) {
+ removeToManyTarget("children", obj, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<AuditableChild3> getChildren() {
+ return (List<AuditableChild3>)readProperty("children");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "charProperty1":
+ return this.charProperty1;
+ case "charProperty2":
+ return this.charProperty2;
+ case "children":
+ return this.children;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "charProperty1":
+ this.charProperty1 = (String)val;
+ break;
+ case "charProperty2":
+ this.charProperty2 = (String)val;
+ break;
+ case "children":
+ this.children = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.charProperty1);
+ out.writeObject(this.charProperty2);
+ out.writeObject(this.children);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.charProperty1 = (String)in.readObject();
+ this.charProperty2 = (String)in.readObject();
+ this.children = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable3.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable3.java
new file mode 100644
index 000000000..edbede352
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable3.java
@@ -0,0 +1,135 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.Auditable3;
+import org.apache.cayenne.commitlog.db.Auditable4;
+import org.apache.cayenne.exp.property.ListProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+
+/**
+ * Class _Auditable3 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Auditable3 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<Auditable3> SELF = PropertyFactory.createSelf(Auditable3.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final StringProperty<String> CHAR_PROPERTY1 = PropertyFactory.createString("charProperty1", String.class);
+ public static final StringProperty<String> CHAR_PROPERTY2 = PropertyFactory.createString("charProperty2", String.class);
+ public static final ListProperty<Auditable4> AUDITABLE4S = PropertyFactory.createList("auditable4s", Auditable4.class);
+
+ protected String charProperty1;
+ protected String charProperty2;
+
+ protected Object auditable4s;
+
+ public void setCharProperty1(String charProperty1) {
+ beforePropertyWrite("charProperty1", this.charProperty1, charProperty1);
+ this.charProperty1 = charProperty1;
+ }
+
+ public String getCharProperty1() {
+ beforePropertyRead("charProperty1");
+ return this.charProperty1;
+ }
+
+ public void setCharProperty2(String charProperty2) {
+ beforePropertyWrite("charProperty2", this.charProperty2, charProperty2);
+ this.charProperty2 = charProperty2;
+ }
+
+ public String getCharProperty2() {
+ beforePropertyRead("charProperty2");
+ return this.charProperty2;
+ }
+
+ public void addToAuditable4s(Auditable4 obj) {
+ addToManyTarget("auditable4s", obj, true);
+ }
+
+ public void removeFromAuditable4s(Auditable4 obj) {
+ removeToManyTarget("auditable4s", obj, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<Auditable4> getAuditable4s() {
+ return (List<Auditable4>)readProperty("auditable4s");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "charProperty1":
+ return this.charProperty1;
+ case "charProperty2":
+ return this.charProperty2;
+ case "auditable4s":
+ return this.auditable4s;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "charProperty1":
+ this.charProperty1 = (String)val;
+ break;
+ case "charProperty2":
+ this.charProperty2 = (String)val;
+ break;
+ case "auditable4s":
+ this.auditable4s = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.charProperty1);
+ out.writeObject(this.charProperty2);
+ out.writeObject(this.auditable4s);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.charProperty1 = (String)in.readObject();
+ this.charProperty2 = (String)in.readObject();
+ this.auditable4s = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable4.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable4.java
new file mode 100644
index 000000000..83cf03db1
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable4.java
@@ -0,0 +1,129 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.Auditable3;
+import org.apache.cayenne.commitlog.db.Auditable4;
+import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+
+/**
+ * Class _Auditable4 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Auditable4 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<Auditable4> SELF = PropertyFactory.createSelf(Auditable4.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final StringProperty<String> CHAR_PROPERTY1 = PropertyFactory.createString("charProperty1", String.class);
+ public static final StringProperty<String> CHAR_PROPERTY2 = PropertyFactory.createString("charProperty2", String.class);
+ public static final EntityProperty<Auditable3> AUDITABLE3 = PropertyFactory.createEntity("auditable3", Auditable3.class);
+
+ protected String charProperty1;
+ protected String charProperty2;
+
+ protected Object auditable3;
+
+ public void setCharProperty1(String charProperty1) {
+ beforePropertyWrite("charProperty1", this.charProperty1, charProperty1);
+ this.charProperty1 = charProperty1;
+ }
+
+ public String getCharProperty1() {
+ beforePropertyRead("charProperty1");
+ return this.charProperty1;
+ }
+
+ public void setCharProperty2(String charProperty2) {
+ beforePropertyWrite("charProperty2", this.charProperty2, charProperty2);
+ this.charProperty2 = charProperty2;
+ }
+
+ public String getCharProperty2() {
+ beforePropertyRead("charProperty2");
+ return this.charProperty2;
+ }
+
+ public void setAuditable3(Auditable3 auditable3) {
+ setToOneTarget("auditable3", auditable3, true);
+ }
+
+ public Auditable3 getAuditable3() {
+ return (Auditable3)readProperty("auditable3");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "charProperty1":
+ return this.charProperty1;
+ case "charProperty2":
+ return this.charProperty2;
+ case "auditable3":
+ return this.auditable3;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "charProperty1":
+ this.charProperty1 = (String)val;
+ break;
+ case "charProperty2":
+ this.charProperty2 = (String)val;
+ break;
+ case "auditable3":
+ this.auditable3 = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.charProperty1);
+ out.writeObject(this.charProperty2);
+ out.writeObject(this.auditable3);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.charProperty1 = (String)in.readObject();
+ this.charProperty2 = (String)in.readObject();
+ this.auditable3 = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable5.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable5.java
new file mode 100644
index 000000000..242db2a21
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_Auditable5.java
@@ -0,0 +1,116 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.Auditable5;
+import org.apache.cayenne.commitlog.db.AuditableChild5;
+import org.apache.cayenne.exp.property.ListProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+
+/**
+ * Class _Auditable5 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Auditable5 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<Auditable5> SELF = PropertyFactory.createSelf(Auditable5.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final StringProperty<String> CHAR_PROPERTY1 = PropertyFactory.createString("charProperty1", String.class);
+ public static final ListProperty<AuditableChild5> CHILDREN = PropertyFactory.createList("children", AuditableChild5.class);
+
+ protected String charProperty1;
+
+ protected Object children;
+
+ public void setCharProperty1(String charProperty1) {
+ beforePropertyWrite("charProperty1", this.charProperty1, charProperty1);
+ this.charProperty1 = charProperty1;
+ }
+
+ public String getCharProperty1() {
+ beforePropertyRead("charProperty1");
+ return this.charProperty1;
+ }
+
+ public void addToChildren(AuditableChild5 obj) {
+ addToManyTarget("children", obj, true);
+ }
+
+ public void removeFromChildren(AuditableChild5 obj) {
+ removeToManyTarget("children", obj, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<AuditableChild5> getChildren() {
+ return (List<AuditableChild5>)readProperty("children");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "charProperty1":
+ return this.charProperty1;
+ case "children":
+ return this.children;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "charProperty1":
+ this.charProperty1 = (String)val;
+ break;
+ case "children":
+ this.children = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.charProperty1);
+ out.writeObject(this.children);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.charProperty1 = (String)in.readObject();
+ this.children = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild1.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild1.java
new file mode 100644
index 000000000..390cc8219
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild1.java
@@ -0,0 +1,110 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.Auditable1;
+import org.apache.cayenne.commitlog.db.AuditableChild1;
+import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+
+/**
+ * Class _AuditableChild1 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _AuditableChild1 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<AuditableChild1> SELF = PropertyFactory.createSelf(AuditableChild1.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final StringProperty<String> CHAR_PROPERTY1 = PropertyFactory.createString("charProperty1", String.class);
+ public static final EntityProperty<Auditable1> PARENT = PropertyFactory.createEntity("parent", Auditable1.class);
+
+ protected String charProperty1;
+
+ protected Object parent;
+
+ public void setCharProperty1(String charProperty1) {
+ beforePropertyWrite("charProperty1", this.charProperty1, charProperty1);
+ this.charProperty1 = charProperty1;
+ }
+
+ public String getCharProperty1() {
+ beforePropertyRead("charProperty1");
+ return this.charProperty1;
+ }
+
+ public void setParent(Auditable1 parent) {
+ setToOneTarget("parent", parent, true);
+ }
+
+ public Auditable1 getParent() {
+ return (Auditable1)readProperty("parent");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "charProperty1":
+ return this.charProperty1;
+ case "parent":
+ return this.parent;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "charProperty1":
+ this.charProperty1 = (String)val;
+ break;
+ case "parent":
+ this.parent = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.charProperty1);
+ out.writeObject(this.parent);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.charProperty1 = (String)in.readObject();
+ this.parent = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild1x.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild1x.java
new file mode 100644
index 000000000..b22b5dbc4
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild1x.java
@@ -0,0 +1,110 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.Auditable1;
+import org.apache.cayenne.commitlog.db.AuditableChild1x;
+import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+
+/**
+ * Class _AuditableChild1x was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _AuditableChild1x extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<AuditableChild1x> SELF = PropertyFactory.createSelf(AuditableChild1x.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final StringProperty<String> CHAR_PROPERTY1 = PropertyFactory.createString("charProperty1", String.class);
+ public static final EntityProperty<Auditable1> PARENT = PropertyFactory.createEntity("parent", Auditable1.class);
+
+ protected String charProperty1;
+
+ protected Object parent;
+
+ public void setCharProperty1(String charProperty1) {
+ beforePropertyWrite("charProperty1", this.charProperty1, charProperty1);
+ this.charProperty1 = charProperty1;
+ }
+
+ public String getCharProperty1() {
+ beforePropertyRead("charProperty1");
+ return this.charProperty1;
+ }
+
+ public void setParent(Auditable1 parent) {
+ setToOneTarget("parent", parent, true);
+ }
+
+ public Auditable1 getParent() {
+ return (Auditable1)readProperty("parent");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "charProperty1":
+ return this.charProperty1;
+ case "parent":
+ return this.parent;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "charProperty1":
+ this.charProperty1 = (String)val;
+ break;
+ case "parent":
+ this.parent = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.charProperty1);
+ out.writeObject(this.parent);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.charProperty1 = (String)in.readObject();
+ this.parent = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild3.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild3.java
new file mode 100644
index 000000000..b3c41b04a
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild3.java
@@ -0,0 +1,129 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.Auditable2;
+import org.apache.cayenne.commitlog.db.AuditableChild3;
+import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+
+/**
+ * Class _AuditableChild3 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _AuditableChild3 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<AuditableChild3> SELF = PropertyFactory.createSelf(AuditableChild3.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final StringProperty<String> CHAR_PROPERTY1 = PropertyFactory.createString("charProperty1", String.class);
+ public static final StringProperty<String> CHAR_PROPERTY2 = PropertyFactory.createString("charProperty2", String.class);
+ public static final EntityProperty<Auditable2> PARENT = PropertyFactory.createEntity("parent", Auditable2.class);
+
+ protected String charProperty1;
+ protected String charProperty2;
+
+ protected Object parent;
+
+ public void setCharProperty1(String charProperty1) {
+ beforePropertyWrite("charProperty1", this.charProperty1, charProperty1);
+ this.charProperty1 = charProperty1;
+ }
+
+ public String getCharProperty1() {
+ beforePropertyRead("charProperty1");
+ return this.charProperty1;
+ }
+
+ public void setCharProperty2(String charProperty2) {
+ beforePropertyWrite("charProperty2", this.charProperty2, charProperty2);
+ this.charProperty2 = charProperty2;
+ }
+
+ public String getCharProperty2() {
+ beforePropertyRead("charProperty2");
+ return this.charProperty2;
+ }
+
+ public void setParent(Auditable2 parent) {
+ setToOneTarget("parent", parent, true);
+ }
+
+ public Auditable2 getParent() {
+ return (Auditable2)readProperty("parent");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "charProperty1":
+ return this.charProperty1;
+ case "charProperty2":
+ return this.charProperty2;
+ case "parent":
+ return this.parent;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "charProperty1":
+ this.charProperty1 = (String)val;
+ break;
+ case "charProperty2":
+ this.charProperty2 = (String)val;
+ break;
+ case "parent":
+ this.parent = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.charProperty1);
+ out.writeObject(this.charProperty2);
+ out.writeObject(this.parent);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.charProperty1 = (String)in.readObject();
+ this.charProperty2 = (String)in.readObject();
+ this.parent = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild5.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild5.java
new file mode 100644
index 000000000..26e66b211
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild5.java
@@ -0,0 +1,110 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.Auditable5;
+import org.apache.cayenne.commitlog.db.AuditableChild5;
+import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+import org.apache.cayenne.exp.property.StringProperty;
+
+/**
+ * Class _AuditableChild5 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _AuditableChild5 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<AuditableChild5> SELF = PropertyFactory.createSelf(AuditableChild5.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final StringProperty<String> CHAR_PROPERTY1 = PropertyFactory.createString("charProperty1", String.class);
+ public static final EntityProperty<Auditable5> PARENT = PropertyFactory.createEntity("parent", Auditable5.class);
+
+ protected String charProperty1;
+
+ protected Object parent;
+
+ public void setCharProperty1(String charProperty1) {
+ beforePropertyWrite("charProperty1", this.charProperty1, charProperty1);
+ this.charProperty1 = charProperty1;
+ }
+
+ public String getCharProperty1() {
+ beforePropertyRead("charProperty1");
+ return this.charProperty1;
+ }
+
+ public void setParent(Auditable5 parent) {
+ setToOneTarget("parent", parent, true);
+ }
+
+ public Auditable5 getParent() {
+ return (Auditable5)readProperty("parent");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "charProperty1":
+ return this.charProperty1;
+ case "parent":
+ return this.parent;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "charProperty1":
+ this.charProperty1 = (String)val;
+ break;
+ case "parent":
+ this.parent = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.charProperty1);
+ out.writeObject(this.parent);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.charProperty1 = (String)in.readObject();
+ this.parent = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E1.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E1.java
new file mode 100644
index 000000000..216bf3aad
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E1.java
@@ -0,0 +1,71 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.E1;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+
+/**
+ * Class _E1 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _E1 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<E1> SELF = PropertyFactory.createSelf(E1.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+
+
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E2.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E2.java
new file mode 100644
index 000000000..c8a9ee396
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E2.java
@@ -0,0 +1,71 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.E2;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+
+/**
+ * Class _E2 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _E2 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<E2> SELF = PropertyFactory.createSelf(E2.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+
+
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E3.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E3.java
new file mode 100644
index 000000000..3c25124f2
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E3.java
@@ -0,0 +1,96 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.E3;
+import org.apache.cayenne.commitlog.db.E4;
+import org.apache.cayenne.exp.property.ListProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+
+/**
+ * Class _E3 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _E3 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<E3> SELF = PropertyFactory.createSelf(E3.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final ListProperty<E4> E4S = PropertyFactory.createList("e4s", E4.class);
+
+
+ protected Object e4s;
+
+ public void addToE4s(E4 obj) {
+ addToManyTarget("e4s", obj, true);
+ }
+
+ public void removeFromE4s(E4 obj) {
+ removeToManyTarget("e4s", obj, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<E4> getE4s() {
+ return (List<E4>)readProperty("e4s");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "e4s":
+ return this.e4s;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "e4s":
+ this.e4s = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.e4s);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.e4s = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E4.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E4.java
new file mode 100644
index 000000000..67c669387
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_E4.java
@@ -0,0 +1,96 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.apache.cayenne.PersistentObject;
+import org.apache.cayenne.commitlog.db.E3;
+import org.apache.cayenne.commitlog.db.E4;
+import org.apache.cayenne.exp.property.ListProperty;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.exp.property.SelfProperty;
+
+/**
+ * Class _E4 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _E4 extends PersistentObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final SelfProperty<E4> SELF = PropertyFactory.createSelf(E4.class);
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final ListProperty<E3> E3S = PropertyFactory.createList("e3s", E3.class);
+
+
+ protected Object e3s;
+
+ public void addToE3s(E3 obj) {
+ addToManyTarget("e3s", obj, true);
+ }
+
+ public void removeFromE3s(E3 obj) {
+ removeToManyTarget("e3s", obj, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<E3> getE3s() {
+ return (List<E3>)readProperty("e3s");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "e3s":
+ return this.e3s;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "e3s":
+ this.e3s = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.e3s);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ super.readState(in);
+ this.e3s = in.readObject();
+ }
+
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_LifecycleMap.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_LifecycleMap.java
new file mode 100644
index 000000000..13bed5f5c
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/db/auto/_LifecycleMap.java
@@ -0,0 +1,12 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+
+
+/**
+ * This class was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public class _LifecycleMap {
+}
\ No newline at end of file
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/unit/AuditableRuntimeCase.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/unit/AuditableRuntimeCase.java
new file mode 100644
index 000000000..ab006130f
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/unit/AuditableRuntimeCase.java
@@ -0,0 +1,101 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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.cayenne.commitlog.unit;
+
+import org.apache.cayenne.runtime.CayenneRuntime;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+import org.apache.cayenne.test.jdbc.DbHelper;
+import org.apache.cayenne.test.jdbc.TableHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+
+/**
+ * A superclass of integration tests for cayenne-lifecycle.
+ */
+public abstract class AuditableRuntimeCase {
+
+ protected CayenneRuntime runtime;
+
+ protected TableHelper auditable1;
+ protected TableHelper auditableChild1;
+ protected TableHelper auditableChild1x;
+
+ protected TableHelper auditable2;
+ protected TableHelper auditableChild3;
+
+ protected TableHelper auditable3;
+ protected TableHelper auditable4;
+
+ protected TableHelper auditable5;
+ protected TableHelper auditableChild5;
+
+ protected TableHelper auditLog;
+
+ @BeforeEach
+ public void startCayenne() throws Exception {
+ this.runtime = configureCayenne().build();
+
+ DbHelper dbHelper = new DbHelper(runtime.getDataSource());
+
+ this.auditLog = new TableHelper(dbHelper, "AUDIT_LOG").setColumns("ID", "LOG");
+
+ this.auditable1 = new TableHelper(dbHelper, "AUDITABLE1").setColumns("ID", "CHAR_PROPERTY1");
+
+ this.auditableChild1 = new TableHelper(dbHelper, "AUDITABLE_CHILD1").setColumns("ID", "AUDITABLE1_ID",
+ "CHAR_PROPERTY1");
+ this.auditableChild1x = new TableHelper(dbHelper, "AUDITABLE_CHILD1X").setColumns("ID", "AUDITABLE1_ID",
+ "CHAR_PROPERTY1");
+
+ this.auditable2 = new TableHelper(dbHelper, "AUDITABLE2").setColumns("ID", "CHAR_PROPERTY1", "CHAR_PROPERTY2");
+
+ this.auditableChild3 = new TableHelper(dbHelper, "AUDITABLE_CHILD3").setColumns("ID", "AUDITABLE2_ID",
+ "CHAR_PROPERTY1", "CHAR_PROPERTY2");
+
+ this.auditable3 = new TableHelper(dbHelper, "AUDITABLE3").setColumns("ID", "CHAR_PROPERTY1", "CHAR_PROPERTY2");
+ this.auditable4 = new TableHelper(dbHelper, "AUDITABLE4").setColumns("ID", "CHAR_PROPERTY1", "CHAR_PROPERTY2",
+ "AUDITABLE3_ID");
+
+ this.auditable5 = new TableHelper(dbHelper, "AUDITABLE5").setColumns("ID", "CHAR_PROPERTY1");
+ this.auditableChild5 = new TableHelper(dbHelper, "AUDITABLE_CHILD5").setColumns("ID", "AUDITABLE5_ID",
+ "CHAR_PROPERTY1");
+
+ this.auditableChild1.deleteAll();
+ this.auditableChild1x.deleteAll();
+ this.auditable1.deleteAll();
+ this.auditableChild3.deleteAll();
+ this.auditable2.deleteAll();
+ this.auditable4.deleteAll();
+ this.auditable3.deleteAll();
+ this.auditableChild5.deleteAll();
+ this.auditable5.deleteAll();
+
+ this.auditLog.deleteAll();
+ }
+
+ protected CayenneRuntimeBuilder configureCayenne() {
+ return CayenneRuntime.builder().addConfig("cayenne-lifecycle.xml");
+ }
+
+ @AfterEach
+ public void shutdownCayenne() {
+ if (runtime != null) {
+ runtime.shutdown();
+ }
+ }
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/commitlog/unit/FlattenedRuntimeCase.java b/cayenne/src/test/java/org/apache/cayenne/commitlog/unit/FlattenedRuntimeCase.java
new file mode 100644
index 000000000..4980ac85c
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/commitlog/unit/FlattenedRuntimeCase.java
@@ -0,0 +1,62 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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.cayenne.commitlog.unit;
+
+import org.apache.cayenne.runtime.CayenneRuntime;
+import org.apache.cayenne.runtime.CayenneRuntimeBuilder;
+import org.apache.cayenne.test.jdbc.DbHelper;
+import org.apache.cayenne.test.jdbc.TableHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+
+public class FlattenedRuntimeCase {
+
+ protected CayenneRuntime runtime;
+
+ protected TableHelper e3;
+ protected TableHelper e4;
+ protected TableHelper e34;
+
+ @BeforeEach
+ public void startCayenne() throws Exception {
+ this.runtime = configureCayenne().build();
+
+ DbHelper dbHelper = new DbHelper(runtime.getDataSource());
+
+ this.e3 = new TableHelper(dbHelper, "E3").setColumns("ID");
+ this.e4 = new TableHelper(dbHelper, "E4").setColumns("ID");
+ this.e34 = new TableHelper(dbHelper, "E34").setColumns("E3_ID", "E4_ID");
+
+ this.e34.deleteAll();
+ this.e3.deleteAll();
+
+ }
+
+ protected CayenneRuntimeBuilder configureCayenne() {
+ return CayenneRuntime.builder().addConfig("cayenne-lifecycle.xml");
+ }
+
+ @AfterEach
+ public void shutdownCayenne() {
+ if (runtime != null) {
+ runtime.shutdown();
+ }
+ }
+
+}
diff --git a/cayenne/src/test/resources/cayenne-lifecycle.xml b/cayenne/src/test/resources/cayenne-lifecycle.xml
new file mode 100644
index 000000000..cdc1381a6
--- /dev/null
+++ b/cayenne/src/test/resources/cayenne-lifecycle.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<domain xmlns="http://cayenne.apache.org/schema/12/domain"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://cayenne.apache.org/schema/12/domain https://cayenne.apache.org/schema/12/domain.xsd"
+ project-version="12">
+ <map name="lifecycle-map"/>
+ <node name="lifecycle-db"
+ factory="org.apache.cayenne.configuration.runtime.XMLPoolingDataSourceFactory"
+ schema-update-strategy="org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy">
+ <map-ref name="lifecycle-map"/>
+ <data-source>
+ <driver value="org.hsqldb.jdbcDriver"/>
+ <url value="jdbc:hsqldb:mem:lifecycle"/>
+ <connectionPool min="1" max="1"/>
+ <login userName="sa"/>
+ </data-source>
+ </node>
+</domain>
diff --git a/cayenne/src/test/resources/lifecycle-map.map.xml b/cayenne/src/test/resources/lifecycle-map.map.xml
new file mode 100644
index 000000000..a3130b785
--- /dev/null
+++ b/cayenne/src/test/resources/lifecycle-map.map.xml
@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="utf-8"?>
+<data-map xmlns="http://cayenne.apache.org/schema/12/modelMap"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://cayenne.apache.org/schema/12/modelMap https://cayenne.apache.org/schema/12/modelMap.xsd"
+ project-version="12">
+ <property name="defaultPackage" value="org.apache.cayenne.commitlog.db"/>
+ <db-entity name="AUDITABLE1">
+ <db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="AUDITABLE2">
+ <db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+ <db-attribute name="CHAR_PROPERTY2" type="VARCHAR" length="200"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="AUDITABLE3">
+ <db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+ <db-attribute name="CHAR_PROPERTY2" type="VARCHAR" length="200"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="AUDITABLE4">
+ <db-attribute name="AUDITABLE3_ID" type="INTEGER"/>
+ <db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+ <db-attribute name="CHAR_PROPERTY2" type="VARCHAR" length="200"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="AUDITABLE5">
+ <db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="AUDITABLE_CHILD1">
+ <db-attribute name="AUDITABLE1_ID" type="INTEGER"/>
+ <db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="AUDITABLE_CHILD1X">
+ <db-attribute name="AUDITABLE1_ID" type="INTEGER"/>
+ <db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="AUDITABLE_CHILD3">
+ <db-attribute name="AUDITABLE2_ID" type="INTEGER"/>
+ <db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+ <db-attribute name="CHAR_PROPERTY2" type="VARCHAR" length="200"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="AUDITABLE_CHILD5">
+ <db-attribute name="AUDITABLE5_ID" type="INTEGER"/>
+ <db-attribute name="CHAR_PROPERTY1" type="VARCHAR" length="200"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="AUDIT_LOG">
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+ <db-attribute name="LOG" type="CLOB"/>
+ </db-entity>
+ <db-entity name="E1">
+ <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="E2">
+ <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="E3">
+ <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="E34">
+ <db-attribute name="E3_ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/>
+ <db-attribute name="E4_ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <db-entity name="E4">
+ <db-attribute name="ID" type="BIGINT" isPrimaryKey="true" isMandatory="true"/>
+ </db-entity>
+ <obj-entity name="AuditLog" className="org.apache.cayenne.commitlog.db.AuditLog" dbEntityName="AUDIT_LOG">
+ <obj-attribute name="log" type="java.lang.String" db-attribute-path="LOG"/>
+ </obj-entity>
+ <obj-entity name="Auditable1" className="org.apache.cayenne.commitlog.db.Auditable1" dbEntityName="AUDITABLE1">
+ <obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+ </obj-entity>
+ <obj-entity name="Auditable2" className="org.apache.cayenne.commitlog.db.Auditable2" dbEntityName="AUDITABLE2">
+ <obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+ <obj-attribute name="charProperty2" type="java.lang.String" db-attribute-path="CHAR_PROPERTY2"/>
+ </obj-entity>
+ <obj-entity name="Auditable3" className="org.apache.cayenne.commitlog.db.Auditable3" dbEntityName="AUDITABLE3">
+ <obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+ <obj-attribute name="charProperty2" type="java.lang.String" db-attribute-path="CHAR_PROPERTY2"/>
+ </obj-entity>
+ <obj-entity name="Auditable4" className="org.apache.cayenne.commitlog.db.Auditable4" dbEntityName="AUDITABLE4">
+ <obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+ <obj-attribute name="charProperty2" type="java.lang.String" db-attribute-path="CHAR_PROPERTY2"/>
+ </obj-entity>
+ <obj-entity name="Auditable5" className="org.apache.cayenne.commitlog.db.Auditable5" dbEntityName="AUDITABLE5">
+ <obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+ </obj-entity>
+ <obj-entity name="AuditableChild1" className="org.apache.cayenne.commitlog.db.AuditableChild1" dbEntityName="AUDITABLE_CHILD1">
+ <obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+ </obj-entity>
+ <obj-entity name="AuditableChild1x" className="org.apache.cayenne.commitlog.db.AuditableChild1x" dbEntityName="AUDITABLE_CHILD1X">
+ <obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+ </obj-entity>
+ <obj-entity name="AuditableChild3" className="org.apache.cayenne.commitlog.db.AuditableChild3" dbEntityName="AUDITABLE_CHILD3">
+ <obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+ <obj-attribute name="charProperty2" type="java.lang.String" db-attribute-path="CHAR_PROPERTY2"/>
+ </obj-entity>
+ <obj-entity name="AuditableChild5" className="org.apache.cayenne.commitlog.db.AuditableChild5" dbEntityName="AUDITABLE_CHILD5">
+ <obj-attribute name="charProperty1" type="java.lang.String" db-attribute-path="CHAR_PROPERTY1"/>
+ </obj-entity>
+ <obj-entity name="E1" className="org.apache.cayenne.commitlog.db.E1" dbEntityName="E1"/>
+ <obj-entity name="E2" className="org.apache.cayenne.commitlog.db.E2" dbEntityName="E2"/>
+ <obj-entity name="E3" className="org.apache.cayenne.commitlog.db.E3" dbEntityName="E3"/>
+ <obj-entity name="E4" className="org.apache.cayenne.commitlog.db.E4" dbEntityName="E4"/>
+ <db-relationship name="children1" source="AUDITABLE1" target="AUDITABLE_CHILD1" toMany="true">
+ <db-attribute-pair source="ID" target="AUDITABLE1_ID"/>
+ </db-relationship>
+ <db-relationship name="children1x" source="AUDITABLE1" target="AUDITABLE_CHILD1X" toMany="true">
+ <db-attribute-pair source="ID" target="AUDITABLE1_ID"/>
+ </db-relationship>
+ <db-relationship name="children" source="AUDITABLE2" target="AUDITABLE_CHILD3" toMany="true">
+ <db-attribute-pair source="ID" target="AUDITABLE2_ID"/>
+ </db-relationship>
+ <db-relationship name="auditable4s" source="AUDITABLE3" target="AUDITABLE4" toMany="true">
+ <db-attribute-pair source="ID" target="AUDITABLE3_ID"/>
+ </db-relationship>
+ <db-relationship name="auditable3" source="AUDITABLE4" target="AUDITABLE3">
+ <db-attribute-pair source="AUDITABLE3_ID" target="ID"/>
+ </db-relationship>
+ <db-relationship name="children" source="AUDITABLE5" target="AUDITABLE_CHILD5" toMany="true">
+ <db-attribute-pair source="ID" target="AUDITABLE5_ID"/>
+ </db-relationship>
+ <db-relationship name="parent" source="AUDITABLE_CHILD1" target="AUDITABLE1">
+ <db-attribute-pair source="AUDITABLE1_ID" target="ID"/>
+ </db-relationship>
+ <db-relationship name="parent" source="AUDITABLE_CHILD1X" target="AUDITABLE1">
+ <db-attribute-pair source="AUDITABLE1_ID" target="ID"/>
+ </db-relationship>
+ <db-relationship name="parent" source="AUDITABLE_CHILD3" target="AUDITABLE2">
+ <db-attribute-pair source="AUDITABLE2_ID" target="ID"/>
+ </db-relationship>
+ <db-relationship name="parent" source="AUDITABLE_CHILD5" target="AUDITABLE5">
+ <db-attribute-pair source="AUDITABLE5_ID" target="ID"/>
+ </db-relationship>
+ <db-relationship name="e34s" source="E3" target="E34" toDependentPK="true" toMany="true">
+ <db-attribute-pair source="ID" target="E3_ID"/>
+ </db-relationship>
+ <db-relationship name="e3" source="E34" target="E3">
+ <db-attribute-pair source="E3_ID" target="ID"/>
+ </db-relationship>
+ <db-relationship name="e4" source="E34" target="E4">
+ <db-attribute-pair source="E4_ID" target="ID"/>
+ </db-relationship>
+ <db-relationship name="e34s" source="E4" target="E34" toDependentPK="true" toMany="true">
+ <db-attribute-pair source="ID" target="E4_ID"/>
+ </db-relationship>
+ <obj-relationship name="children1" source="Auditable1" target="AuditableChild1" deleteRule="Deny" db-relationship-path="children1"/>
+ <obj-relationship name="children" source="Auditable2" target="AuditableChild3" deleteRule="Deny" db-relationship-path="children"/>
+ <obj-relationship name="auditable4s" source="Auditable3" target="Auditable4" deleteRule="Deny" db-relationship-path="auditable4s"/>
+ <obj-relationship name="auditable3" source="Auditable4" target="Auditable3" deleteRule="Nullify" db-relationship-path="auditable3"/>
+ <obj-relationship name="children" source="Auditable5" target="AuditableChild5" deleteRule="Deny" db-relationship-path="children"/>
+ <obj-relationship name="parent" source="AuditableChild1" target="Auditable1" deleteRule="Nullify" db-relationship-path="parent"/>
+ <obj-relationship name="parent" source="AuditableChild1x" target="Auditable1" deleteRule="Nullify" db-relationship-path="parent"/>
+ <obj-relationship name="parent" source="AuditableChild3" target="Auditable2" deleteRule="Nullify" db-relationship-path="parent"/>
+ <obj-relationship name="parent" source="AuditableChild5" target="Auditable5" deleteRule="Nullify" db-relationship-path="parent"/>
+ <obj-relationship name="e4s" source="E3" target="E4" deleteRule="Deny" db-relationship-path="e34s.e4"/>
+ <obj-relationship name="e3s" source="E4" target="E3" deleteRule="Deny" db-relationship-path="e34s.e3"/>
+ <cgen xmlns="http://cayenne.apache.org/schema/12/cgen">
+ <destDir>../java</destDir>
+ <mode>entity</mode>
+ <template>templates/v4_1/subclass.vm</template>
+ <superTemplate>templates/v4_1/superclass.vm</superTemplate>
+ <embeddableTemplate>templates/v4_1/embeddable-subclass.vm</embeddableTemplate>
+ <embeddableSuperTemplate>templates/v4_1/embeddable-superclass.vm</embeddableSuperTemplate>
+ <queryTemplate>templates/v4_1/datamap-subclass.vm</queryTemplate>
+ <querySuperTemplate>templates/v4_1/datamap-superclass.vm</querySuperTemplate>
+ <outputPattern>*.java</outputPattern>
+ <makePairs>true</makePairs>
+ <usePkgPath>true</usePkgPath>
+ <overwrite>false</overwrite>
+ <createPropertyNames>false</createPropertyNames>
+ <createPKProperties>false</createPKProperties>
+ </cgen>
+</data-map>
diff --git a/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part2.adoc b/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part2.adoc
index bd1ac8274..67fe4efcf 100644
--- a/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part2.adoc
+++ b/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part2.adoc
@@ -28,6 +28,8 @@ include::part2/queries.adoc[]
include::part2/lifecycle.adoc[]
+include::part2/commitLog.adoc[]
+
include::part2/tuning.adoc[]
include::part2/customize.adoc[]
diff --git a/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part2/commitLog.adoc b/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part2/commitLog.adoc
new file mode 100644
index 000000000..5f56136cc
--- /dev/null
+++ b/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part2/commitLog.adoc
@@ -0,0 +1,140 @@
+// 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
+//
+// https://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.
+
+include::../var.adoc[]
+
+[[commit-log]]
+=== Commit Log
+
+Cayenne includes built-in support for capturing commit changes and delivering them to application-defined listeners. This lets you build audit trails, propagate changes to external systems, or enforce post-commit business rules without coupling that logic to individual entity classes.
+
+NOTE: Prior to Cayenne 5.0 this feature shipped as a separate `cayenne-commitlog` artifact. Starting with 5.0 it is part of the core `cayenne` dependency. Migrate by replacing `CommitLogModule.extend(binder)` with `CoreModule.extend(binder)`.
+
+==== Basic Usage
+
+To receive commit notifications, follow three steps.
+
+===== 1. Annotate entities
+
+Mark every entity whose changes you want to capture with `@CommitLog`:
+
+[source,java]
+----
+@CommitLog
+public class Order extends _Order {
+ // ...
+}
+----
+
+By default every committed entity is included. To limit auditing to annotated entities only, call `commitLogAnnotationEntitiesOnly()` during configuration (see below).
+
+The annotation accepts optional attributes:
+
+[source,java]
+----
+@CommitLog(
+ ignoredProperties = {"internalState"}, // exclude from change records
+ ignoreAttributes = false, // set true to skip all attributes
+ ignoreToOneRelationships = false, // set true to skip all to-one relationships
+ ignoreToManyRelationships = false // set true to skip all to-many relationships
+)
+public class Order extends _Order { ... }
+----
+
+For properties containing sensitive data, annotate the getter with `@Confidential` instead of `ignoredProperties`. The value will appear in the `ChangeMap` as `*******` rather than the real value.
+
+===== 2. Implement CommitLogListener
+
+[source,java]
+----
+public class AuditListener implements CommitLogListener {
+
+ @Override
+ public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) {
+ for (ObjectChange change : changes.getUniqueChanges()) {
+ switch (change.getType()) {
+ case INSERT -> System.out.println("Inserted: " + change.getPostCommitId());
+ case UPDATE -> System.out.println("Updated: " + change.getPostCommitId());
+ case DELETE -> System.out.println("Deleted: " + change.getPreCommitId());
+ }
+ }
+ }
+}
+----
+
+`ChangeMap` gives access to the full set of changes in one commit. Each `ObjectChange` carries:
+
+* `getType()` — `INSERT`, `UPDATE`, or `DELETE`
+* `getPreCommitId()` / `getPostCommitId()` — the object identity before and after the commit
+* `getAttributeChanges()` — map of attribute name → `AttributeChange` (old and new value)
+* `getToOneRelationshipChanges()` / `getToManyRelationshipChanges()` — relationship-level changes
+
+Multiple listeners may be registered; all receive identical change data for the same commit.
+
+===== 3. Register the listener
+
+[source,java]
+----
+CayenneRuntime runtime = CayenneRuntime.builder()
+ .addConfig("cayenne-project.xml")
+ .addModule(binder -> CoreModule.extend(binder)
+ .addCommitLogListener(AuditListener.class))
+ .build();
+----
+
+==== Restricting to Annotated Entities
+
+To audit only entities marked with `@CommitLog`:
+
+[source,java]
+----
+CoreModule.extend(binder)
+ .commitLogAnnotationEntitiesOnly()
+ .addCommitLogListener(AuditListener.class)
+----
+
+==== Dispatching Events Outside the Transaction
+
+By default, listeners are called within the database transaction, so a listener can write additional rows as part of the same commit. If your listener performs its own transaction (for example writing to a different database), call `excludeCommitLogFromTransaction()` to move the notification outside the main transaction:
+
+[source,java]
+----
+CoreModule.extend(binder)
+ .excludeCommitLogFromTransaction()
+ .addCommitLogListener(AuditListener.class)
+----
+
+IMPORTANT: `excludeCommitLogFromTransaction()` must be called *before* `addCommitLogListener()`.
+
+==== Custom Entity Factory
+
+For full control over which entities are included and how their metadata is computed, provide a custom `CommitLogEntityFactory`:
+
+[source,java]
+----
+public class MyEntityFactory implements CommitLogEntityFactory {
+ @Override
+ public CommitLogEntity getEntity(ObjEntity entity) {
+ // return null to exclude the entity, or a CommitLogEntity instance to include it
+ ...
+ }
+}
+----
+
+[source,java]
+----
+CoreModule.extend(binder)
+ .commitLogEntityFactory(MyEntityFactory.class)
+ .addCommitLogListener(AuditListener.class)
+----
diff --git a/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part5.adoc b/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part5.adoc
index c0cb192c9..7f70d8f16 100644
--- a/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part5.adoc
+++ b/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part5.adoc
@@ -16,8 +16,6 @@
include::part5/cacheInvalidation.adoc[]
-include::part5/commitLog.adoc[]
-
include::part5/crypto.adoc[]
include::part5/jCache.adoc[]
diff --git a/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part5/commitLog.adoc b/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part5/commitLog.adoc
deleted file mode 100644
index 0e89f4efd..000000000
--- a/docs/asciidoc/cayenne-guide/src/docs/asciidoc/_cayenne-guide/part5/commitLog.adoc
+++ /dev/null
@@ -1,81 +0,0 @@
-// 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
-//
-// https://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.
-
-include::../var.adoc[]
-
-[[ext-commit-log]]
-=== Commit log extension
-
-The goal of this module is to capture commit changes and present them to interested parties in an easy-to-process format.
-
-==== Maven
-
-[source, XML,subs="verbatim,attributes"]
-----
-<dependency>
- <groupId>org.apache.cayenne</groupId>
- <artifactId>cayenne-commitlog</artifactId>
- <version>{version}</version>
-</dependency>
-----
-
-==== Gradle
-
-[source, Groovy,subs="verbatim,attributes"]
-----
-compile 'org.apache.cayenne:cayenne-commitlog:{version}'
-----
-
-==== Usage
-
-In order to use `commitlog` module you need to perform three steps:
-
-. Mark all entities which changes you are interested in with `@org.apache.cayenne.commitlog.CommitLog` annotation
-+
-[source, Java]
-----
-@CommitLog(ignoredProperties = {"somePrivatePropertyToSkip"})
-public class MyEntity extends _MyEntity {
- // ...
-}
-----
-
-. Implement `CommitLogListener` interface.
-+
-[source, java]
-----
-public class MyCommitLogListener implements CommitLogListener {
- @Override
- public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) {
- // ChangeMap will contain all information about changes happened in performed commit
- // this particular example will print IDs of all inserted objects
- changes.getUniqueChanges().stream()
- .filter(change -> change.getType() == ObjectChangeType.INSERT)
- .map(ObjectChange::getPostCommitId)
- .forEach(id -> System.out.println("Inserted new entity with id: " + id));
- }
-}
-----
-
-. Register your listener implementation.
-+
-[source, java]
-----
-CayenneRuntime.builder()
- .addModule(binder -> CommitLogModule.extend(binder)
- .addListener(MyCommitLogListener.class))
-----
-+
-NOTE: You can use several listeners, but they all will get same changes.
-