Skip to content

Commit

Permalink
[#434] Implemented creatability validation
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Nov 8, 2018
1 parent 274f12d commit 275b645
Show file tree
Hide file tree
Showing 13 changed files with 164 additions and 15 deletions.
Expand Up @@ -115,11 +115,15 @@ private PathPosition(Type<?> currentClass, Type<?> valueClass, Type<?> keyClass,
this.attribute = attribute;
}

Class<?> getRealCurrentClass() {
public Type<?> getRealCurrentType() {
return currentClass;
}

public Class<?> getRealCurrentClass() {
return currentClass.getJavaType();
}

Type<?> getCurrentType() {
public Type<?> getCurrentType() {
if (valueClass != null) {
return valueClass;
}
Expand All @@ -130,7 +134,7 @@ Type<?> getCurrentType() {
return currentClass;
}

Class<?> getCurrentClass() {
public Class<?> getCurrentClass() {
return getCurrentType().getJavaType();
}

Expand Down Expand Up @@ -174,17 +178,17 @@ public void reset(Type<?> startClass) {
}

private Type<?> getType(Type<?> baseType, Attribute<?, ?> attribute) {
Class<?> baseClass = baseType.getJavaType();
if (attribute instanceof PluralAttribute<?, ?, ?>) {
return metamodel.type(((PluralAttribute<?, ?, ?>) attribute).getJavaType());
}

Class<?> baseClass = baseType.getJavaType();
if (baseClass != null) {
Class<?> clazz = JpaMetamodelUtils.resolveFieldClass(baseType.getJavaType(), attribute);
if (clazz != null) {
return metamodel.type(clazz);
}
}
if (attribute instanceof PluralAttribute<?, ?, ?>) {
return ((PluralAttribute<?, ?, ?>) attribute).getElementType();
}
return ((SingularAttribute<?, ?>) attribute).getType();
}

Expand Down
Expand Up @@ -58,6 +58,12 @@
import com.blazebit.persistence.parser.predicate.LtPredicate;
import com.blazebit.persistence.parser.predicate.MemberOfPredicate;

import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
*
* @author Christian Beikov
Expand All @@ -71,6 +77,19 @@ public UpdatableExpressionVisitor(EntityMetamodel metamodel, Class<?> startClass
super(metamodel, metamodel.type(startClass), null);
this.updatable = updatable;
}

public Map<Attribute<?, ?>, Type<?>> getPossibleTargets() {
Map<Attribute<?, ?>, Type<?>> possibleTargets = new HashMap<>();

List<PathPosition> positions = pathPositions;
int size = positions.size();
for (int i = 0; i < size; i++) {
PathPosition position = positions.get(i);
possibleTargets.put(position.getAttribute(), position.getRealCurrentType());
}

return possibleTargets;
}

@Override
public void visit(PropertyExpression expression) {
Expand Down
Expand Up @@ -219,7 +219,7 @@ private static AttributeAccessor forEntityAttribute(EntityViewManagerImpl evm, C
}

public static AttributeAccessor forEntityMapping(EntityViewManagerImpl evm, MethodAttribute<?, ?> attribute) {
if (attribute instanceof MappingAttribute<?, ?>) {
if (((AbstractMethodAttribute<?, ?>) attribute).getUpdateMappableAttribute() != null) {
return forEntityMapping(evm, attribute.getDeclaringType().getEntityClass(), ((MappingAttribute<?, ?>) attribute).getMapping());
} else {
return null;
Expand Down
Expand Up @@ -421,9 +421,13 @@ protected boolean determineForcedUnique(MetamodelBuildingContext context) {
return false;
}

public javax.persistence.metamodel.Attribute<?, ?> getUpdateMappableAttribute() {
return updateMappableAttribute;
}

public boolean isUpdateMappable() {
// Since we can cascade correlated views, we consider them update mappable
return isCorrelated() || updateMappableAttribute != null;
return hasDirtyStateIndex() || updateMappableAttribute != null;
}

public Class<?> getCorrelated() {
Expand All @@ -440,6 +444,8 @@ public String getCorrelationExpression() {

public abstract boolean needsDirtyTracker();

public abstract boolean hasDirtyStateIndex();

/**
* @author Christian Beikov
* @since 1.2.0
Expand Down
Expand Up @@ -379,6 +379,11 @@ public boolean needsDirtyTracker() {
return isUpdatable() || isUpdateCascaded() && !getUpdateCascadeAllowedSubtypes().isEmpty();
}

@Override
public boolean hasDirtyStateIndex() {
return getDirtyStateIndex() != -1;
}

public int getAttributeIndex() {
return attributeIndex;
}
Expand Down
Expand Up @@ -97,6 +97,11 @@ public boolean isMutable() {
return false;
}

@Override
public boolean hasDirtyStateIndex() {
return false;
}

@Override
public String getMappedBy() {
return null;
Expand Down
Expand Up @@ -16,6 +16,9 @@

package com.blazebit.persistence.view.impl.metamodel;

import com.blazebit.persistence.parser.util.JpaMetamodelUtils;
import com.blazebit.persistence.spi.ExtendedAttribute;
import com.blazebit.persistence.spi.ExtendedManagedType;
import com.blazebit.persistence.view.EntityViewManager;
import com.blazebit.persistence.view.FlushMode;
import com.blazebit.persistence.view.FlushStrategy;
Expand All @@ -25,8 +28,13 @@
import com.blazebit.persistence.view.metamodel.MethodAttribute;
import com.blazebit.persistence.view.spi.type.TypeConverter;

import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.BasicType;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.SingularAttribute;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
Expand All @@ -37,6 +45,7 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
Expand Down Expand Up @@ -96,6 +105,7 @@ public ManagedViewTypeImpl(ViewMapping viewMapping, ManagedType<?> managedType,
this.flushStrategy = context.getFlushStrategy(javaType, viewMapping.getFlushStrategy());
this.lockMode = viewMapping.getResolvedLockMode();

ExtendedManagedType<?> extendedManagedType = context.getEntityMetamodel().getManagedType(ExtendedManagedType.class, jpaManagedType);
boolean embeddable = !(jpaManagedType instanceof EntityType<?>);

if (viewMapping.isCreatable()) {
Expand Down Expand Up @@ -143,6 +153,44 @@ public ManagedViewTypeImpl(ViewMapping viewMapping, ManagedType<?> managedType,
// Initialize attribute type and the dirty state index of attributes
int index = viewMapping.getIdAttribute() == null ? 0 : 1;
int dirtyStateIndex = 0;
Set<String> requiredUpdatableAttributes;
Set<String> mappedColumns;
if (creatable && validatePersistability) {
requiredUpdatableAttributes = new HashSet<>();
mappedColumns = new HashSet<>();
OUTER: for (Map.Entry<String, ? extends ExtendedAttribute<?, ?>> entry : extendedManagedType.getOwnedSingularAttributes().entrySet()) {
ExtendedAttribute<?, ?> extendedAttribute = entry.getValue();
SingularAttribute<?, ?> attribute = (SingularAttribute<?, ?>) extendedAttribute.getAttribute();
if (!attribute.isVersion() && !attribute.isOptional()) {
// The attribute could be the id attribute of an owned *ToOne association
if ((attribute.getType() instanceof BasicType<?> || attribute.getType() instanceof EmbeddableType<?>) && extendedAttribute.getAttributePath().size() > 1) {
List<Attribute<?, ?>> attributePath = extendedAttribute.getAttributePath();
// So we check the *ToOne attribute instead
for (int i = attributePath.size() - 2; i >= 0; i--) {
SingularAttribute<?, ?> superAttribute = (SingularAttribute<?, ?>) attributePath.get(i);
if (superAttribute.getType() instanceof EntityType<?>) {
// If it is optional, the attribute isn't optional
// Otherwise break to add it to the required set
if (superAttribute.isOptional()) {
continue OUTER;
} else {
break;
}
}
}
}
requiredUpdatableAttributes.add(entry.getKey());
}
}
} else {
requiredUpdatableAttributes = Collections.emptySet();
mappedColumns = Collections.emptySet();
}

for (String excludedEntityAttribute : excludedEntityAttributes) {
removeRequiredUpdatableAttribute(requiredUpdatableAttributes, mappedColumns, extendedManagedType, excludedEntityAttribute);
}

for (MethodAttributeMapping mapping : viewMapping.getMethodAttributes().values()) {
AbstractMethodAttribute<? super X, ?> attribute;
if (mapping.isId() || mapping.isVersion()) {
Expand All @@ -154,6 +202,9 @@ public ManagedViewTypeImpl(ViewMapping viewMapping, ManagedType<?> managedType,
} else {
attribute = mapping.getMethodAttribute(this, index, -1, context, embeddableMapping);
}
if (!requiredUpdatableAttributes.isEmpty()) {
removeRequiredUpdatableAttribute(requiredUpdatableAttributes, mappedColumns, extendedManagedType, attribute);
}
} else {
// Note that the dirty state index is only a "suggested" index, but the implementation can choose not to use it
attribute = mapping.getMethodAttribute(this, index, dirtyStateIndex, context, embeddableMapping);
Expand All @@ -162,6 +213,11 @@ public ManagedViewTypeImpl(ViewMapping viewMapping, ManagedType<?> managedType,
dirtyStateIndex++;
}
}

if (!requiredUpdatableAttributes.isEmpty() && attribute.isUpdatable() && attribute.getUpdateMappableAttribute() != null) {
removeRequiredUpdatableAttribute(requiredUpdatableAttributes, mappedColumns, extendedManagedType, attribute);
}

hasJoinFetchedCollections = hasJoinFetchedCollections || attribute.hasJoinFetchedCollections();
attributes.put(mapping.getName(), attribute);
index++;
Expand Down Expand Up @@ -246,6 +302,60 @@ public ManagedViewTypeImpl(ViewMapping viewMapping, ManagedType<?> managedType,
context.addError("Entity view type '" + javaType.getName() + "' has no @EntityViewInheritanceMapping but is used as inheritance subtype in: " + classes);
}
}

if (!requiredUpdatableAttributes.isEmpty()) {
// Before failing, remove all attribute for which we covered all columns already
for (Iterator<String> iterator = requiredUpdatableAttributes.iterator(); iterator.hasNext(); ) {
ExtendedAttribute<?, ?> extendedAttribute = extendedManagedType.getAttributes().get(iterator.next());
if (extendedAttribute == null || mappedColumns.containsAll(Arrays.asList(extendedAttribute.getColumnNames()))) {
iterator.remove();
}
}
if (!requiredUpdatableAttributes.isEmpty()) {
// A version attribute defined on a mapped super class isn't reported as version attribute apparently
if (jpaManagedType instanceof IdentifiableType<?>) {
if (((IdentifiableType<Object>) jpaManagedType).hasVersionAttribute()) {
for (Iterator<String> iterator = requiredUpdatableAttributes.iterator(); iterator.hasNext(); ) {
ExtendedAttribute<?, ?> extendedAttribute = extendedManagedType.getAttributes().get(iterator.next());
try {
SingularAttribute<? super Object, ?> version = ((IdentifiableType<Object>) jpaManagedType).getVersion(extendedAttribute.getElementClass());
if (extendedAttribute.getAttributePathString().equals(version.getName())) {
iterator.remove();
}
} catch (IllegalArgumentException ex) {
// Ignore
}
}
}
}
if (!requiredUpdatableAttributes.isEmpty()) {
context.addError("Entity view type '" + javaType.getName() + "' might not be persistable because it is missing updatable attribute definitions for non-optional entity attributes: " + requiredUpdatableAttributes + ". Add attributes, disable validation or exclude attributes if you know values are set via the entity constructor!");
}
}
}
}

private static void removeRequiredUpdatableAttribute(Set<String> requiredUpdatableAttributes, Set<String> mappedColumns, ExtendedManagedType<?> extendedManagedType, AbstractMethodAttribute<?, ?> attribute) {
removeRequiredUpdatableAttribute(requiredUpdatableAttributes, mappedColumns, extendedManagedType, attribute.getMapping());
}

private static void removeRequiredUpdatableAttribute(Set<String> requiredUpdatableAttributes, Set<String> mappedColumns, ExtendedManagedType<?> extendedManagedType, String mapping) {
requiredUpdatableAttributes.remove(mapping);
ExtendedAttribute<?, ?> extendedAttribute = extendedManagedType.getAttributes().get(mapping);
if (extendedAttribute != null) {
mappedColumns.addAll(Arrays.asList(extendedAttribute.getColumnNames()));
for (ExtendedAttribute<?, ?> columnEquivalentAttribute : extendedAttribute.getColumnEquivalentAttributes()) {
requiredUpdatableAttributes.remove(columnEquivalentAttribute.getAttributePathString());
}
if (extendedAttribute.getAttribute() instanceof SingularAttribute<?, ?>) {
SingularAttribute<?, ?> singularAttribute = (SingularAttribute<?, ?>) extendedAttribute.getAttribute();
if (singularAttribute.getType() instanceof EmbeddableType<?>) {
for (String embeddedPropertyName : JpaMetamodelUtils.getEmbeddedPropertyNames((EmbeddableType<?>) singularAttribute.getType())) {
removeRequiredUpdatableAttribute(requiredUpdatableAttributes, mappedColumns, extendedManagedType, mapping + "." + embeddedPropertyName);
}
}
}
}
}

@Override
Expand Down
Expand Up @@ -32,7 +32,7 @@
* @author Christian Beikov
* @since 1.2.0
*/
@CreatableEntityView
@CreatableEntityView(validatePersistability = false)
@UpdatableEntityView
@EntityView(Document.class)
public abstract class DocumentCreateView implements DocumentInterfaceView {
Expand Down
Expand Up @@ -354,7 +354,7 @@ public void updatableMutableAndCreatableView() {
assertTrue(docViewType.getAttribute("people").isMutable());
}

@CreatableEntityView
@CreatableEntityView(excludedEntityAttributes = {"name", "idx", "age"})
@EntityView(Document.class)
public static interface DocumentViewWithUpdatableCreatableViewTypes {
@IdMapping
Expand Down
Expand Up @@ -23,7 +23,7 @@
* @author Christian Beikov
* @since 1.2.0
*/
@CreatableEntityView
@CreatableEntityView(validatePersistability = false)
public interface FriendPersonCreateView extends FriendPersonView {

}
Expand Up @@ -31,7 +31,7 @@
* @author Christian Beikov
* @since 1.2.0
*/
@CreatableEntityView
@CreatableEntityView(validatePersistability = false)
@UpdatableEntityView
@EntityView(Document.class)
public interface UpdatableDocumentView extends DocumentIdView {
Expand Down
Expand Up @@ -29,7 +29,7 @@
* @author Christian Beikov
* @since 1.2.0
*/
@CreatableEntityView
@CreatableEntityView(validatePersistability = false)
@UpdatableEntityView
@EntityView(Person.class)
public interface UpdatablePersonView extends PersonIdView {
Expand Down
Expand Up @@ -26,7 +26,7 @@
* @author Christian Beikov
* @since 1.2.0
*/
@CreatableEntityView
@CreatableEntityView(validatePersistability = false)
@UpdatableEntityView
@EntityView(Version.class)
public interface UpdatableVersionView extends VersionIdView {
Expand Down

0 comments on commit 275b645

Please sign in to comment.