diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eb9e80c89..91dbff0e16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ Not yet released * Implemented creatability validation for creatable entity views * Implemented `SET_NULL` inverse remove strategy validation for updatable entity views * Add updatable entity view support for inverse collections without a mapped by i.e. explicit join columns +* Rewrtite implicit joins in `ON` clause automatically to subqueries +* Add support for multiple non-cascading parent objects for updatable entity views ### Bug fixes diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java index 2b705396e4..f8739ea3b8 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java @@ -1111,7 +1111,8 @@ private JoinNode renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNo sb.append(node.getAliasInfo().getAlias()); renderedJoins.add(node); - boolean onClause = valuesNode != null || node.getOnPredicate() != null && !node.getOnPredicate().getChildren().isEmpty() || onCondition != null; + boolean realOnClause = node.getOnPredicate() != null && !node.getOnPredicate().getChildren().isEmpty() || onCondition != null; + boolean onClause = valuesNode != null || realOnClause; if (onClause) { sb.append(joinRestrictionKeyword); @@ -1125,7 +1126,9 @@ private JoinNode renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNo if (valuesNode != null) { if (!externalRepresentation) { renderValuesClausePredicate(sb, valuesNode, valuesNode.getAlias(), externalRepresentation); - sb.append(" AND "); + if (realOnClause) { + sb.append(" AND "); + } } valuesNode = null; } @@ -2185,7 +2188,9 @@ private boolean isSingleValuedAssociationId(JoinResult joinResult, List) { // Skip the foreign join column check for map keys // They aren't allowed as join sources in the JPA providers yet so we can only render them directly @@ -2193,18 +2198,24 @@ private boolean isSingleValuedAssociationId(JoinResult joinResult, List) { + if (baseType instanceof EmbeddableType) { if (node.getParentTreeNode() == null) { - attributePath = node.getValuesLikeAttribute() + "." + attributePath; + fullAttributePath = node.getValuesLikeAttribute() + "." + fullAttributePath; + elementCollectionPath = node.isValueClazzAttributeSingular() ? null : node.getValuesLikeAttribute(); baseType = node.getValueType(); - break; + } else { + if (node.getParentTreeNode().getAttribute().isCollection()) { + elementCollectionPath = node.getParentTreeNode().getRelationName(); + } else { + attributePath = node.getParentTreeNode().getRelationName() + "." + attributePath; + } + fullAttributePath = node.getParentTreeNode().getRelationName() + "." + fullAttributePath; + node = node.getParent(); + baseType = node.getNodeType(); } - attributePath = node.getParentTreeNode().getRelationName() + "." + attributePath; - node = node.getParent(); - baseType = node.getNodeType(); } - if (mainQuery.jpaProvider.isForeignJoinColumn((EntityType) baseType, attributePath)) { + if (mainQuery.jpaProvider.isForeignJoinColumn((EntityType) baseType, fullAttributePath)) { return false; } } else if (mainQuery.jpaProvider.isForeignJoinColumn((EntityType) baseType, maybeSingularAssociation.getName())) { @@ -2213,10 +2224,18 @@ private boolean isSingleValuedAssociationId(JoinResult joinResult, List managedType = metamodel.getManagedType(ExtendedManagedType.class, JpaMetamodelUtils.getTypeName(baseType)); - ExtendedAttribute associationAttribute = managedType.getOwnedSingularAttributes().get(attributePath); - return managedType.getOwnedSingularAttributes().containsKey(attributePath + "." + maybeSingularAssociationIdExpression) - && associationAttribute != null - && (contains(metamodel.getManagedType(ExtendedManagedType.class, associationAttribute.getElementClass()).getIdAttributes(), maybeSingularAssociationIdExpression) || mainQuery.jpaProvider.supportsSingleValuedAssociationNaturalIdExpressions()); + if (elementCollectionPath == null) { + ExtendedAttribute associationAttribute = managedType.getOwnedSingularAttributes().get(fullAttributePath); + return managedType.getOwnedSingularAttributes().containsKey(fullAttributePath + "." + maybeSingularAssociationIdExpression) + && associationAttribute != null + && (contains(metamodel.getManagedType(ExtendedManagedType.class, associationAttribute.getElementClass()).getIdAttributes(), maybeSingularAssociationIdExpression) || mainQuery.jpaProvider.supportsSingleValuedAssociationNaturalIdExpressions()); + } else { + // We assume that associations within element collections are always owned + ExtendedAttribute associationAttribute = managedType.getAttributes().get(fullAttributePath); + return !mainQuery.jpaProvider.needsElementCollectionIdCutoff() && managedType.getAttributes().containsKey(fullAttributePath + "." + maybeSingularAssociationIdExpression) + && associationAttribute != null + && (contains(metamodel.getManagedType(ExtendedManagedType.class, associationAttribute.getElementClass()).getIdAttributes(), maybeSingularAssociationIdExpression) || mainQuery.jpaProvider.supportsSingleValuedAssociationNaturalIdExpressions()); + } } private static boolean contains(Set> attributes, PathElementExpression expression) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java index 03c3754775..7250ae0925 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java @@ -841,7 +841,6 @@ public void appendDeReference(StringBuilder sb, String property, boolean renderT appendAlias(sb, renderTreat, externalRepresentation); // If we have a valuesTypeName, the property can only be "value" which is already handled in appendAlias if (property != null && valuesTypeName == null) { - Set> idAttributes; if (requiresElementCollectionIdCutoff && parentTreeNode != null && parentTreeNode.getAttribute().getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION && property.endsWith(".id")) { // See https://hibernate.atlassian.net/browse/HHH-13045 for details diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/CustomQuerySpecification.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/CustomQuerySpecification.java index fbe5ac8cf8..aa40725a92 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/CustomQuerySpecification.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/CustomQuerySpecification.java @@ -397,7 +397,7 @@ protected StringBuilder applySqlTransformations(String sqlQuery) { int subselectIndex = sb.indexOf(subselectTableExpr, 0); if (subselectIndex == -1) { // this is probably a VALUES clause for an entity type - int syntheticPredicateStart = sb.indexOf(syntheticPredicate, SqlUtils.indexOfWhere(sb)); + int syntheticPredicateStart = sb.indexOf(syntheticPredicate, SqlUtils.indexOfFrom(sb)); int end = syntheticPredicateStart + syntheticPredicate.length(); if (sb.indexOf(andSeparator, end) == end) { sb.replace(syntheticPredicateStart, end + andSeparator.length(), ""); diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Document.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Document.java index 675cb0d7a0..8faead0a87 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Document.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/Document.java @@ -210,7 +210,7 @@ public void setWrappedByteArray(Byte[] wrappedByteArray) { this.wrappedByteArray = wrappedByteArray; } - @OneToMany(mappedBy = "partnerDocument") + @OneToMany(mappedBy = "partnerDocument", cascade = CascadeType.PERSIST) public Set getPartners() { return partners; } @@ -260,8 +260,8 @@ public Map getContacts() { return contacts; } - public void setContacts(Map localized) { - this.contacts = localized; + public void setContacts(Map contacts) { + this.contacts = contacts; } @OneToMany @@ -271,8 +271,8 @@ public Map getContacts2() { return contacts2; } - public void setContacts2(Map localized) { - this.contacts2 = localized; + public void setContacts2(Map contacts2) { + this.contacts2 = contacts2; } @OneToMany diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/accessor/DirtyStateViewAttributeAccessor.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/accessor/DirtyStateViewAttributeAccessor.java index 2b596700e6..23d70fdbd0 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/accessor/DirtyStateViewAttributeAccessor.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/accessor/DirtyStateViewAttributeAccessor.java @@ -34,12 +34,14 @@ public final class DirtyStateViewAttributeAccessor extends ViewAttributeAccessor implements InitialValueAttributeAccessor { private final int dirtyStateIndex; + private final boolean updatableOnly; private final BasicUserType userType; @SuppressWarnings({ "rawtypes", "unchecked" }) public DirtyStateViewAttributeAccessor(EntityViewManagerImpl evm, MethodAttribute attribute) { super(evm, attribute, false); this.dirtyStateIndex = ((AbstractMethodAttribute) attribute).getDirtyStateIndex(); + this.updatableOnly = ((AbstractMethodAttribute) attribute).isUpdatableOnly() && !attribute.isCorrelated(); if (attribute instanceof SingularAttribute) { SingularAttribute singularAttribute = (SingularAttribute) attribute; if (singularAttribute.getType() instanceof BasicType) { @@ -82,7 +84,9 @@ public void setValue(Object view, Object value) { super.setValue(view, value); if (view instanceof MutableStateTrackable) { MutableStateTrackable mutableStateTrackable = (MutableStateTrackable) view; - if (value instanceof DirtyTracker) { + if (updatableOnly && value instanceof MutableStateTrackable) { + ((MutableStateTrackable) value).$$_addReadOnlyParent(mutableStateTrackable, dirtyStateIndex); + } else if (value instanceof DirtyTracker) { ((DirtyTracker) value).$$_setParent(mutableStateTrackable, dirtyStateIndex); } mutableStateTrackable.$$_getMutableState()[dirtyStateIndex] = value; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListCollectionInstantiator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListCollectionInstantiator.java index 6d7467705e..94cae3c072 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListCollectionInstantiator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListCollectionInstantiator.java @@ -33,15 +33,17 @@ public class ListCollectionInstantiator extends AbstractCollectionInstantiator { private final Set> allowedSubtypes; + private final Set> parentRequiringSubtypes; private final boolean updatable; private final boolean indexed; private final boolean optimize; private final boolean forceUnique; private final Comparator comparator; - public ListCollectionInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, boolean updatable, boolean indexed, boolean optimize, boolean forceUnique, Comparator comparator) { + public ListCollectionInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean indexed, boolean optimize, boolean forceUnique, Comparator comparator) { super(collectionFactory); this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.indexed = indexed; this.optimize = optimize; @@ -90,6 +92,6 @@ public List createCollection(int size) { @Override public RecordingList createRecordingCollection(int size) { - return new RecordingList(createCollection(size), indexed, allowedSubtypes, updatable, optimize); + return new RecordingList(createCollection(size), indexed, allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedCollectionInstantiator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedCollectionInstantiator.java index 05081e3da2..804b129ba2 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedCollectionInstantiator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedCollectionInstantiator.java @@ -32,14 +32,16 @@ public class OrderedCollectionInstantiator extends AbstractCollectionInstantiator { private final Set> allowedSubtypes; + private final Set> parentRequiringSubtypes; private final boolean updatable; private final boolean optimize; private final boolean forceUnique; private final Comparator comparator; - public OrderedCollectionInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, boolean updatable, boolean optimize, boolean forceUnique, Comparator comparator) { + public OrderedCollectionInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize, boolean forceUnique, Comparator comparator) { super(collectionFactory); this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.optimize = optimize; this.forceUnique = forceUnique; @@ -87,6 +89,6 @@ public Collection createCollection(int size) { @Override public RecordingCollection, ?> createRecordingCollection(int size) { - return new RecordingCollection(createCollection(size), false, true, allowedSubtypes, updatable, optimize); + return new RecordingCollection(createCollection(size), false, true, allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedMapInstantiator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedMapInstantiator.java index cfb5a2e53b..a88b894f45 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedMapInstantiator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedMapInstantiator.java @@ -28,12 +28,14 @@ public class OrderedMapInstantiator extends AbstractMapInstantiator, RecordingMap, ?, ?>> { private final Set> allowedSubtypes; + private final Set> parentRequiringSubtypes; private final boolean updatable; private final boolean optimize; - public OrderedMapInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, boolean updatable, boolean optimize) { + public OrderedMapInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { super(collectionFactory); this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.optimize = optimize; } @@ -45,6 +47,6 @@ public OrderedMapInstantiator(PluralObjectFactory> collectionFactory, @Override public RecordingMap, ?, ?> createRecordingCollection(int size) { - return new RecordingMap(createCollection(size), true, allowedSubtypes, updatable, optimize); + return new RecordingMap(createCollection(size), true, allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedSetCollectionInstantiator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedSetCollectionInstantiator.java index bdae44948f..be4c646c9a 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedSetCollectionInstantiator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/OrderedSetCollectionInstantiator.java @@ -28,12 +28,14 @@ public class OrderedSetCollectionInstantiator extends AbstractCollectionInstantiator { private final Set> allowedSubtypes; + private final Set> parentRequiringSubtypes; private final boolean updatable; private final boolean optimize; - public OrderedSetCollectionInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, boolean updatable, boolean optimize) { + public OrderedSetCollectionInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { super(collectionFactory); this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.optimize = optimize; } @@ -50,6 +52,6 @@ public Set createCollection(int size) { @Override public RecordingSet, ?> createRecordingCollection(int size) { - return new RecordingSet(createCollection(size), true, allowedSubtypes, updatable, optimize); + return new RecordingSet(createCollection(size), true, allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java index 3b77005931..1b99e84326 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java @@ -46,6 +46,7 @@ public class RecordingCollection, E> implements Collecti protected final C delegate; protected final Set> allowedSubtypes; + protected final Set> parentRequiringSubtypes; protected final boolean updatable; protected final boolean indexed; private final boolean ordered; @@ -60,9 +61,10 @@ public class RecordingCollection, E> implements Collecti // We remember the iterator so we can do a proper hash based collection replacement private transient RecordingReplacingIterator currentIterator; - protected RecordingCollection(C delegate, boolean indexed, boolean ordered, Set> allowedSubtypes, boolean updatable, boolean optimize, boolean hashBased) { + protected RecordingCollection(C delegate, boolean indexed, boolean ordered, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize, boolean hashBased) { this.delegate = delegate; this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.indexed = indexed; this.ordered = ordered; @@ -70,9 +72,10 @@ protected RecordingCollection(C delegate, boolean indexed, boolean ordered, Set< this.hashBased = hashBased; } - public RecordingCollection(C delegate, boolean indexed, boolean ordered, Set> allowedSubtypes, boolean updatable, boolean optimize) { + public RecordingCollection(C delegate, boolean indexed, boolean ordered, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { this.delegate = delegate; this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.indexed = indexed; this.ordered = ordered; @@ -169,6 +172,25 @@ public RecordingCollection(C delegate, boolean indexed, boolean ordered, Set newCollection = new ArrayList<>(delegate.size()); + for (E e : delegate) { + if (e == oldObject) { + newCollection.add((E) newObject); + } else { + newCollection.add(e); + } + } + delegate.clear(); + delegate.addAll(newCollection); + } else { + delegate.remove(oldObject); + delegate.add((E) newObject); + } + } + public void $$_unsetParent() { this.parentIndex = 0; this.parent = null; @@ -490,11 +512,14 @@ protected void checkType(Object e, String action) { if (!allowedSubtypes.contains(c)) { throw new IllegalArgumentException(action + " instances of type [" + c.getName() + "] is not allowed!"); } + if (parentRequiringSubtypes.contains(c) && !((DirtyTracker) e).$$_hasParent()) { + throw new IllegalArgumentException(action + " instances of type [" + c.getName() + "] is not allowed until they are assigned to an attribute that cascades the type!"); + } } } protected void checkType(Collection collection, String action) { - if (collection != null && !allowedSubtypes.isEmpty()) { + if (collection != null && !collection.isEmpty() && !allowedSubtypes.isEmpty()) { for (Object e : collection) { Class c; if (e instanceof EntityViewProxy) { @@ -506,6 +531,9 @@ protected void checkType(Collection collection, String action) { if (!allowedSubtypes.contains(c)) { throw new IllegalArgumentException(action + " instances of type [" + c.getName() + "] is not allowed!"); } + if (parentRequiringSubtypes.contains(c) && !((DirtyTracker) e).$$_hasParent()) { + throw new IllegalArgumentException(action + " instances of type [" + c.getName() + "] is not allowed until they are assigned to an attribute that cascades the type!"); + } } } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java index e3f5954f41..6e625799ed 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java @@ -30,8 +30,8 @@ */ public class RecordingList extends RecordingCollection, E> implements List { - public RecordingList(List delegate, boolean indexed, Set> allowedSubtypes, boolean updatable, boolean optimize) { - super(delegate, indexed, indexed, allowedSubtypes, updatable, optimize); + public RecordingList(List delegate, boolean indexed, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { + super(delegate, indexed, indexed, allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingMap.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingMap.java index c62ad1564f..b18930e787 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingMap.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingMap.java @@ -18,6 +18,7 @@ import com.blazebit.persistence.view.impl.entity.MapViewToEntityMapper; import com.blazebit.persistence.view.impl.proxy.DirtyTracker; +import com.blazebit.persistence.view.impl.proxy.MutableStateTrackable; import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.spi.type.BasicDirtyTracker; import com.blazebit.persistence.view.spi.type.EntityViewProxy; @@ -47,6 +48,7 @@ public class RecordingMap, K, V> implements Map, Dirty protected final C delegate; protected final Set> allowedSubtypes; + protected final Set> parentRequiringSubtypes; protected final boolean updatable; private final boolean optimize; private final boolean hashBased; @@ -62,18 +64,20 @@ public class RecordingMap, K, V> implements Map, Dirty // We remember the iterator so we can do a proper hash based collection replacement private transient RecordingEntrySetReplacingIterator currentIterator; - protected RecordingMap(C delegate, Set> allowedSubtypes, boolean updatable, boolean optimize, boolean hashBased, boolean ordered) { + protected RecordingMap(C delegate, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize, boolean hashBased, boolean ordered) { this.delegate = delegate; this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.optimize = optimize; this.hashBased = hashBased; this.ordered = ordered; } - public RecordingMap(C delegate, boolean ordered, Set> allowedSubtypes, boolean updatable, boolean optimize) { + public RecordingMap(C delegate, boolean ordered, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { this.delegate = delegate; this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.optimize = optimize; this.ordered = ordered; @@ -176,6 +180,40 @@ public RecordingMap(C delegate, boolean ordered, Set> allowedSubtypes, return parent != null; } + @Override + public void $$_replaceAttribute(Object oldObject, int attributeIndex, Object newObject) { + if (oldObject instanceof MutableStateTrackable) { + ((MutableStateTrackable) oldObject).$$_removeReadOnlyParent(this, attributeIndex); + } + if (newObject instanceof MutableStateTrackable) { + ((MutableStateTrackable) newObject).$$_addReadOnlyParent(this, attributeIndex); + } + if (attributeIndex == 1) { + if (ordered) { + Map newMap = new LinkedHashMap<>(delegate.size()); + for (Entry entry : delegate.entrySet()) { + if (entry.getKey() == oldObject) { + newMap.put((K) newObject, entry.getValue()); + } else { + newMap.put(entry.getKey(), entry.getValue()); + } + } + delegate.clear(); + delegate.putAll(newMap); + } else { + V value = delegate.remove(oldObject); + delegate.put((K) newObject, value); + } + } else { + for (Entry entry : delegate.entrySet()) { + if (entry.getValue() == oldObject) { + entry.setValue((V) newObject); + break; + } + } + } + } + @Override public void $$_unsetParent() { this.parentIndex = 0; @@ -534,11 +572,14 @@ protected void checkType(Object e, String action) { if (!allowedSubtypes.contains(c)) { throw new IllegalArgumentException(action + " instances of type [" + c.getName() + "] is not allowed!"); } + if (parentRequiringSubtypes.contains(c) && !((DirtyTracker) e).$$_hasParent()) { + throw new IllegalArgumentException(action + " instances of type [" + c.getName() + "] is not allowed until they are assigned to an attribute that cascades the type!"); + } } } protected void checkType(Map collection, String action) { - if (collection != null && !allowedSubtypes.isEmpty()) { + if (collection != null && !collection.isEmpty() && !allowedSubtypes.isEmpty()) { for (Object e : collection.values()) { Class c; if (e instanceof EntityViewProxy) { @@ -550,6 +591,9 @@ protected void checkType(Map collection, String action) { if (!allowedSubtypes.contains(c)) { throw new IllegalArgumentException(action + " instances of type [" + c.getName() + "] is not allowed!"); } + if (parentRequiringSubtypes.contains(c) && !((DirtyTracker) e).$$_hasParent()) { + throw new IllegalArgumentException(action + " instances of type [" + c.getName() + "] is not allowed until they are assigned to an attribute that cascades the type!"); + } } } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingNavigableMap.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingNavigableMap.java index 03b47b5097..e4acf0cd31 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingNavigableMap.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingNavigableMap.java @@ -27,8 +27,8 @@ */ public class RecordingNavigableMap, K, V> extends RecordingSortedMap implements NavigableMap { - public RecordingNavigableMap(C delegate, Set> allowedSubtypes, boolean updatable, boolean optimize) { - super(delegate, allowedSubtypes, updatable, optimize); + public RecordingNavigableMap(C delegate, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { + super(delegate, allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingNavigableSet.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingNavigableSet.java index d27f7a08fa..1905a48521 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingNavigableSet.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingNavigableSet.java @@ -27,8 +27,8 @@ */ public class RecordingNavigableSet extends RecordingSortedSet, E> implements NavigableSet { - public RecordingNavigableSet(NavigableSet delegate, Set> allowedSubtypes, boolean updatable, boolean optimize) { - super(delegate, allowedSubtypes, updatable, optimize); + public RecordingNavigableSet(NavigableSet delegate, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { + super(delegate, allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSet.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSet.java index dc82d9f95e..d7f98af025 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSet.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSet.java @@ -25,12 +25,12 @@ */ public class RecordingSet, E> extends RecordingCollection implements Set { - protected RecordingSet(C delegate, Set> allowedSubtypes, boolean updatable, boolean optimize, boolean hashBased, boolean ordered) { - super(delegate, false, ordered, allowedSubtypes, updatable, optimize, hashBased); + protected RecordingSet(C delegate, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize, boolean hashBased, boolean ordered) { + super(delegate, false, ordered, allowedSubtypes, parentRequiringSubtypes, updatable, optimize, hashBased); } - public RecordingSet(C delegate, boolean ordered, Set> allowedSubtypes, boolean updatable, boolean optimize) { - super(delegate, false, ordered, allowedSubtypes, updatable, optimize, true); + public RecordingSet(C delegate, boolean ordered, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { + super(delegate, false, ordered, allowedSubtypes, parentRequiringSubtypes, updatable, optimize, true); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSortedMap.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSortedMap.java index 82c408882e..2e2a66d934 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSortedMap.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSortedMap.java @@ -27,8 +27,8 @@ */ public class RecordingSortedMap, K, V> extends RecordingMap implements SortedMap { - protected RecordingSortedMap(C delegate, Set> allowedSubtypes, boolean updatable, boolean optimize) { - super(delegate, allowedSubtypes, updatable, optimize, false, false); + protected RecordingSortedMap(C delegate, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { + super(delegate, allowedSubtypes, parentRequiringSubtypes, updatable, optimize, false, false); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSortedSet.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSortedSet.java index 3d5f012a42..e3ffb83076 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSortedSet.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingSortedSet.java @@ -27,8 +27,8 @@ */ public class RecordingSortedSet, E> extends RecordingSet implements SortedSet { - protected RecordingSortedSet(C delegate, Set> allowedSubtypes, boolean updatable, boolean optimize) { - super(delegate, allowedSubtypes, updatable, optimize, false, false); + protected RecordingSortedSet(C delegate, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { + super(delegate, allowedSubtypes, parentRequiringSubtypes, updatable, optimize, false, false); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/SortedMapInstantiator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/SortedMapInstantiator.java index 7176112d08..53f13c94f4 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/SortedMapInstantiator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/SortedMapInstantiator.java @@ -30,13 +30,15 @@ public class SortedMapInstantiator extends AbstractMapInstantiator, RecordingNavigableMap, ?, ?>> { private final Set> allowedSubtypes; + private final Set> parentRequiringSubtypes; private final boolean updatable; private final boolean optimize; private final Comparator comparator; - public SortedMapInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, boolean updatable, boolean optimize, Comparator comparator) { + public SortedMapInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize, Comparator comparator) { super(collectionFactory); this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.optimize = optimize; this.comparator = comparator; @@ -49,6 +51,6 @@ public SortedMapInstantiator(PluralObjectFactory> collectionFactory, S @Override public RecordingNavigableMap, ?, ?> createRecordingCollection(int size) { - return new RecordingNavigableMap(createCollection(size), allowedSubtypes, updatable, optimize); + return new RecordingNavigableMap(createCollection(size), allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/SortedSetCollectionInstantiator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/SortedSetCollectionInstantiator.java index 8bd80d2836..276a80dcb2 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/SortedSetCollectionInstantiator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/SortedSetCollectionInstantiator.java @@ -30,13 +30,15 @@ public class SortedSetCollectionInstantiator extends AbstractCollectionInstantiator { private final Set> allowedSubtypes; + private final Set> parentRequiringSubtypes; private final boolean updatable; private final boolean optimize; private final Comparator comparator; - public SortedSetCollectionInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, boolean updatable, boolean optimize, Comparator comparator) { + public SortedSetCollectionInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize, Comparator comparator) { super(collectionFactory); this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.optimize = optimize; this.comparator = comparator; @@ -54,6 +56,6 @@ public NavigableSet createCollection(int size) { @Override public RecordingSortedSet, ?> createRecordingCollection(int size) { - return new RecordingNavigableSet(createCollection(size), allowedSubtypes, updatable, optimize); + return new RecordingNavigableSet(createCollection(size), allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/UnorderedMapInstantiator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/UnorderedMapInstantiator.java index a921357941..59d56de2e9 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/UnorderedMapInstantiator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/UnorderedMapInstantiator.java @@ -28,12 +28,14 @@ public class UnorderedMapInstantiator extends AbstractMapInstantiator, RecordingMap, ?, ?>> { private final Set> allowedSubtypes; + private final Set> parentRequiringSubtypes; private final boolean updatable; private final boolean optimize; - public UnorderedMapInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, boolean updatable, boolean optimize) { + public UnorderedMapInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { super(collectionFactory); this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.optimize = optimize; } @@ -45,6 +47,6 @@ public UnorderedMapInstantiator(PluralObjectFactory> collectionFactory @Override public RecordingMap, ?, ?> createRecordingCollection(int size) { - return new RecordingMap(createCollection(size), false, allowedSubtypes, updatable, optimize); + return new RecordingMap(createCollection(size), false, allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/UnorderedSetCollectionInstantiator.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/UnorderedSetCollectionInstantiator.java index 28233b0bfe..3569d8efb6 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/UnorderedSetCollectionInstantiator.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/UnorderedSetCollectionInstantiator.java @@ -28,12 +28,14 @@ public class UnorderedSetCollectionInstantiator extends AbstractCollectionInstantiator { private final Set> allowedSubtypes; + private final Set> parentRequiringSubtypes; private final boolean updatable; private final boolean optimize; - public UnorderedSetCollectionInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, boolean updatable, boolean optimize) { + public UnorderedSetCollectionInstantiator(PluralObjectFactory> collectionFactory, Set> allowedSubtypes, Set> parentRequiringSubtypes, boolean updatable, boolean optimize) { super(collectionFactory); this.allowedSubtypes = allowedSubtypes; + this.parentRequiringSubtypes = parentRequiringSubtypes; this.updatable = updatable; this.optimize = optimize; } @@ -50,6 +52,6 @@ public Set createCollection(int size) { @Override public RecordingSet, ?> createRecordingCollection(int size) { - return new RecordingSet(createCollection(size), false, allowedSubtypes, updatable, optimize); + return new RecordingSet(createCollection(size), false, allowedSubtypes, parentRequiringSubtypes, updatable, optimize); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractViewToEntityMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractViewToEntityMapper.java index c03cf81e6a..e386f59eb8 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractViewToEntityMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractViewToEntityMapper.java @@ -132,6 +132,12 @@ public void remove(UpdateContext context, Object element) { updater.remove(context, (EntityViewProxy) element); } + @Override + public boolean cascades(Object element) { + Class viewTypeClass = getViewTypeClass(element); + return persistUpdater.containsKey(viewTypeClass) || updateUpdater.containsKey(viewTypeClass); + } + @Override public void removeById(UpdateContext context, Object id) { defaultUpdater.remove(context, id); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/LoadOnlyViewToEntityMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/LoadOnlyViewToEntityMapper.java index a90a243933..8483138493 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/LoadOnlyViewToEntityMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/LoadOnlyViewToEntityMapper.java @@ -61,6 +61,11 @@ public void removeById(UpdateContext context, Object id) { } + @Override + public boolean cascades(Object value) { + return false; + } + @Override public , E, V> DirtyAttributeFlusher getNestedDirtyFlusher(UpdateContext context, MutableStateTrackable current, DirtyAttributeFlusher fullFlusher) { return fullFlusher; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/ViewToEntityMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/ViewToEntityMapper.java index 1a719f1243..a003fc8c08 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/ViewToEntityMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/ViewToEntityMapper.java @@ -48,4 +48,5 @@ public interface ViewToEntityMapper extends ElementToEntityMapper { public Object loadEntity(UpdateContext context, Object view); + public boolean cascades(Object value); } \ No newline at end of file diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java index 6b8cb7d7a7..e8db1603d8 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java @@ -922,6 +922,8 @@ protected boolean isEmbedded() { public abstract Set> getAllowedSubtypes(); + public abstract Set> getParentRequiringSubtypes(); + public abstract boolean isOptimizeCollectionActionsEnabled(); public abstract CollectionInstantiator getCollectionInstantiator(); @@ -937,24 +939,24 @@ protected final CollectionInstantiator createCollectionInstantiator(MetamodelBui if (comparator != null) { context.addError("Comparator can't be defined for indexed attribute at the " + getLocation()); } - return new ListCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), isUpdatable(), true, isOptimizeCollectionActionsEnabled(), false, null); + return new ListCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), getParentRequiringSubtypes(), isUpdatable(), true, isOptimizeCollectionActionsEnabled(), false, null); } else { if (sorted) { - return new SortedSetCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled(), comparator); + return new SortedSetCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), getParentRequiringSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled(), comparator); } else { if (getCollectionType() == PluralAttribute.CollectionType.SET) { if (comparator != null) { context.addError("Comparator can't be defined for non-sorted set attribute at the " + getLocation()); } if (ordered) { - return new OrderedSetCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled()); + return new OrderedSetCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), getParentRequiringSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled()); } else { - return new UnorderedSetCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled()); + return new UnorderedSetCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), getParentRequiringSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled()); } } else if (getCollectionType() == PluralAttribute.CollectionType.LIST) { - return new ListCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), isUpdatable(), false, isOptimizeCollectionActionsEnabled(), isForcedUnique(), comparator); + return new ListCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), getParentRequiringSubtypes(), isUpdatable(), false, isOptimizeCollectionActionsEnabled(), isForcedUnique(), comparator); } else { - return new OrderedCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled(), isForcedUnique(), comparator); + return new OrderedCollectionInstantiator((PluralObjectFactory>) collectionFactory, getAllowedSubtypes(), getParentRequiringSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled(), isForcedUnique(), comparator); } } } @@ -963,11 +965,11 @@ protected final CollectionInstantiator createCollectionInstantiator(MetamodelBui @SuppressWarnings({ "unchecked", "rawtypes" }) protected final MapInstantiator createMapInstantiator(PluralObjectFactory> mapFactory, boolean sorted, boolean ordered, Comparator comparator) { if (sorted) { - return new SortedMapInstantiator((PluralObjectFactory>) mapFactory, getAllowedSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled(), comparator); + return new SortedMapInstantiator((PluralObjectFactory>) mapFactory, getAllowedSubtypes(), getParentRequiringSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled(), comparator); } else if (ordered) { - return new OrderedMapInstantiator((PluralObjectFactory>) mapFactory, getAllowedSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled()); + return new OrderedMapInstantiator((PluralObjectFactory>) mapFactory, getAllowedSubtypes(), getParentRequiringSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled()); } else { - return new UnorderedMapInstantiator((PluralObjectFactory>) mapFactory, getAllowedSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled()); + return new UnorderedMapInstantiator((PluralObjectFactory>) mapFactory, getAllowedSubtypes(), getParentRequiringSubtypes(), isUpdatable(), isOptimizeCollectionActionsEnabled()); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodAttribute.java index a1541664c1..8ec62a1e4a 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodAttribute.java @@ -399,6 +399,10 @@ public Y getValue(Object o) { public abstract int getDirtyStateIndex(); + public boolean isUpdatableOnly() { + return hasDirtyStateIndex() && !isPersistCascaded() && !isUpdateCascaded(); + } + public abstract Map getWritableMappedByMappings(); protected final Set> createAllowedSubtypesSet() { @@ -418,6 +422,22 @@ protected final Set> createAllowedSubtypesSet() { return Collections.unmodifiableSet(allowedSubtypes); } + protected final Set> createParentRequiringSubtypesSet() { + Set> readOnlyAllowedSubtypes = getReadOnlyAllowedSubtypes(); + Set> persistAllowedSubtypes = getPersistCascadeAllowedSubtypes(); + Set> updateAllowedSubtypes = getUpdateCascadeAllowedSubtypes(); + Set> allowedSubtypes = new HashSet<>(readOnlyAllowedSubtypes.size()); + for (Type t : readOnlyAllowedSubtypes) { + if (t instanceof ManagedViewTypeImplementor) { + ManagedViewTypeImplementor viewType = (ManagedViewTypeImplementor) t; + if (viewType.isUpdatable() && !updateAllowedSubtypes.contains(t) || viewType.isCreatable() && !persistAllowedSubtypes.contains(t)) { + allowedSubtypes.add(t.getJavaType()); + } + } + } + return Collections.unmodifiableSet(allowedSubtypes); + } + @Override public boolean isOptimizeCollectionActionsEnabled() { // For now, we optimize collection actions only when optimistic lock protected diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodPluralAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodPluralAttribute.java index 8e66705291..32ddef1492 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodPluralAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodPluralAttribute.java @@ -56,6 +56,7 @@ public abstract class AbstractMethodPluralAttribute extends AbstractMet private final Set> persistSubtypes; private final Set> updateSubtypes; private final Set> allowedSubtypes; + private final Set> parentRequiringSubtypes; private final Map, String> elementInheritanceSubtypes; private final boolean sorted; private final boolean ordered; @@ -95,9 +96,14 @@ public AbstractMethodPluralAttribute(ManagedViewTypeImplementor viewType, Met this.readOnlySubtypes = (Set>) (Set) mapping.getReadOnlySubtypes(context, embeddableMapping); if (updatable) { - this.persistSubtypes = determinePersistSubtypeSet(elementType, mapping.getCascadeSubtypes(context, embeddableMapping), mapping.getCascadePersistSubtypes(context, embeddableMapping), context); + Set> types = determinePersistSubtypeSet(elementType, mapping.getCascadeSubtypes(context, embeddableMapping), mapping.getCascadePersistSubtypes(context, embeddableMapping), context); this.persistCascaded = mapping.getCascadeTypes().contains(CascadeType.PERSIST) - || mapping.getCascadeTypes().contains(CascadeType.AUTO) && !persistSubtypes.isEmpty(); + || mapping.getCascadeTypes().contains(CascadeType.AUTO) && !types.isEmpty(); + if (persistCascaded) { + this.persistSubtypes = types; + } else { + this.persistSubtypes = Collections.emptySet(); + } } else { this.persistCascaded = false; this.persistSubtypes = Collections.emptySet(); @@ -134,6 +140,8 @@ public AbstractMethodPluralAttribute(ManagedViewTypeImplementor viewType, Met } this.allowedSubtypes = createAllowedSubtypesSet(); + // We treat elements of correlated collections specially + this.parentRequiringSubtypes = isCorrelated() ? Collections.>emptySet() : createParentRequiringSubtypesSet(); this.optimisticLockProtected = determineOptimisticLockProtected(mapping, context, mutable); this.elementInheritanceSubtypes = (Map, String>) (Map) mapping.getElementInheritanceSubtypes(context, embeddableMapping); this.dirtyStateIndex = determineDirtyStateIndex(dirtyStateIndex); @@ -293,6 +301,11 @@ public Set> getAllowedSubtypes() { return allowedSubtypes; } + @Override + public Set> getParentRequiringSubtypes() { + return parentRequiringSubtypes; + } + @Override public AttributeType getAttributeType() { return AttributeType.PLURAL; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodSingularAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodSingularAttribute.java index 41c8a16dc9..876d51795a 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodSingularAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodSingularAttribute.java @@ -61,6 +61,7 @@ public abstract class AbstractMethodSingularAttribute extends AbstractMeth private final Set> persistSubtypes; private final Set> updateSubtypes; private final Set> allowedSubtypes; + private final Set> parentRequiringSubtypes; private final Map, String> inheritanceSubtypes; @SuppressWarnings("unchecked") @@ -110,9 +111,14 @@ public AbstractMethodSingularAttribute(ManagedViewTypeImplementor viewType, M this.readOnlySubtypes = (Set>) (Set) mapping.getReadOnlySubtypes(context, embeddableMapping); if (updatable) { - this.persistSubtypes = determinePersistSubtypeSet(type, mapping.getCascadeSubtypes(context, embeddableMapping), mapping.getCascadePersistSubtypes(context, embeddableMapping), context); + Set> types = determinePersistSubtypeSet(type, mapping.getCascadeSubtypes(context, embeddableMapping), mapping.getCascadePersistSubtypes(context, embeddableMapping), context); this.persistCascaded = mapping.getCascadeTypes().contains(CascadeType.PERSIST) - || mapping.getCascadeTypes().contains(CascadeType.AUTO) && !persistSubtypes.isEmpty(); + || mapping.getCascadeTypes().contains(CascadeType.AUTO) && !types.isEmpty(); + if (persistCascaded) { + this.persistSubtypes = types; + } else { + this.persistSubtypes = Collections.emptySet(); + } } else { this.persistCascaded = false; this.persistSubtypes = Collections.emptySet(); @@ -120,7 +126,7 @@ public AbstractMethodSingularAttribute(ManagedViewTypeImplementor viewType, M ManagedType managedType = context.getEntityMetamodel().getManagedType(declaringType.getEntityClass()); this.mappedBy = mapping.determineMappedBy(managedType, this.mapping, context, embeddableMapping); - this.disallowOwnedUpdatableSubview = context.isDisallowOwnedUpdatableSubview() && mapping.isDisallowOwnedUpdatableSubview() && type instanceof ManagedViewType && mappedBy == null + this.disallowOwnedUpdatableSubview = context.isDisallowOwnedUpdatableSubview() && mapping.determineDisallowOwnedUpdatableSubview(context, embeddableMapping, updateMappableAttribute) && type instanceof ManagedViewType && mappedBy == null && updateMappableAttribute != null && updateMappableAttribute.getPersistentAttributeType() != javax.persistence.metamodel.Attribute.PersistentAttributeType.EMBEDDED; // The declaring type must be mutable, otherwise attributes can't have cascading @@ -165,6 +171,8 @@ public AbstractMethodSingularAttribute(ManagedViewTypeImplementor viewType, M } this.allowedSubtypes = createAllowedSubtypesSet(); + // We treat correlated attributes specially + this.parentRequiringSubtypes = isCorrelated() ? Collections.>emptySet() : createParentRequiringSubtypesSet(); this.optimisticLockProtected = determineOptimisticLockProtected(mapping, context, mutable); this.inheritanceSubtypes = (Map, String>) (Map) mapping.getInheritanceSubtypes(context, embeddableMapping); this.dirtyStateIndex = determineDirtyStateIndex(dirtyStateIndex); @@ -342,6 +350,11 @@ public Set> getAllowedSubtypes() { return allowedSubtypes; } + @Override + public Set> getParentRequiringSubtypes() { + return parentRequiringSubtypes; + } + @Override public Type getType() { return type; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterAttribute.java index fcbd0f273f..aef515b50d 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterAttribute.java @@ -137,6 +137,11 @@ public Set> getAllowedSubtypes() { return Collections.emptySet(); } + @Override + public Set> getParentRequiringSubtypes() { + return Collections.emptySet(); + } + @Override public boolean isOptimizeCollectionActionsEnabled() { return false; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java index 24e9243c4e..5647672b2e 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java @@ -24,6 +24,7 @@ import com.blazebit.persistence.view.spi.EntityViewAttributeMapping; import com.blazebit.persistence.view.spi.type.TypeConverter; +import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.ManagedType; import java.lang.annotation.Annotation; import java.util.Arrays; @@ -67,7 +68,7 @@ public abstract class AttributeMapping implements EntityViewAttributeMapping { protected ContainerBehavior containerBehavior; protected Class> comparatorClass; protected boolean forceUniqueness; - protected boolean disallowOwnedUpdatableSubview = true; + protected Boolean disallowOwnedUpdatableSubview; // Other configs protected Integer defaultBatchSize; @@ -177,7 +178,7 @@ public void setForceUniqueness(boolean forceUniqueness) { @Override public boolean isDisallowOwnedUpdatableSubview() { - return disallowOwnedUpdatableSubview; + return !Boolean.FALSE.equals(disallowOwnedUpdatableSubview); } @Override @@ -210,6 +211,8 @@ public boolean isSorted() { return containerBehavior == ContainerBehavior.SORTED; } + public abstract boolean determineDisallowOwnedUpdatableSubview(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping, Attribute updateMappableAttribute); + public abstract String determineMappedBy(ManagedType managedType, String mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping); public abstract Map determineWritableMappedByMappings(ManagedType managedType, String mappedBy, MetamodelBuildingContext context); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java index 0921256bab..8c6b1be4c2 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java @@ -26,8 +26,8 @@ import com.blazebit.persistence.view.MappingSubquery; import com.blazebit.persistence.view.impl.metamodel.attribute.CorrelatedMethodCollectionAttribute; import com.blazebit.persistence.view.impl.metamodel.attribute.CorrelatedMethodListAttribute; -import com.blazebit.persistence.view.impl.metamodel.attribute.CorrelatedMethodSingularAttribute; import com.blazebit.persistence.view.impl.metamodel.attribute.CorrelatedMethodSetAttribute; +import com.blazebit.persistence.view.impl.metamodel.attribute.CorrelatedMethodSingularAttribute; import com.blazebit.persistence.view.impl.metamodel.attribute.MappingMethodCollectionAttribute; import com.blazebit.persistence.view.impl.metamodel.attribute.MappingMethodListAttribute; import com.blazebit.persistence.view.impl.metamodel.attribute.MappingMethodMapAttribute; @@ -321,25 +321,21 @@ public void initializeViewMappings(MetamodelBuildingContext context) { } // Also see AbstractMethodPluralAttribute#determineUpdatable() for the same logic if (attributeViewMapping != null) { + boolean allowCreatable = cascadeTypes.contains(CascadeType.PERSIST) || attributeViewMapping.isCreatable() && cascadeTypes.contains(CascadeType.AUTO); if (isUpdatable == Boolean.TRUE || getDeclaringView().isUpdatable()) { - if (hasSetter || isCollection && (cascadeTypes.contains(CascadeType.PERSIST) || attributeViewMapping.isCreatable())) { - boolean allowUpdatable; - if (isCollection) { - allowUpdatable = true; - } else { - allowUpdatable = !disallowOwnedUpdatableSubview || attributeViewMapping.isUpdatable(); - } + if (hasSetter || isCollection && allowCreatable) { + boolean allowUpdatable = cascadeTypes.contains(CascadeType.UPDATE) || attributeViewMapping.isUpdatable() && cascadeTypes.contains(CascadeType.AUTO); // But only if the attribute is explicitly or implicitly updatable - this.readOnlySubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), false, false); - this.cascadeSubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), allowUpdatable, true); + this.readOnlySubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), true, true); + this.cascadeSubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), allowUpdatable, allowCreatable); } else { this.cascadeSubtypeMappings = Collections.emptyMap(); } } else { // Allow all read-only subtypes and also creatable subtypes for creatable-only views - if (getDeclaringView().isCreatable() && (hasSetter || isCollection && (cascadeTypes.contains(CascadeType.PERSIST) || attributeViewMapping.isCreatable()))) { - this.readOnlySubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), false, false); - this.cascadePersistSubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), false, true); + if (getDeclaringView().isCreatable() && (hasSetter || isCollection && allowCreatable)) { + this.readOnlySubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), true, true); + this.cascadePersistSubtypeMappings = initializeDependentSubtypeMappingsAuto(context, attributeViewMapping.getEntityViewClass(), false, allowCreatable); } this.cascadeSubtypeMappings = Collections.emptyMap(); } @@ -383,6 +379,24 @@ public boolean validateDependencies(MetamodelBuildingContext context, Set updateMappableAttribute) { + if (disallowOwnedUpdatableSubview != null) { + return disallowOwnedUpdatableSubview; + } + String mappedBy; + if (embeddableMapping == null) { + mappedBy = this.mappedBy; + } else { + if (embeddableMappedByMap == null) { + embeddableMappedByMap = new HashMap<>(1); + } + mappedBy = embeddableMappedByMap.get(embeddableMapping); + } + + return disallowOwnedUpdatableSubview = isCollection || mappedBy != null && !mappedBy.isEmpty() || updateMappableAttribute != null && updateMappableAttribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE; + } + public String determineMappedBy(ManagedType managedType, String mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { String mappedBy; if (embeddableMapping == null) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterAttributeMapping.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterAttributeMapping.java index 7678bd79e9..9645b90d68 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterAttributeMapping.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterAttributeMapping.java @@ -35,6 +35,7 @@ import com.blazebit.persistence.view.spi.EntityViewMapping; import com.blazebit.persistence.view.spi.EntityViewParameterMapping; +import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.ManagedType; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; @@ -100,6 +101,11 @@ public String getMappedBy() { return null; } + @Override + public boolean determineDisallowOwnedUpdatableSubview(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping, Attribute updateMappableAttribute) { + return false; + } + @Override public String determineMappedBy(ManagedType managedType, String mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { return null; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/DirtyTracker.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/DirtyTracker.java index ede50640bf..cc74fbc74e 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/DirtyTracker.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/DirtyTracker.java @@ -42,4 +42,6 @@ public interface DirtyTracker extends BasicDirtyTracker { public long $$_getSimpleDirty(); + public void $$_replaceAttribute(Object oldObject, int attributeIndex, Object newObject); + } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/MutableStateTrackable.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/MutableStateTrackable.java index ad88dbfe63..582dc87549 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/MutableStateTrackable.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/MutableStateTrackable.java @@ -18,6 +18,8 @@ import com.blazebit.persistence.view.spi.type.EntityViewProxy; +import java.util.List; + /** * @author Christian Beikov * @since 1.2.0 @@ -35,6 +37,17 @@ public interface MutableStateTrackable extends EntityViewProxy, DirtyTracker { public DirtyTracker $$_getParent(); + /** + * Returns an interleaved list of read only parent objects and parent indexes. + * + * @return An interleaved list of read only parent objects and parent indexes + */ + public List $$_getReadOnlyParents(); + + public void $$_addReadOnlyParent(DirtyTracker readOnlyParent, int parentIndex); + + public void $$_removeReadOnlyParent(DirtyTracker readOnlyParent, int parentIndex); + public int $$_getParentIndex(); public void $$_setIsNew(boolean isNew); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java index 61b494487f..4b5b289a1f 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java @@ -277,6 +277,7 @@ private Class createProxyClass(EntityViewManager entityViewMana boolean dirtyChecking = false; CtField dirtyField = null; + CtField readOnlyParentsField = null; CtField parentField = null; CtField parentIndexField = null; CtField initialStateField = null; @@ -307,6 +308,10 @@ private Class createProxyClass(EntityViewManager entityViewMana mutableStateField.setModifiers(getModifiers(false)); cc.addField(mutableStateField); + readOnlyParentsField = new CtField(pool.get(List.class.getName()), "$$_readOnlyParents", cc); + readOnlyParentsField.setModifiers(getModifiers(true)); + readOnlyParentsField.setGenericSignature(Descriptor.of(List.class.getName()) + "<" + Descriptor.of(Object.class.getName()) + ">;"); + cc.addField(readOnlyParentsField); parentField = new CtField(pool.get(DirtyTracker.class.getName()), "$$_parent", cc); parentField.setModifiers(getModifiers(true)); cc.addField(parentField); @@ -317,11 +322,14 @@ private Class createProxyClass(EntityViewManager entityViewMana dirtyChecking = true; addGetter(cc, mutableStateField, "$$_getMutableState"); + addGetter(cc, readOnlyParentsField, "$$_getReadOnlyParents"); addGetter(cc, parentField, "$$_getParent"); addGetter(cc, parentIndexField, "$$_getParentIndex"); + addAddReadOnlyParent(cc, readOnlyParentsField, parentField); + addRemoveReadOnlyParent(cc, readOnlyParentsField); addSetParent(cc, parentField, parentIndexField); addHasParent(cc, parentField); - addUnsetParent(cc, parentField, parentIndexField); + addUnsetParent(cc, parentField, parentIndexField, readOnlyParentsField); markDirtyStub = addMarkDirtyStub(cc); } @@ -387,6 +395,7 @@ private Class createProxyClass(EntityViewManager entityViewMana } if (dirtyChecking) { + addReplaceAttribute(cc, mutableAttributes); cc.removeMethod(markDirtyStub); if (mutableAttributeCount > 64) { throw new IllegalArgumentException("Support for more than 64 mutable attributes per view is not yet implemented! " + viewType.getJavaType().getName() + " has " + mutableAttributeCount); @@ -1247,6 +1256,101 @@ private boolean supportsDirtyTracking(AbstractMethodAttribute mutableAttri } } + private CtMethod addAddReadOnlyParent(CtClass cc, CtField readOnlyParentField, CtField parentField) throws CannotCompileException { + FieldInfo parentFieldInfo = readOnlyParentField.getFieldInfo2(); + String desc = "(" + Descriptor.of(DirtyTracker.class.getName()) + "I)V"; + ConstPool cp = parentFieldInfo.getConstPool(); + MethodInfo minfo = new MethodInfo(cp, "$$_addReadOnlyParent", desc); + minfo.setAccessFlags(AccessFlag.PUBLIC); + String readOnlyParentFieldName = parentFieldInfo.getName(); + String parentFieldName = parentField.getName(); + + StringBuilder sb = new StringBuilder(); + + sb.append("{\n"); + + // Strict check for write-parent + sb.append("\tif ($0.").append(parentFieldName).append(" == null) {\n"); + sb.append("\t\tthrow new IllegalStateException(\"Can't set read only parent for object \" + $0.toString() + \" util it doesn't have a writable parent! First add the object to an attribute with proper cascading. If you just want to reference it convert the object with EntityViewManager.getReference() or EntityViewManager.convert()!\");\n"); + sb.append("\t}\n"); + + sb.append("\tif ($0.").append(readOnlyParentFieldName).append(" == null) {\n"); + sb.append("\t\t$0.").append(readOnlyParentFieldName).append(" = new java.util.ArrayList();\n"); + sb.append("\t}\n"); + + sb.append("\t$0.").append(readOnlyParentFieldName).append(".add($1);\n"); + sb.append("\t$0.").append(readOnlyParentFieldName).append(".add(Integer#valueOf($2));\n"); + + sb.append('}'); + + CtMethod method = CtMethod.make(minfo, cc); + method.setBody(sb.toString()); + cc.addMethod(method); + return method; + } + + private CtMethod addRemoveReadOnlyParent(CtClass cc, CtField readOnlyParentField) throws CannotCompileException { + FieldInfo parentFieldInfo = readOnlyParentField.getFieldInfo2(); + String desc = "(" + Descriptor.of(DirtyTracker.class.getName()) + "I)V"; + ConstPool cp = parentFieldInfo.getConstPool(); + MethodInfo minfo = new MethodInfo(cp, "$$_removeReadOnlyParent", desc); + minfo.setAccessFlags(AccessFlag.PUBLIC); + String readOnlyParentFieldName = parentFieldInfo.getName(); + + StringBuilder sb = new StringBuilder(); + + sb.append("{\n"); + + sb.append("\tif ($0.").append(readOnlyParentFieldName).append(" != null) {\n"); + sb.append("\t\tint size = $0.").append(readOnlyParentFieldName).append(".size();\n"); + sb.append("\t\tfor (int i = 0; i < size; i += 2) {\n"); + sb.append("\t\t\tif ($0.").append(readOnlyParentFieldName).append(".get(i) == $1 && ((Integer) $0.").append(readOnlyParentFieldName).append(".get(i + 1)).intValue() == $2) {\n"); + sb.append("\t\t\t\t$0.").append(readOnlyParentFieldName).append(".remove(i + 1);\n"); + sb.append("\t\t\t\t$0.").append(readOnlyParentFieldName).append(".remove(i);\n"); + sb.append("\t\t\t\tbreak;\n"); + sb.append("\t\t\t}\n"); + sb.append("\t\t}\n"); + sb.append("\t}\n"); + + sb.append('}'); + + CtMethod method = CtMethod.make(minfo, cc); + method.setBody(sb.toString()); + cc.addMethod(method); + return method; + } + + private CtMethod addReplaceAttribute(CtClass cc, AbstractMethodAttribute[] attributes) throws CannotCompileException { + String desc = "(" + Descriptor.of(Object.class.getName()) + "I" + Descriptor.of(Object.class.getName()) + ")V"; + ConstPool cp = cc.getClassFile().getConstPool(); + MethodInfo minfo = new MethodInfo(cp, "$$_replaceAttribute", desc); + minfo.setAccessFlags(AccessFlag.PUBLIC); + + StringBuilder sb = new StringBuilder(); + + sb.append("{\n"); + + sb.append("\tswitch ($2) {"); + for (int i = 0; i < attributes.length; i++) { + AbstractMethodAttribute attribute = attributes[i]; + // Recording collections handle replacing themselves + if (attribute != null && !attribute.isCollection() && attribute.isSubview() && attribute.isUpdatableOnly()) { + sb.append("\t\tcase ").append(attribute.getDirtyStateIndex()).append(": $0.set").append(Character.toUpperCase(attribute.getName().charAt(0))).append(attribute.getName(), 1, attribute.getName().length()); + sb.append("((").append(attribute.getJavaType().getName()).append(") $3); break;\n"); + } + } + + sb.append("\t\tdefault: throw new IllegalArgumentException(\"Invalid non-mutable attribute index: \" + $2);\n"); + sb.append("\t}\n"); + + sb.append('}'); + + CtMethod method = CtMethod.make(minfo, cc); + method.setBody(sb.toString()); + cc.addMethod(method); + return method; + } + private CtMethod addSetParent(CtClass cc, CtField parentField, CtField parentIndexField) throws CannotCompileException { FieldInfo parentFieldInfo = parentField.getFieldInfo2(); FieldInfo parentIndexFieldInfo = parentIndexField.getFieldInfo2(); @@ -1294,7 +1398,7 @@ private CtMethod addHasParent(CtClass cc, CtField parentField) throws CannotComp return method; } - private CtMethod addUnsetParent(CtClass cc, CtField parentField, CtField parentIndexField) throws CannotCompileException { + private CtMethod addUnsetParent(CtClass cc, CtField parentField, CtField parentIndexField, CtField readOnlyParentsField) throws CannotCompileException { FieldInfo parentFieldInfo = parentField.getFieldInfo2(); FieldInfo parentIndexFieldInfo = parentIndexField.getFieldInfo2(); String desc = "()V"; @@ -1303,10 +1407,14 @@ private CtMethod addUnsetParent(CtClass cc, CtField parentField, CtField parentI minfo.setAccessFlags(AccessFlag.PUBLIC); String parentFieldName = parentFieldInfo.getName(); String parentIndexFieldName = parentIndexFieldInfo.getName(); + String readOnlyParentsFieldName = readOnlyParentsField.getName(); StringBuilder sb = new StringBuilder(); sb.append("{\n"); + sb.append("\tif ($0.").append(parentFieldName).append(" != null && $0.").append(readOnlyParentsFieldName).append(" != null && !$0.").append(readOnlyParentsFieldName).append(".isEmpty()) {\n"); + sb.append("\t\tthrow new IllegalStateException(\"Can't unset writable parent \" + $0.").append(parentFieldName).append(" + \" on object \" + $0.toString() + \" because it is still connected to read only parents: \" + $0.").append(readOnlyParentsFieldName).append(");\n"); + sb.append("\t}\n"); sb.append("\t$0.").append(parentFieldName).append(" = null;\n"); sb.append("\t$0.").append(parentIndexFieldName).append(" = 0;\n"); @@ -1472,6 +1580,7 @@ private CtMethod addSetter(AbstractMethodAttribute attribute, CtClass cc, // Only consider subviews here for now if (attribute.isSubview()) { String subtypeArray = addAllowedSubtypeField(cc, attribute); + addParentRequiringSubtypeField(cc, attribute); sb.append("\tif ($1 != null) {\n"); sb.append("\t\tClass c;\n"); sb.append("\t\tif ($1 instanceof ").append(EntityViewProxy.class.getName()).append(") {\n"); @@ -1486,12 +1595,19 @@ private CtMethod addSetter(AbstractMethodAttribute attribute, CtClass cc, sb.append(".concat(c.getName())"); sb.append(");\n"); sb.append("\t\t}\n"); + + sb.append("\t\tif (").append(attributeField.getDeclaringClass().getName()).append('#').append(attribute.getName()).append("_$$_parentRequiringSubtypes.contains(c) && !((").append(DirtyTracker.class.getName()).append(") $1).$$_hasParent()) {\n"); + sb.append("\t\t\tthrow new IllegalArgumentException("); + sb.append("\"Setting instances of type [\" + c.getName() + \"] on attribute '").append(attribute.getName()).append("' is not allowed until they are assigned to an attribute that cascades the type! If you want this attribute to cascade, annotate it with @UpdatableMapping(cascade = { UPDATE }) or @UpdatableMapping(cascade = { PERSIST })\""); + sb.append(");\n"); + sb.append("\t\t}\n"); sb.append("\t}\n"); } } } if (attribute != null && attribute.getDirtyStateIndex() != -1) { + int mutableStateIndex = attribute.getDirtyStateIndex(); // Unset previous object parent if (attribute.isCollection()) { sb.append("\tif ($0.").append(fieldName).append(" != null && $0.").append(fieldName).append(" != $1) {\n"); @@ -1506,12 +1622,18 @@ private CtMethod addSetter(AbstractMethodAttribute attribute, CtClass cc, } sb.append("\t}\n"); } else if (attribute.isSubview()) { - sb.append("\tif ($0.").append(fieldName).append(" != $1 && $0.").append(fieldName).append(" instanceof ").append(BasicDirtyTracker.class.getName()).append(") {\n"); - sb.append("\t\t((").append(BasicDirtyTracker.class.getName()).append(") $0.").append(fieldName).append(").$$_unsetParent();\n"); + if (attribute.isUpdatableOnly() && !attribute.isCorrelated()) { + sb.append("\tif ($0.").append(fieldName).append(" != $1 && $0.").append(fieldName).append(" instanceof ").append(MutableStateTrackable.class.getName()).append(") {\n"); + sb.append("\t\t\t((").append(MutableStateTrackable.class.getName()).append(") $0.").append(fieldName).append(").$$_removeReadOnlyParent($0, ").append(mutableStateIndex).append(");\n"); + sb.append("\t} else if ($0.").append(fieldName).append(" != $1 && $0.").append(fieldName).append(" instanceof ").append(BasicDirtyTracker.class.getName()).append(") {\n"); + sb.append("\t\t\t((").append(BasicDirtyTracker.class.getName()).append(") $0.").append(fieldName).append(").$$_unsetParent();\n"); + } else { + sb.append("\tif ($0.").append(fieldName).append(" != $1 && $0.").append(fieldName).append(" instanceof ").append(BasicDirtyTracker.class.getName()).append(") {\n"); + sb.append("\t\t((").append(BasicDirtyTracker.class.getName()).append(") $0.").append(fieldName).append(").$$_unsetParent();\n"); + } sb.append("\t}\n"); } - int mutableStateIndex = attribute.getDirtyStateIndex(); if (mutableStateField != null) { // this.mutableState[mutableStateIndex] = $1 sb.append("\t$0.").append(mutableStateField.getName()).append("[").append(mutableStateIndex).append("] = "); @@ -1535,8 +1657,16 @@ private CtMethod addSetter(AbstractMethodAttribute attribute, CtClass cc, sb.append("\t\t}\n"); } } else if (attribute.isSubview()) { - sb.append("\t\tif ($1 instanceof ").append(BasicDirtyTracker.class.getName()).append(") {\n"); - sb.append("\t\t\t((").append(BasicDirtyTracker.class.getName()).append(") $1).$$_setParent($0, ").append(mutableStateIndex).append(");\n"); + // Correlated attributes are treated special, we don't consider correlated attributes read-only parents + if (attribute.isUpdatableOnly() && !attribute.isCorrelated()) { + sb.append("\t\tif ($1 instanceof ").append(MutableStateTrackable.class.getName()).append(") {\n"); + sb.append("\t\t\t((").append(MutableStateTrackable.class.getName()).append(") $1).$$_addReadOnlyParent($0, ").append(mutableStateIndex).append(");\n"); + sb.append("\t\t} else if ($1 instanceof ").append(BasicDirtyTracker.class.getName()).append(") {\n"); + sb.append("\t\t\t((").append(BasicDirtyTracker.class.getName()).append(") $1).$$_setParent($0, ").append(mutableStateIndex).append(");\n"); + } else { + sb.append("\t\tif ($1 instanceof ").append(BasicDirtyTracker.class.getName()).append(") {\n"); + sb.append("\t\t\t((").append(BasicDirtyTracker.class.getName()).append(") $1).$$_setParent($0, ").append(mutableStateIndex).append(");\n"); + } sb.append("\t\t}\n"); } sb.append("\t}\n"); @@ -1969,6 +2099,7 @@ private void renderFieldInitialization(EntityViewManager entityViewManager, Mana if (methodAttribute instanceof PluralAttribute) { PluralAttribute pluralAttribute = (PluralAttribute) methodAttribute; addAllowedSubtypeField(attributeFields[i].getDeclaringClass(), methodAttribute); + addParentRequiringSubtypeField(attributeFields[i].getDeclaringClass(), methodAttribute); switch (pluralAttribute.getCollectionType()) { case MAP: @@ -2015,6 +2146,8 @@ private void renderFieldInitialization(EntityViewManager entityViewManager, Mana } sb.append(attributeFields[i].getDeclaringClass().getName()).append('#'); sb.append(methodAttribute.getName()).append("_$$_subtypes").append(','); + sb.append(attributeFields[i].getDeclaringClass().getName()).append('#'); + sb.append(methodAttribute.getName()).append("_$$_parentRequiringSubtypes").append(','); sb.append(methodAttribute.isUpdatable()).append(','); sb.append(methodAttribute.isOptimizeCollectionActionsEnabled()); sb.append(");\n"); @@ -2200,6 +2333,50 @@ private String addAllowedSubtypeField(CtClass declaringClass, AbstractMethodAttr return subtypeArray; } + private String addParentRequiringSubtypeField(CtClass declaringClass, AbstractMethodAttribute attribute) throws CannotCompileException { + Set> parentRequiringSubtypes = attribute.getParentRequiringSubtypes(); + String subtypeArray; + if (!parentRequiringSubtypes.isEmpty()) { + StringBuilder subtypeArrayBuilder = new StringBuilder(); + for (Class c : parentRequiringSubtypes) { + subtypeArrayBuilder.append(c.getName()); + subtypeArrayBuilder.append(", "); + } + + subtypeArrayBuilder.setLength(subtypeArrayBuilder.length() - 2); + subtypeArray = subtypeArrayBuilder.toString(); + } else { + subtypeArray = ""; + } + + try { + declaringClass.getDeclaredField(attribute.getName() + "_$$_parentRequiringSubtypes"); + return subtypeArray; + } catch (NotFoundException ex) { + } + + StringBuilder fieldSb = new StringBuilder(); + fieldSb.append("private static final java.util.Set "); + fieldSb.append(attribute.getName()); + fieldSb.append("_$$_parentRequiringSubtypes = "); + + if (!parentRequiringSubtypes.isEmpty()) { + fieldSb.append("new java.util.HashSet(java.util.Arrays.asList(new java.lang.Class[]{ "); + for (Class c : parentRequiringSubtypes) { + fieldSb.append(c.getName()); + fieldSb.append(".class, "); + } + + fieldSb.setLength(fieldSb.length() - 2); + fieldSb.append(" }));"); + } else { + fieldSb.append("java.util.Collections.emptySet();"); + } + CtField allowedSubtypesField = CtField.make(fieldSb.toString(), declaringClass); + declaringClass.addField(allowedSubtypesField); + return subtypeArray; + } + private String addEmptyObjectArray(CtClass declaringClass) throws NotFoundException, CannotCompileException { String name = "$$_empty_object_array"; CtField[] fields = declaringClass.getDeclaredFields(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/AbstractPluralAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/AbstractPluralAttributeFlusher.java index 699191771b..fe026637ef 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/AbstractPluralAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/AbstractPluralAttributeFlusher.java @@ -320,6 +320,11 @@ public boolean requiresFlushAfterPersist(V value) { return false; } + @Override + public boolean requiresDeferredFlush(V value) { + return false; + } + protected final X persistOrMerge(EntityManager em, X object) { return persistOrMerge(em, object, elementDescriptor); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/BasicAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/BasicAttributeFlusher.java index bf7df09db3..80ffb449c5 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/BasicAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/BasicAttributeFlusher.java @@ -496,6 +496,11 @@ public boolean requiresFlushAfterPersist(V value) { return false; } + @Override + public boolean requiresDeferredFlush(V value) { + return false; + } + @Override public DirtyAttributeFlusher, E, V> getDirtyFlusher(UpdateContext context, Object view, Object initial, Object current) { if (updatable) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java index 9fa6ea189d..13daf940f4 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java @@ -200,6 +200,11 @@ public boolean requiresFlushAfterPersist(V value) { return false; } + @Override + public boolean requiresDeferredFlush(V value) { + return false; + } + protected boolean executeActions(UpdateContext context, Collection jpaCollection, List>> actions, ViewToEntityMapper mapper) { for (CollectionAction> action : actions) { action.doAction(jpaCollection, context, mapper, removeListener); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionElementAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionElementAttributeFlusher.java index 082ef2db8b..e1c8ae4a53 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionElementAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionElementAttributeFlusher.java @@ -138,6 +138,11 @@ public boolean requiresFlushAfterPersist(V value) { return false; } + @Override + public boolean requiresDeferredFlush(V value) { + return false; + } + @Override public DirtyChecker[] getNestedCheckers(V current) { throw new UnsupportedOperationException(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CompositeAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CompositeAttributeFlusher.java index 9d4492acf6..964279e6ee 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CompositeAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CompositeAttributeFlusher.java @@ -386,6 +386,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer Object[] state = element.$$_getMutableState(); boolean optimisticLock = false; + List deferredFlushers = null; if (value instanceof DirtyStateTrackable) { Object[] initialState = ((DirtyStateTrackable) value).$$_getInitialState(); @@ -394,18 +395,34 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer for (int i = 0; i < state.length; i++) { DirtyAttributeFlusher flusher = flushers[i]; if (flusher != null) { - optimisticLock |= flusher.isOptimisticLockProtected(); - Object newInitialValue = flusher.cloneDeep(value, initialState[i], state[i]); - flusher.flushQuery(context, parameterPrefix, query, ownerView, value, state[i], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[i]); - initialState[i] = flusher.getNewInitialValue(context, newInitialValue, state[i]); + if (flusher.requiresDeferredFlush(state[i])) { + if (deferredFlushers == null) { + deferredFlushers = new ArrayList<>(); + } + deferredFlushers.add(i); + optimisticLock |= flusher.isOptimisticLockProtected(); + } else { + optimisticLock |= flusher.isOptimisticLockProtected(); + Object newInitialValue = flusher.cloneDeep(value, initialState[i], state[i]); + flusher.flushQuery(context, parameterPrefix, query, ownerView, value, state[i], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[i]); + initialState[i] = flusher.getNewInitialValue(context, newInitialValue, state[i]); + } } } } else { for (int i = 0; i < state.length; i++) { DirtyAttributeFlusher flusher = flushers[i]; if (flusher != null) { - optimisticLock |= flusher.isOptimisticLockProtected(); - flusher.flushQuery(context, parameterPrefix, query, ownerView, value, state[i], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[i]); + if (flusher.requiresDeferredFlush(state[i])) { + if (deferredFlushers == null) { + deferredFlushers = new ArrayList<>(); + } + deferredFlushers.add(i); + optimisticLock |= flusher.isOptimisticLockProtected(); + } else { + optimisticLock |= flusher.isOptimisticLockProtected(); + flusher.flushQuery(context, parameterPrefix, query, ownerView, value, state[i], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[i]); + } } } } @@ -423,6 +440,33 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer context.getInitialStateResetter().addVersionedView(element, element.$$_getVersion()); versionFlusher.flushQuery(context, parameterPrefix, query, ownerView, value, element.$$_getVersion(), null); } + + if (deferredFlushers != null) { + if (value instanceof DirtyStateTrackable) { + Object[] initialState = ((DirtyStateTrackable) value).$$_getInitialState(); + context.getInitialStateResetter().addState(initialState, initialState.clone()); + + for (int i = 0; i < deferredFlushers.size(); i++) { + final int index = deferredFlushers.get(i); + final DirtyAttributeFlusher flusher = flushers[index]; + if (flusher != null) { + optimisticLock |= flusher.isOptimisticLockProtected(); + Object newInitialValue = flusher.cloneDeep(value, initialState[index], state[index]); + flusher.flushQuery(context, parameterPrefix, query, ownerView, value, state[index], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[index]); + initialState[index] = flusher.getNewInitialValue(context, newInitialValue, state[index]); + } + } + } else { + for (int i = 0; i < deferredFlushers.size(); i++) { + final int index = deferredFlushers.get(i); + final DirtyAttributeFlusher flusher = flushers[index]; + if (flusher != null) { + optimisticLock |= flusher.isOptimisticLockProtected(); + flusher.flushQuery(context, parameterPrefix, query, ownerView, value, state[index], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[index]); + } + } + } + } } public Object getEntityIdCopy(UpdateContext context, EntityViewProxy updatableProxy) { @@ -478,6 +522,7 @@ public boolean flushEntity(UpdateContext context, Object entity, Object ownerVie RecordingMap recordingMap = null; Object removedValue = null; Set removedKeys = null; + List afterPersistFlushers = null; List deferredFlushers = null; boolean successful = false; try { @@ -553,6 +598,13 @@ public boolean flushEntity(UpdateContext context, Object entity, Object ownerVie final DirtyAttributeFlusher flusher = flushers[i]; if (flusher != null) { if (doPersist && flusher.requiresFlushAfterPersist(state[i])) { + if (afterPersistFlushers == null) { + afterPersistFlushers = new ArrayList<>(); + } + afterPersistFlushers.add(i); + wasDirty = true; + optimisticLock |= flusher.isOptimisticLockProtected(); + } else if (flusher.requiresDeferredFlush(state[i])) { if (deferredFlushers == null) { deferredFlushers = new ArrayList<>(); } @@ -574,6 +626,13 @@ public boolean flushEntity(UpdateContext context, Object entity, Object ownerVie final DirtyAttributeFlusher flusher = flushers[i]; if (flusher != null) { if (doPersist && flusher.requiresFlushAfterPersist(state[i])) { + if (afterPersistFlushers == null) { + afterPersistFlushers = new ArrayList<>(); + } + afterPersistFlushers.add(i); + wasDirty = true; + optimisticLock |= flusher.isOptimisticLockProtected(); + } else if (flusher.requiresDeferredFlush(state[i])) { if (deferredFlushers == null) { deferredFlushers = new ArrayList<>(); } @@ -608,6 +667,9 @@ public boolean flushEntity(UpdateContext context, Object entity, Object ownerVie } versionFlusher.flushEntity(context, entity, ownerView, value, updatableProxy.$$_getVersion(), null); } + if (deferredFlushers != null) { + deferredFlushEntity(context, entity, ownerView, updatableProxy, deferredFlushers); + } if (doPersist) { // If the class of the object is an entity, we persist the object context.getEntityManager().persist(entity); @@ -629,11 +691,11 @@ public boolean flushEntity(UpdateContext context, Object entity, Object ownerVie context.getInitialStateResetter().addPersistedView(updatableProxy, oldId); } - if (successful && deferredFlushers != null) { - deferredFlushEntity(context, entity, ownerView, updatableProxy, deferredFlushers); + if (successful && afterPersistFlushers != null) { + deferredFlushEntity(context, entity, ownerView, updatableProxy, afterPersistFlushers); } - } else if (successful && deferredFlushers != null) { - deferredFlushEntity(context, entity, ownerView, updatableProxy, deferredFlushers); + } else if (successful && afterPersistFlushers != null) { + deferredFlushEntity(context, entity, ownerView, updatableProxy, afterPersistFlushers); } if (doPersist) { @@ -643,9 +705,17 @@ public boolean flushEntity(UpdateContext context, Object entity, Object ownerVie } if (recordingCollection != null && (recordingCollection.isHashBased() || persistViewMapper != null)) { // Reset the parent accordingly - updatableProxy.$$_unsetParent(); - if (newObject instanceof BasicDirtyTracker) { - ((BasicDirtyTracker) newObject).$$_setParent(parent, parentIndex); + List readOnlyParents = updatableProxy.$$_getReadOnlyParents(); + if (newObject != updatableProxy) { + if (newObject instanceof BasicDirtyTracker) { + ((BasicDirtyTracker) newObject).$$_setParent(parent, parentIndex); + if (newObject instanceof MutableStateTrackable && readOnlyParents != null) { + for (int i = 0; i < readOnlyParents.size(); i += 2) { + ((DirtyTracker) readOnlyParents.get(i)).$$_replaceAttribute(updatableProxy, (int) readOnlyParents.get(i + 1), newObject); + } + } + } + updatableProxy.$$_unsetParent(); } if (recordingCollection.getCurrentIterator() == null) { recordingCollection.getDelegate().add(newObject); @@ -654,9 +724,17 @@ public boolean flushEntity(UpdateContext context, Object entity, Object ownerVie } } else if (recordingMap != null && (persistViewMapper != null || updatableProxy.$$_getParentIndex() == 1 && recordingMap.isHashBased())) { // Reset the parent accordingly - updatableProxy.$$_unsetParent(); - if (newObject instanceof BasicDirtyTracker) { - ((BasicDirtyTracker) newObject).$$_setParent(parent, parentIndex); + List readOnlyParents = updatableProxy.$$_getReadOnlyParents(); + if (newObject != updatableProxy) { + if (newObject instanceof BasicDirtyTracker) { + ((BasicDirtyTracker) newObject).$$_setParent(parent, parentIndex); + if (newObject instanceof MutableStateTrackable) { + for (int i = 0; i < readOnlyParents.size(); i += 2) { + ((DirtyTracker) readOnlyParents.get(i)).$$_replaceAttribute(updatableProxy, (int) readOnlyParents.get(i + 1), newObject); + } + } + } + updatableProxy.$$_unsetParent(); } if (updatableProxy.$$_getParentIndex() == 1) { if (recordingMap.getCurrentIterator() == null) { @@ -955,6 +1033,11 @@ public boolean requiresFlushAfterPersist(Object value) { return false; } + @Override + public boolean requiresDeferredFlush(Object value) { + return false; + } + @Override public DirtyAttributeFlusher getDirtyFlusher(UpdateContext context, Object view, Object initial, Object current) { return this; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/DirtyAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/DirtyAttributeFlusher.java index 6001555ca7..11b71a1465 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/DirtyAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/DirtyAttributeFlusher.java @@ -66,6 +66,8 @@ public interface DirtyAttributeFlusher, public boolean requiresFlushAfterPersist(V value); + public boolean requiresDeferredFlush(V value); + public boolean requiresDeleteCascadeAfterRemove(); public boolean isViewOnlyDeleteCascaded(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/EmbeddableAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/EmbeddableAttributeFlusher.java index ca13af3264..90bbe4c71b 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/EmbeddableAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/EmbeddableAttributeFlusher.java @@ -226,6 +226,11 @@ public boolean requiresFlushAfterPersist(V value) { return nestedGraphNode != null && nestedGraphNode.requiresFlushAfterPersist(value); } + @Override + public boolean requiresDeferredFlush(V value) { + return nestedGraphNode != null && nestedGraphNode.requiresDeferredFlush(value); + } + @Override public DirtyChecker[] getNestedCheckers(V current) { return viewToEntityMapper.getUpdater(current).getDirtyChecker().getNestedCheckers((DirtyStateTrackable) current); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/SubviewAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/SubviewAttributeFlusher.java index 5e1675faba..49ff70cfd5 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/SubviewAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/SubviewAttributeFlusher.java @@ -507,6 +507,11 @@ public boolean requiresFlushAfterPersist(V value) { return false; } + @Override + public boolean requiresDeferredFlush(V value) { + return value instanceof EntityViewProxy && ((EntityViewProxy) value).$$_isNew() && !viewToEntityMapper.cascades(value); + } + @Override public DirtyChecker[] getNestedCheckers(V current) { return viewToEntityMapper.getUpdater(current).getDirtyChecker().getNestedCheckers((DirtyStateTrackable) current); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewTest.java index 17053b1d4f..1a27455568 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewTest.java @@ -33,6 +33,7 @@ import org.junit.runners.Parameterized; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * @@ -91,27 +92,11 @@ public void testUpdateWithSubview() { clearQueries(); // When - docView.setResponsiblePerson(newPerson); - update(docView); - - // Then - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isFullMode()) { - if (isQueryStrategy()) { - builder.update(Document.class); - } else { - fullFetch(builder); - if (version) { - builder.update(Document.class); - } - } + try { + docView.setResponsiblePerson(newPerson); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.validate(); - - assertNoUpdateAndReload(docView); - assertEquals(p1.getId(), doc1.getResponsiblePerson().getId()); } @Test @@ -123,27 +108,11 @@ public void testUpdateWithModifySubview() { // When newPerson.setName("newOwner"); - docView.setResponsiblePerson(newPerson); - update(docView); - - // Then - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isFullMode()) { - if (isQueryStrategy()) { - builder.update(Document.class); - } else { - fullFetch(builder); - if (version) { - builder.update(Document.class); - } - } + try { + docView.setResponsiblePerson(newPerson); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - builder.validate(); - - assertNoUpdateAndReload(docView); - Assert.assertEquals(p1.getId(), doc1.getResponsiblePerson().getId()); - Assert.assertEquals("pers2", p2.getName()); } @Test diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/complex/EntityViewUpdateSubviewInverseEmbeddedTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/complex/EntityViewUpdateSubviewInverseEmbeddedComplexTest.java similarity index 97% rename from entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/complex/EntityViewUpdateSubviewInverseEmbeddedTest.java rename to entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/complex/EntityViewUpdateSubviewInverseEmbeddedComplexTest.java index 3887ba8215..270e1e8cbd 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/complex/EntityViewUpdateSubviewInverseEmbeddedTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/complex/EntityViewUpdateSubviewInverseEmbeddedComplexTest.java @@ -56,7 +56,7 @@ @RunWith(Parameterized.class) // NOTE: No Datanucleus support yet @Category({ NoDatanucleus.class, NoEclipselink.class}) -public class EntityViewUpdateSubviewInverseEmbeddedTest extends AbstractEntityViewUpdateTest { +public class EntityViewUpdateSubviewInverseEmbeddedComplexTest extends AbstractEntityViewUpdateTest { @Override protected Class[] getEntityClasses() { @@ -69,7 +69,7 @@ protected Class[] getEntityClasses() { }; } - public EntityViewUpdateSubviewInverseEmbeddedTest(FlushMode mode, FlushStrategy strategy, boolean version) { + public EntityViewUpdateSubviewInverseEmbeddedComplexTest(FlushMode mode, FlushStrategy strategy, boolean version) { super(mode, strategy, version, UpdatableLegacyOrderView.class); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/complex/model/UpdatableLegacyOrderView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/complex/model/UpdatableLegacyOrderView.java index 1c7ea4f385..f69591eaa2 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/complex/model/UpdatableLegacyOrderView.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/complex/model/UpdatableLegacyOrderView.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.testsuite.update.subview.inverse.embedded.complex.model; +import com.blazebit.persistence.view.CascadeType; import com.blazebit.persistence.view.CreatableEntityView; import com.blazebit.persistence.view.EntityView; import com.blazebit.persistence.view.InverseRemoveStrategy; @@ -37,7 +38,7 @@ public interface UpdatableLegacyOrderView extends LegacyOrderIdView { @MappingInverse(removeStrategy = InverseRemoveStrategy.REMOVE) - @UpdatableMapping + @UpdatableMapping(cascade = { CascadeType.UPDATE, CascadeType.PERSIST }) Set getPositions(); void setPositions(Set positions); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/entityid/model/UpdatableLegacyOrderView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/entityid/model/UpdatableLegacyOrderView.java index 00747cefba..e01fa59fb3 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/entityid/model/UpdatableLegacyOrderView.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/entityid/model/UpdatableLegacyOrderView.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.testsuite.update.subview.inverse.embedded.entityid.model; +import com.blazebit.persistence.view.CascadeType; import com.blazebit.persistence.view.CreatableEntityView; import com.blazebit.persistence.view.EntityView; import com.blazebit.persistence.view.InverseRemoveStrategy; @@ -37,7 +38,7 @@ public interface UpdatableLegacyOrderView extends LegacyOrderIdView { @MappingInverse(removeStrategy = InverseRemoveStrategy.REMOVE) - @UpdatableMapping + @UpdatableMapping(cascade = { CascadeType.UPDATE, CascadeType.PERSIST }) Set getPositions(); void setPositions(Set positions); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/simple/EntityViewUpdateSubviewInverseEmbeddedTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/simple/EntityViewUpdateSubviewInverseEmbeddedSimpleTest.java similarity index 97% rename from entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/simple/EntityViewUpdateSubviewInverseEmbeddedTest.java rename to entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/simple/EntityViewUpdateSubviewInverseEmbeddedSimpleTest.java index 6dcb1b40b6..3bfa7e4699 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/simple/EntityViewUpdateSubviewInverseEmbeddedTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/simple/EntityViewUpdateSubviewInverseEmbeddedSimpleTest.java @@ -63,7 +63,7 @@ @RunWith(Parameterized.class) // NOTE: No Datanucleus support yet @Category({ NoDatanucleus.class, NoEclipselink.class}) -public class EntityViewUpdateSubviewInverseEmbeddedTest extends AbstractEntityViewUpdateTest { +public class EntityViewUpdateSubviewInverseEmbeddedSimpleTest extends AbstractEntityViewUpdateTest { @Override protected Class[] getEntityClasses() { @@ -76,7 +76,7 @@ protected Class[] getEntityClasses() { }; } - public EntityViewUpdateSubviewInverseEmbeddedTest(FlushMode mode, FlushStrategy strategy, boolean version) { + public EntityViewUpdateSubviewInverseEmbeddedSimpleTest(FlushMode mode, FlushStrategy strategy, boolean version) { super(mode, strategy, version, UpdatableLegacyOrderView.class); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/simple/model/UpdatableLegacyOrderView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/simple/model/UpdatableLegacyOrderView.java index 689bff6abc..aa717ee54c 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/simple/model/UpdatableLegacyOrderView.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/embedded/simple/model/UpdatableLegacyOrderView.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.testsuite.update.subview.inverse.embedded.simple.model; +import com.blazebit.persistence.view.CascadeType; import com.blazebit.persistence.view.CreatableEntityView; import com.blazebit.persistence.view.EntityView; import com.blazebit.persistence.view.InverseRemoveStrategy; @@ -37,7 +38,7 @@ public interface UpdatableLegacyOrderView extends LegacyOrderIdView { @MappingInverse(removeStrategy = InverseRemoveStrategy.REMOVE) - @UpdatableMapping + @UpdatableMapping(cascade = { CascadeType.UPDATE, CascadeType.PERSIST }) Set getPositions(); void setPositions(Set positions); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/entity/EntityViewUpdateSubviewInverseEmbeddedTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/entity/EntityViewUpdateSubviewInverseOneToOneEntityTest.java similarity index 96% rename from entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/entity/EntityViewUpdateSubviewInverseEmbeddedTest.java rename to entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/entity/EntityViewUpdateSubviewInverseOneToOneEntityTest.java index 67732f404d..4fabb7abad 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/entity/EntityViewUpdateSubviewInverseEmbeddedTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/entity/EntityViewUpdateSubviewInverseOneToOneEntityTest.java @@ -48,7 +48,7 @@ //@Category({ NoDatanucleus.class, NoEclipselink.class}) // NOTE: Hibernate has a bug when selecting inverse one-to-ones: https://hibernate.atlassian.net/browse/HHH-12885 @Category({ NoHibernate.class, NoDatanucleus.class, NoEclipselink.class}) -public class EntityViewUpdateSubviewInverseEmbeddedTest extends AbstractEntityViewUpdateTest { +public class EntityViewUpdateSubviewInverseOneToOneEntityTest extends AbstractEntityViewUpdateTest { private DocumentForSimpleOneToOne doc1; private DocumentForSimpleOneToOne doc2; @@ -61,7 +61,7 @@ protected Class[] getEntityClasses() { }; } - public EntityViewUpdateSubviewInverseEmbeddedTest(FlushMode mode, FlushStrategy strategy, boolean version) { + public EntityViewUpdateSubviewInverseOneToOneEntityTest(FlushMode mode, FlushStrategy strategy, boolean version) { super(mode, strategy, version, UpdatableDocumentForOneToOneView.class); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/simple/EntityViewUpdateSubviewInverseEmbeddedTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/simple/EntityViewUpdateSubviewInverseOneToOneSimpleTest.java similarity index 96% rename from entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/simple/EntityViewUpdateSubviewInverseEmbeddedTest.java rename to entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/simple/EntityViewUpdateSubviewInverseOneToOneSimpleTest.java index 0375cf0568..198bb09b67 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/simple/EntityViewUpdateSubviewInverseEmbeddedTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/onetoone/simple/EntityViewUpdateSubviewInverseOneToOneSimpleTest.java @@ -47,7 +47,7 @@ @RunWith(Parameterized.class) // NOTE: No Datanucleus support yet @Category({ NoDatanucleus.class, NoEclipselink.class}) -public class EntityViewUpdateSubviewInverseEmbeddedTest extends AbstractEntityViewUpdateTest { +public class EntityViewUpdateSubviewInverseOneToOneSimpleTest extends AbstractEntityViewUpdateTest { private DocumentForSimpleOneToOne doc1; private DocumentForSimpleOneToOne doc2; @@ -60,7 +60,7 @@ protected Class[] getEntityClasses() { }; } - public EntityViewUpdateSubviewInverseEmbeddedTest(FlushMode mode, FlushStrategy strategy, boolean version) { + public EntityViewUpdateSubviewInverseOneToOneSimpleTest(FlushMode mode, FlushStrategy strategy, boolean version) { super(mode, strategy, version, UpdatableDocumentForOneToOneView.class); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/simple/EntityViewUpdateSubviewInverseEmbeddedTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/simple/EntityViewUpdateSubviewInverseSimpleTest.java similarity index 93% rename from entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/simple/EntityViewUpdateSubviewInverseEmbeddedTest.java rename to entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/simple/EntityViewUpdateSubviewInverseSimpleTest.java index d1063a69fe..03c88b045f 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/simple/EntityViewUpdateSubviewInverseEmbeddedTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/simple/EntityViewUpdateSubviewInverseSimpleTest.java @@ -42,9 +42,9 @@ @RunWith(Parameterized.class) // NOTE: No Datanucleus support yet @Category({ NoDatanucleus.class, NoEclipselink.class}) -public class EntityViewUpdateSubviewInverseEmbeddedTest extends AbstractEntityViewUpdateDocumentTest { +public class EntityViewUpdateSubviewInverseSimpleTest extends AbstractEntityViewUpdateDocumentTest { - public EntityViewUpdateSubviewInverseEmbeddedTest(FlushMode mode, FlushStrategy strategy, boolean version) { + public EntityViewUpdateSubviewInverseSimpleTest(FlushMode mode, FlushStrategy strategy, boolean version) { super(mode, strategy, version, UpdatablePersonView.class); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/simple/model/UpdatablePersonView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/simple/model/UpdatablePersonView.java index ef8e43d2a5..8ce5462a14 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/simple/model/UpdatablePersonView.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/simple/model/UpdatablePersonView.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.view.testsuite.update.subview.inverse.simple.model; import com.blazebit.persistence.testsuite.entity.Person; +import com.blazebit.persistence.view.CascadeType; import com.blazebit.persistence.view.CreatableEntityView; import com.blazebit.persistence.view.EntityView; import com.blazebit.persistence.view.InverseRemoveStrategy; @@ -40,7 +41,7 @@ public interface UpdatablePersonView extends PersonIdView { void setName(String name); @MappingInverse(removeStrategy = InverseRemoveStrategy.REMOVE) - @UpdatableMapping + @UpdatableMapping(cascade = { CascadeType.UPDATE, CascadeType.PERSIST }) Set getOwnedDocuments2(); void setOwnedDocuments2(Set ownedDocuments2); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/unmapped/model/UpdatableDocumentView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/unmapped/model/UpdatableDocumentView.java index 97848b5331..ac216bbc2d 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/unmapped/model/UpdatableDocumentView.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/inverse/unmapped/model/UpdatableDocumentView.java @@ -18,6 +18,7 @@ import com.blazebit.persistence.testsuite.entity.Document; import com.blazebit.persistence.view.AllowUpdatableEntityViews; +import com.blazebit.persistence.view.CascadeType; import com.blazebit.persistence.view.CreatableEntityView; import com.blazebit.persistence.view.EntityView; import com.blazebit.persistence.view.InverseRemoveStrategy; @@ -40,13 +41,13 @@ public interface UpdatableDocumentView extends DocumentIdView { public String getName(); public void setName(String name); - @UpdatableMapping + @UpdatableMapping(cascade = { CascadeType.UPDATE, CascadeType.PERSIST }) @AllowUpdatableEntityViews PersonIdView getOwner(); void setOwner(PersonIdView owner); @MappingInverse(removeStrategy = InverseRemoveStrategy.REMOVE) - @UpdatableMapping + @UpdatableMapping(cascade = { CascadeType.UPDATE, CascadeType.PERSIST }) Set getVersions(); void setVersions(Set versions); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/EntityViewUpdateSubviewMultiParentInverseTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/EntityViewUpdateSubviewMultiParentInverseTest.java new file mode 100644 index 0000000000..5c08785773 --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/EntityViewUpdateSubviewMultiParentInverseTest.java @@ -0,0 +1,252 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.update.subview.multiparent.inverse; + +import com.blazebit.persistence.testsuite.base.jpa.assertion.AssertStatementBuilder; +import com.blazebit.persistence.testsuite.base.jpa.category.NoDatanucleus; +import com.blazebit.persistence.testsuite.base.jpa.category.NoEclipselink; +import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.testsuite.entity.Person; +import com.blazebit.persistence.view.FlushMode; +import com.blazebit.persistence.view.FlushStrategy; +import com.blazebit.persistence.view.impl.ConfigurationProperties; +import com.blazebit.persistence.view.spi.EntityViewConfiguration; +import com.blazebit.persistence.view.testsuite.update.AbstractEntityViewUpdateDocumentTest; +import com.blazebit.persistence.view.testsuite.update.subview.multiparent.inverse.model.DocumentIdView; +import com.blazebit.persistence.view.testsuite.update.subview.multiparent.inverse.model.PersonView; +import com.blazebit.persistence.view.testsuite.update.subview.multiparent.inverse.model.UpdatableDocumentWithGraphView; +import com.blazebit.persistence.view.testsuite.update.subview.multiparent.inverse.model.UpdatablePersonView; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Collection; + +import static org.junit.Assert.*; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +@RunWith(Parameterized.class) +// NOTE: No Datanucleus support yet +@Category({ NoDatanucleus.class, NoEclipselink.class}) +public class EntityViewUpdateSubviewMultiParentInverseTest extends AbstractEntityViewUpdateDocumentTest { + + public EntityViewUpdateSubviewMultiParentInverseTest(FlushMode mode, FlushStrategy strategy, boolean version) { + super(mode, strategy, version, UpdatableDocumentWithGraphView.class); + } + + @Parameterized.Parameters(name = "{0} - {1} - VERSIONED={2}") + public static Object[][] combinations() { + return MODE_STRATEGY_VERSION_COMBINATIONS; + } + + @Override + protected void registerViewTypes(EntityViewConfiguration cfg) { + cfg.setProperty(ConfigurationProperties.UPDATER_DISALLOW_OWNED_UPDATABLE_SUBVIEW, "true"); + cfg.addEntityView(DocumentIdView.class); + cfg.addEntityView(PersonView.class); + cfg.addEntityView(UpdatablePersonView.class); + } + + @Override + protected String[] getFetchedCollections() { + return new String[] { "partners" }; + } + + @Test + public void testUpdateAddToCollectionAndSetNonCascading() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView p2 = getP2View(UpdatablePersonView.class); + clearQueries(); + + // When + docView.getPartners().add(p2); + docView.setOwner(p2); + update(docView); + + // Then + verifyAdd(docView); + } + + @Test + public void testUpdateAddToCollectionAndSetMultipleNonCascading() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView p2 = getP2View(UpdatablePersonView.class); + clearQueries(); + + // When + docView.getPartners().add(p2); + docView.setOwner(p2); + docView.setResponsiblePerson(p2); + update(docView); + + // Then + verifyAdd(docView); + } + + private void verifyAdd(UpdatableDocumentWithGraphView docView) { + // Assert that the document and the people are loaded, but only a relation insert is done + AssertStatementBuilder builder = assertUnorderedQuerySequence(); + + if (isQueryStrategy()) { + if (isFullMode()) { + builder.update(Person.class); + } + } else { + if (isFullMode()) { + fullFetch(builder); + } else { + builder.select(Document.class); + } + builder.select(Person.class); + } + + builder.update(Document.class); + + builder.update(Person.class) + .validate(); + + assertNoUpdateAndReload(docView, true); + assertSubviewEquals(doc1.getPartners(), docView.getPartners()); + } + + @Test + public void testUpdateAddNewToCollectionAndSetNonCascading() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView pNew = evm.create(UpdatablePersonView.class); + pNew.setName("newPers"); + clearQueries(); + + // When + docView.getPartners().add(pNew); + docView.setOwner(pNew); + update(docView); + + // Then + verifyAddNew(docView); + } + + @Test + public void testUpdateAddNewToCollectionAndSetMultipleNonCascading() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView pNew = evm.create(UpdatablePersonView.class); + pNew.setName("newPers"); + clearQueries(); + + // When + docView.getPartners().add(pNew); + docView.setOwner(pNew); + docView.setResponsiblePerson(pNew); + update(docView); + + // Then + verifyAddNew(docView); + } + + private void verifyAddNew(UpdatableDocumentWithGraphView docView) { + // Assert that the document and the people are loaded, but only a relation insert is done + AssertStatementBuilder builder = assertUnorderedQuerySequence(); + + if (isQueryStrategy()) { + if (isFullMode()) { + builder.update(Person.class); + builder.update(Person.class); + } + } else { + if (isFullMode()) { + fullFetch(builder); + } else { + builder.select(Document.class); + } + } + + builder.update(Document.class); + + builder.insert(Person.class) + .validate(); + + assertNoUpdateAndReload(docView, true); + assertSubviewEquals(doc1.getPartners(), docView.getPartners()); + } + + @Test + public void testUnsetParentWithReadOnlyParent() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView p2 = getP2View(UpdatablePersonView.class); + docView.getPartners().add(p2); + docView.setOwner(p2); + try { + docView.getPartners().remove(p2); + Assert.fail("Expected failure!"); + } catch (IllegalStateException ex) { + Assert.assertTrue(ex.getMessage(), ex.getMessage().contains("Can't unset writable parent")); + } + } + + public static void assertSubviewEquals(Collection persons, Collection personSubviews) { + if (persons == null) { + assertNull(personSubviews); + return; + } + + assertNotNull(personSubviews); + assertEquals(persons.size(), personSubviews.size()); + for (Person p : persons) { + boolean found = false; + for (PersonView pSub : personSubviews) { + if (p.getName().equals(pSub.getName())) { + found = true; + break; + } + } + + if (!found) { + Assert.fail("Could not find a person subview instance with the name: " + p.getName()); + } + } + } + + @Override + protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { + builder.update(Person.class); + builder.update(Person.class); + return versionUpdate(builder); + } + + @Override + protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { + return builder.assertSelect() + .fetching(Document.class) + .fetching(Person.class) + .and(); + } + + @Override + protected AssertStatementBuilder versionUpdate(AssertStatementBuilder builder) { + return builder.update(Document.class); + } +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/DocumentIdView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/DocumentIdView.java new file mode 100644 index 0000000000..08af715fa6 --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/DocumentIdView.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.update.subview.multiparent.inverse.model; + +import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.IdMapping; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +@EntityView(Document.class) +public interface DocumentIdView { + + @IdMapping + public Long getId(); + +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/PersonView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/PersonView.java new file mode 100644 index 0000000000..3fd05e3f36 --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/PersonView.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.update.subview.multiparent.inverse.model; + +import com.blazebit.persistence.testsuite.entity.Person; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.testsuite.basic.model.IdHolderView; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +@EntityView(Person.class) +public interface PersonView extends IdHolderView { + + public String getName(); +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/UpdatableDocumentWithGraphView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/UpdatableDocumentWithGraphView.java new file mode 100644 index 0000000000..bec8b1466a --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/UpdatableDocumentWithGraphView.java @@ -0,0 +1,57 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.update.subview.multiparent.inverse.model; + +import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.IdMapping; +import com.blazebit.persistence.view.UpdatableEntityView; + +import java.util.List; +import java.util.Set; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +@UpdatableEntityView +@EntityView(Document.class) +public interface UpdatableDocumentWithGraphView { + + @IdMapping + public Long getId(); + + public Long getVersion(); + + public String getName(); + + public void setName(String name); + + public PersonView getOwner(); + + public void setOwner(PersonView owner); + + public PersonView getResponsiblePerson(); + + public void setResponsiblePerson(PersonView responsiblePerson); + + public Set getPartners(); + + public void setPartners(Set partners); + +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/UpdatablePersonView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/UpdatablePersonView.java new file mode 100644 index 0000000000..7e0bd92362 --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/inverse/model/UpdatablePersonView.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.update.subview.multiparent.inverse.model; + +import com.blazebit.persistence.testsuite.entity.Person; +import com.blazebit.persistence.view.CreatableEntityView; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.UpdatableEntityView; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +@CreatableEntityView(excludedEntityAttributes = "age") +@UpdatableEntityView +@EntityView(Person.class) +public interface UpdatablePersonView extends PersonView { + + public void setName(String name); + +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/EntityViewUpdateSubviewMultiParentJoinTableTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/EntityViewUpdateSubviewMultiParentJoinTableTest.java new file mode 100644 index 0000000000..d3afc4794b --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/EntityViewUpdateSubviewMultiParentJoinTableTest.java @@ -0,0 +1,274 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.update.subview.multiparent.jointable; + +import com.blazebit.persistence.testsuite.base.jpa.assertion.AssertStatementBuilder; +import com.blazebit.persistence.testsuite.base.jpa.category.NoDatanucleus; +import com.blazebit.persistence.testsuite.base.jpa.category.NoEclipselink; +import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.testsuite.entity.Person; +import com.blazebit.persistence.view.FlushMode; +import com.blazebit.persistence.view.FlushStrategy; +import com.blazebit.persistence.view.impl.ConfigurationProperties; +import com.blazebit.persistence.view.spi.EntityViewConfiguration; +import com.blazebit.persistence.view.testsuite.update.AbstractEntityViewUpdateDocumentTest; +import com.blazebit.persistence.view.testsuite.update.subview.multiparent.jointable.model.DocumentIdView; +import com.blazebit.persistence.view.testsuite.update.subview.multiparent.jointable.model.PersonView; +import com.blazebit.persistence.view.testsuite.update.subview.multiparent.jointable.model.UpdatableDocumentWithGraphView; +import com.blazebit.persistence.view.testsuite.update.subview.multiparent.jointable.model.UpdatablePersonView; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Collection; + +import static org.junit.Assert.*; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +@RunWith(Parameterized.class) +// NOTE: No Datanucleus support yet +@Category({ NoDatanucleus.class, NoEclipselink.class}) +public class EntityViewUpdateSubviewMultiParentJoinTableTest extends AbstractEntityViewUpdateDocumentTest { + + public EntityViewUpdateSubviewMultiParentJoinTableTest(FlushMode mode, FlushStrategy strategy, boolean version) { + super(mode, strategy, version, UpdatableDocumentWithGraphView.class); + } + + @Parameterized.Parameters(name = "{0} - {1} - VERSIONED={2}") + public static Object[][] combinations() { + return MODE_STRATEGY_VERSION_COMBINATIONS; + } + + @Override + protected void registerViewTypes(EntityViewConfiguration cfg) { + cfg.setProperty(ConfigurationProperties.UPDATER_DISALLOW_OWNED_UPDATABLE_SUBVIEW, "true"); + cfg.addEntityView(DocumentIdView.class); + cfg.addEntityView(PersonView.class); + cfg.addEntityView(UpdatablePersonView.class); + } + + @Override + protected String[] getFetchedCollections() { + return new String[] { "people" }; + } + + @Test + public void testUpdateAddToCollectionAndSetNonCascading() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView p2 = getP2View(UpdatablePersonView.class); + clearQueries(); + + // When + docView.getPeople().add(p2); + docView.setOwner(p2); + update(docView); + + // Then + verifyAdd(docView); + } + + @Test + public void testUpdateAddToCollectionAndSetMultipleNonCascading() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView p2 = getP2View(UpdatablePersonView.class); + clearQueries(); + + // When + docView.getPeople().add(p2); + docView.setOwner(p2); + docView.setResponsiblePerson(p2); + update(docView); + + // Then + verifyAdd(docView); + } + + private void verifyAdd(UpdatableDocumentWithGraphView docView) { + // Assert that the document and the people are loaded, but only a relation insert is done + AssertStatementBuilder builder = assertUnorderedQuerySequence(); + + if (isQueryStrategy()) { + if (isFullMode()) { + assertReplaceAnd(builder); + builder.update(Person.class); + builder.update(Person.class); + } + } else { + fullFetch(builder); + if (isFullMode()) { + builder.select(Person.class); + } + } + + builder.update(Document.class); + + builder.assertInsert() + .forRelation(Document.class, "people") + .validate(); + + assertNoUpdateAndReload(docView, true); + assertSubviewEquals(doc1.getPeople(), docView.getPeople()); + } + + @Test + public void testUpdateAddNewToCollectionAndSetNonCascading() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView pNew = evm.create(UpdatablePersonView.class); + pNew.setName("newPers"); + clearQueries(); + + // When + docView.getPeople().add(pNew); + docView.setOwner(pNew); + update(docView); + + // Then + verifyAddNew(docView); + } + + @Test + public void testUpdateAddNewToCollectionAndSetMultipleNonCascading() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView pNew = evm.create(UpdatablePersonView.class); + pNew.setName("newPers"); + clearQueries(); + + // When + docView.getPeople().add(pNew); + docView.setOwner(pNew); + docView.setResponsiblePerson(pNew); + update(docView); + + // Then + verifyAddNew(docView); + } + + private void verifyAddNew(UpdatableDocumentWithGraphView docView) { + // Assert that the document and the people are loaded, but only a relation insert is done + AssertStatementBuilder builder = assertUnorderedQuerySequence(); + + if (isQueryStrategy()) { + if (isFullMode()) { + assertReplaceAnd(builder); + builder.update(Person.class); + } + } else { + fullFetch(builder); + } + + builder.update(Document.class); + + builder.insert(Document.class, "people") + .insert(Person.class) + .validate(); + + assertNoUpdateAndReload(docView, true); + assertSubviewEquals(doc1.getPeople(), docView.getPeople()); + } + + @Test + public void testUpdateSetNonCascadingOnly() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView p2 = getP2View(UpdatablePersonView.class); + try { + docView.setOwner(p2); + Assert.fail("Expected failure!"); + } catch (IllegalArgumentException ex) { + Assert.assertTrue(ex.getMessage(), ex.getMessage().contains("Setting instances of type")); + } + } + + @Test + public void testUnsetParentWithReadOnlyParent() { + // Given + final UpdatableDocumentWithGraphView docView = getDoc1View(); + UpdatablePersonView p2 = getP2View(UpdatablePersonView.class); + docView.getPeople().add(p2); + docView.setOwner(p2); + try { + docView.getPeople().remove(docView.getPeople().size() - 1); + Assert.fail("Expected failure!"); + } catch (IllegalStateException ex) { + Assert.assertTrue(ex.getMessage(), ex.getMessage().contains("Can't unset writable parent")); + } + } + + public static void assertSubviewEquals(Collection persons, Collection personSubviews) { + if (persons == null) { + assertNull(personSubviews); + return; + } + + assertNotNull(personSubviews); + assertEquals(persons.size(), personSubviews.size()); + for (Person p : persons) { + boolean found = false; + for (PersonView pSub : personSubviews) { + if (p.getName().equals(pSub.getName())) { + found = true; + break; + } + } + + if (!found) { + Assert.fail("Could not find a person subview instance with the name: " + p.getName()); + } + } + } + + private AssertStatementBuilder assertReplaceAnd(AssertStatementBuilder builder) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + if (doc1.getPeople().size() > 1) { + builder.insert(Document.class, "people"); + } + return builder; + } + + @Override + protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { + assertReplaceAnd(builder); + builder.update(Person.class); + builder.update(Person.class); + return versionUpdate(builder); + } + + @Override + protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { + return builder.assertSelect() + .fetching(Document.class) + .fetching(Document.class, "people") + .fetching(Person.class) + .and(); + } + + @Override + protected AssertStatementBuilder versionUpdate(AssertStatementBuilder builder) { + return builder.update(Document.class); + } +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/DocumentIdView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/DocumentIdView.java new file mode 100644 index 0000000000..afda16553d --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/DocumentIdView.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.update.subview.multiparent.jointable.model; + +import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.IdMapping; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +@EntityView(Document.class) +public interface DocumentIdView { + + @IdMapping + public Long getId(); + +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/PersonView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/PersonView.java new file mode 100644 index 0000000000..f2e0dae819 --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/PersonView.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.update.subview.multiparent.jointable.model; + +import com.blazebit.persistence.testsuite.entity.Person; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.testsuite.basic.model.IdHolderView; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +@EntityView(Person.class) +public interface PersonView extends IdHolderView { + + public String getName(); +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/UpdatableDocumentWithGraphView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/UpdatableDocumentWithGraphView.java new file mode 100644 index 0000000000..b8995117a5 --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/UpdatableDocumentWithGraphView.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.update.subview.multiparent.jointable.model; + +import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.IdMapping; +import com.blazebit.persistence.view.UpdatableEntityView; + +import java.util.List; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +@UpdatableEntityView +@EntityView(Document.class) +public interface UpdatableDocumentWithGraphView { + + @IdMapping + public Long getId(); + + public Long getVersion(); + + public String getName(); + + public void setName(String name); + + public PersonView getOwner(); + + public void setOwner(PersonView owner); + + public PersonView getResponsiblePerson(); + + public void setResponsiblePerson(PersonView responsiblePerson); + + public List getPeople(); + + public void setPeople(List people); + +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/UpdatablePersonView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/UpdatablePersonView.java new file mode 100644 index 0000000000..5cf2ccc17d --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/multiparent/jointable/model/UpdatablePersonView.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.testsuite.update.subview.multiparent.jointable.model; + +import com.blazebit.persistence.testsuite.entity.Person; +import com.blazebit.persistence.view.CreatableEntityView; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.UpdatableEntityView; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +@CreatableEntityView(excludedEntityAttributes = "age") +@UpdatableEntityView +@EntityView(Person.class) +public interface UpdatablePersonView extends PersonView { + + public void setName(String name); + +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewCollectionsTest.java index 6c56063875..eef159dc19 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewCollectionsTest.java @@ -242,33 +242,11 @@ public void testUpdateModifyCollectionElement() { clearQueries(); // When - docView.getPeople().get(0).setFriend(newFriend); - update(docView); - - // Then - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - if (isFullMode()) { - assertReplaceAnd(builder); - } - builder.update(Person.class); - if (version || isFullMode()) { - builder.update(Document.class); - } - } else { - fullFetch(builder); - if (version) { - builder.update(Document.class); - } - builder.update(Person.class); + try { + docView.getPeople().get(0).setFriend(newFriend); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.validate(); - - assertNoCollectionUpdateAndReload(docView); - assertEquals(p4.getId(), p1.getFriend().getId()); - assertSubviewEquals(doc1.getPeople(), docView.getPeople()); } @Test @@ -280,33 +258,11 @@ public void testUpdateModifyCollectionElementCopy() { // When newFriend.setName("newFriend"); - docView.getPeople().get(0).setFriend(newFriend); - update(docView); - - // Then - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - if (isFullMode()) { - assertReplaceAnd(builder).update(Person.class); - versionUpdate(builder); - } - } else { - if (isFullMode()) { - fullFetch(builder); - if (version) { - versionUpdate(builder); - } - } + try { + docView.getPeople().get(0).setFriend(newFriend); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.validate(); - - assertNoCollectionUpdateAndReload(docView); - assertEquals(p3.getId(), p1.getFriend().getId()); - assertEquals("pers3", p3.getName()); - docView.getPeople().get(0).getFriend().setName("pers3"); - assertSubviewEquals(doc1.getPeople(), docView.getPeople()); } @Test @@ -318,35 +274,11 @@ public void testUpdateModifyCollectionElementAndModify() { // When newFriend.setName("newFriend"); - docView.getPeople().get(0).setFriend(newFriend); - update(docView); - - // Then - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - if (isFullMode()) { - assertReplaceAnd(builder); - } - builder.update(Person.class); - if (version ||isFullMode()) { - builder.update(Document.class); - } - } else { - fullFetch(builder); - if (version) { - builder.update(Document.class); - } - builder.update(Person.class); + try { + docView.getPeople().get(0).setFriend(newFriend); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.validate(); - - assertNoCollectionUpdateAndReload(docView); - assertEquals(p4.getId(), p1.getFriend().getId()); - assertEquals("pers4", p4.getName()); - docView.getPeople().get(0).getFriend().setName("pers4"); - assertSubviewEquals(doc1.getPeople(), docView.getPeople()); } @Test diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewMapsTest.java index 3202d21feb..5eac93f73b 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewMapsTest.java @@ -238,34 +238,11 @@ public void testUpdateModifyCollectionElement() { clearQueries(); // When - docView.getContacts().get(1).setFriend(newFriend); - update(docView); - - // Then - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - if (isFullMode()) { - assertReplaceAnd(builder); - } else { - builder.update(Person.class); - } - if (version || isFullMode()) { - builder.update(Document.class); - } - } else { - fullFetch(builder); - builder.update(Person.class); - if (version) { - builder.update(Document.class); - } + try { + docView.getContacts().get(1).setFriend(newFriend); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.validate(); - - assertNoUpdateAndReload(docView, true); - assertEquals(p4.getId(), p1.getFriend().getId()); - assertSubviewEquals(doc1.getContacts(), docView.getContacts()); } @Test @@ -277,35 +254,11 @@ public void testUpdateModifyCollectionElementCopy() { // When newFriend.setName("newFriend"); - docView.getContacts().get(1).setFriend(newFriend); - update(docView); - - // Then - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - if (isFullMode()) { - assertReplaceAnd(builder); - if (version || isFullMode()) { - versionUpdate(builder); - } - } - } else { - if (isFullMode()) { - fullFetch(builder); - if (version) { - versionUpdate(builder); - } - } + try { + docView.getContacts().get(1).setFriend(newFriend); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.validate(); - - assertNoUpdateAndReload(docView, true); - assertEquals(p3.getId(), p1.getFriend().getId()); - assertEquals("pers3", p3.getName()); - docView.getContacts().get(1).getFriend().setName("pers3"); - assertSubviewEquals(doc1.getContacts(), docView.getContacts()); } @Test @@ -317,36 +270,11 @@ public void testUpdateModifyCollectionElementAndModify() { // When newFriend.setName("newFriend"); - docView.getContacts().get(1).setFriend(newFriend); - update(docView); - - // Then - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - if (isFullMode()) { - assertReplaceAnd(builder); - } else { - builder.update(Person.class); - } - if (version || isFullMode()) { - builder.update(Document.class); - } - } else { - fullFetch(builder); - builder.update(Person.class); - if (version) { - builder.update(Document.class); - } + try { + docView.getContacts().get(1).setFriend(newFriend); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.validate(); - - assertNoUpdateAndReload(docView, true); - assertEquals(p4.getId(), p1.getFriend().getId()); - assertEquals("pers4", p4.getName()); - docView.getContacts().get(1).getFriend().setName("pers4"); - assertSubviewEquals(doc1.getContacts(), docView.getContacts()); } @Test diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewTest.java index d8f86af4e2..3b3d133945 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewTest.java @@ -35,6 +35,7 @@ import org.junit.runners.Parameterized; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * @@ -102,29 +103,11 @@ public void testUpdateWithSubview() { clearQueries(); // When - docView.getResponsiblePerson().setFriend(newFriend); - update(docView); - - // Then - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - builder.update(Person.class); - if (isFullMode() || version) { - builder.update(Document.class); - } - } else { - fullFetch(builder); - if (version) { - builder.update(Document.class); - } - builder.update(Person.class); + try { + docView.getResponsiblePerson().setFriend(newFriend); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.validate(); - - assertNoUpdateAndReload(docView); - assertEquals(p4.getId(), doc1.getResponsiblePerson().getFriend().getId()); } @Test @@ -136,30 +119,11 @@ public void testUpdateWithModifySubview() { // When newFriend.setName("newFriend"); - docView.getResponsiblePerson().setFriend(newFriend); - update(docView); - - // Then - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - builder.update(Person.class); - if (isFullMode() || version) { - builder.update(Document.class); - } - } else { - fullFetch(builder); - if (version) { - builder.update(Document.class); - } - builder.update(Person.class); + try { + docView.getResponsiblePerson().setFriend(newFriend); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.validate(); - - assertNoUpdateAndReload(docView); - assertEquals(p4.getId(), doc1.getResponsiblePerson().getFriend().getId()); - assertEquals("pers4", p4.getName()); } @Test diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewCollectionsTest.java index 7f16c33291..2053ed939b 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewCollectionsTest.java @@ -109,31 +109,11 @@ public void testUpdateAddToCollection() { clearQueries(); // When - docView.getPeople().add(newPerson); - update(docView); - - // Then - // Assert that the document and the people are loaded, but only a relation insert is done - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - if (isFullMode()) { - assertReplaceAnd(builder); - } - } else { - fullFetch(builder); + try { + docView.getPeople().add(newPerson); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Adding instances of type")); } - - if (version || isFullMode() && isQueryStrategy()) { - builder.update(Document.class); - } - - builder.assertInsert() - .forRelation(Document.class, "people") - .validate(); - - assertNoUpdateAndReload(docView); - assertSubviewEquals(doc1.getPeople(), docView.getPeople()); } @Test @@ -189,39 +169,11 @@ public void testUpdateAddToCollectionAndModifySubview() { // When newPerson.setName("newPerson"); - docView.getPeople().add(newPerson); - update(docView); - - // Then - // In partial mode, only the document is loaded. In full mode, the people are also loaded - // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - if (isFullMode()) { - assertReplaceAnd(builder); - } - } else { - if (isFullMode()) { - fullFetch(builder); - } else { - if (preferLoadingAndDiffingOverRecreate()) { - fullFetch(builder); - } else { - assertReplaceAnd(builder); - } - } + try { + docView.getPeople().add(newPerson); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Adding instances of type")); } - if (version || isFullMode() && isQueryStrategy()) { - builder.update(Document.class); - } - - builder.assertInsert() - .forRelation(Document.class, "people") - .validate(); - assertNoUpdateAndReload(docView); - assertEquals(doc1.getPeople().size(), docView.getPeople().size()); - assertEquals("pers2", p2.getName()); } @Test diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewMapsTest.java index 1282c17eec..e871476bf7 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewMapsTest.java @@ -106,30 +106,11 @@ public void testUpdateAddToCollection() { clearQueries(); // When - docView.getContacts().put(2, newPerson); - update(docView); - - // Then - // Assert that the document and the people are loaded, but only a relation insert is done - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - if (isFullMode()) { - assertReplaceAnd(builder); - } - } else { - fullFetch(builder); + try { + docView.getContacts().put(2, newPerson); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Putting instances of type")); } - - if (version || isQueryStrategy() && isFullMode()) { - builder.update(Document.class); - } - - builder.insert(Document.class, "contacts") - .validate(); - - assertNoUpdateAndReload(docView); - assertSubviewEquals(doc1.getContacts(), docView.getContacts()); } @Test @@ -184,39 +165,11 @@ public void testUpdateAddToCollectionAndModifySubview() { // When newPerson.setName("newPerson"); - docView.getContacts().put(2, newPerson); - update(docView); - - // Then - // In partial mode, only the document is loaded. In full mode, the people are also loaded - // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (isQueryStrategy()) { - if (isFullMode()) { - assertReplaceAnd(builder); - } - } else { - if (isFullMode()) { - fullFetch(builder); - } else { - if (preferLoadingAndDiffingOverRecreate()) { - fullFetch(builder); - } else { - assertReplaceAnd(builder); - } - } - } - - if (version || isQueryStrategy() && isFullMode()) { - builder.update(Document.class); + try { + docView.getContacts().put(2, newPerson); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Putting instances of type")); } - - builder.insert(Document.class, "contacts") - .validate(); - assertNoUpdateAndReload(docView); - assertEquals(doc1.getContacts().size(), docView.getContacts().size()); - assertEquals("pers2", p2.getName()); } @Test diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewTest.java index 4c2abc8417..cd89a441d3 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewTest.java @@ -33,6 +33,7 @@ import org.junit.runners.Parameterized; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * @@ -92,23 +93,11 @@ public void testUpdateWithSubview() { clearQueries(); // When - docView.setResponsiblePerson(newPerson); - update(docView); - - // Then - // Assert that only the document is loaded and finally also updated - // There is no need to actually load the person - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (!isQueryStrategy()) { - fullFetch(builder); + try { + docView.setResponsiblePerson(newPerson); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.update(Document.class) - .validate(); - - assertNoUpdateAndReload(docView); - assertEquals(p2.getId(), doc1.getResponsiblePerson().getId()); } @Test @@ -120,24 +109,11 @@ public void testUpdateWithModifySubview() { // When newPerson.setName("newOwner"); - docView.setResponsiblePerson(newPerson); - update(docView); - - // Then - // Assert that only the document is loaded and finally also updated - // But the person is not updated - AssertStatementBuilder builder = assertUnorderedQuerySequence(); - - if (!isQueryStrategy()) { - fullFetch(builder); + try { + docView.setResponsiblePerson(newPerson); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("Setting instances of type")); } - - builder.update(Document.class) - .validate(); - - assertNoUpdateAndReload(docView); - Assert.assertEquals(p2.getId(), doc1.getResponsiblePerson().getId()); - Assert.assertEquals("pers2", p2.getName()); } @Test diff --git a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java index e3012b036a..4b5a916aa6 100644 --- a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java +++ b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java @@ -196,7 +196,7 @@ public HibernateJpaProvider(PersistenceUnitUtil persistenceUnitUtil, String dbms // See https://hibernate.atlassian.net/browse/HHH-12775 for details this.supportsSingleValuedAssociationNaturalIdExpressions = major > 5 || major == 5 && minor >= 4; // See https://hibernate.atlassian.net/browse/HHH-13045 for details - this.needsElementCollectionIdCutoffForCompositeIdOwner = major < 5 || major == 5 && minor < 4 || major == 5 && minor == 4 && "SNAPSHOT".equals(type); + this.needsElementCollectionIdCutoffForCompositeIdOwner = major < 5 || major == 5 && minor < 4; } catch (Exception e) { throw new RuntimeException(e); }