/*
* Copyright 2014 - 2017 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.impl.metamodel;
import com.blazebit.persistence.impl.expression.SyntaxErrorException;
import com.blazebit.persistence.view.BatchFetch;
import com.blazebit.persistence.view.CorrelationProvider;
import com.blazebit.persistence.view.FetchStrategy;
import com.blazebit.persistence.view.IdMapping;
import com.blazebit.persistence.view.Mapping;
import com.blazebit.persistence.view.MappingCorrelated;
import com.blazebit.persistence.view.MappingCorrelatedSimple;
import com.blazebit.persistence.view.MappingParameter;
import com.blazebit.persistence.view.MappingSubquery;
import com.blazebit.persistence.view.SubqueryProvider;
import com.blazebit.persistence.view.impl.CollectionJoinMappingGathererExpressionVisitor;
import com.blazebit.persistence.view.impl.CorrelationProviderHelper;
import com.blazebit.persistence.view.impl.ScalarTargetResolvingExpressionVisitor;
import com.blazebit.persistence.view.impl.ScalarTargetResolvingExpressionVisitor.TargetType;
import com.blazebit.persistence.view.impl.UpdatableExpressionVisitor;
import com.blazebit.persistence.view.metamodel.Attribute;
import com.blazebit.persistence.view.metamodel.PluralAttribute;
import com.blazebit.persistence.view.metamodel.Type;
import com.blazebit.reflection.ReflectionUtils;
import javax.persistence.metamodel.ManagedType;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
*
* @author Christian Beikov
* @since 1.0
*/
public abstract class AbstractAttribute<X, Y> implements Attribute<X, Y> {
private static final String[] EMPTY = new String[0];
private static final String THIS = "this";
private static final Pattern PREFIX_THIS_REPLACE_PATTERN = Pattern.compile("([^a-zA-Z0-9\\.])this\\.");
protected final ManagedViewTypeImpl<X> declaringType;
protected final Class<Y> javaType;
protected final String mapping;
protected final String[] fetches;
protected final FetchStrategy fetchStrategy;
protected final int batchSize;
protected final Class<? extends SubqueryProvider> subqueryProvider;
protected final String subqueryExpression;
protected final String subqueryAlias;
protected final Class<? extends CorrelationProvider> correlationProvider;
protected final String correlationBasis;
protected final String correlationResult;
protected final Class<?> correlated;
protected final String correlationKeyAlias;
protected final String correlationExpression;
protected final MappingType mappingType;
protected final boolean id;
@SuppressWarnings("unchecked")
public AbstractAttribute(ManagedViewTypeImpl<X> declaringType, AttributeMapping mapping, MetamodelBuildingContext context) {
if (mapping.getJavaType() == null) {
context.addError("The attribute type is not resolvable " + mapping.getErrorLocation());
}
BatchFetch batchFetch = mapping.getBatchFetch();
int batchSize;
if (batchFetch == null || batchFetch.size() == -1) {
batchSize = -1;
} else if (batchFetch.size() < 1) {
context.addError("Illegal batch fetch size lower than 1 defined at '" + mapping.getErrorLocation() + "'!");
batchSize = Integer.MIN_VALUE;
} else {
batchSize = batchFetch.size();
}
this.declaringType = declaringType;
this.javaType = (Class<Y>) mapping.getJavaType();
Annotation mappingAnnotation = mapping.getMapping();
if (mappingAnnotation instanceof IdMapping) {
this.mapping = ((IdMapping) mappingAnnotation).value();
this.fetches = EMPTY;
this.fetchStrategy = FetchStrategy.JOIN;
this.batchSize = -1;
this.subqueryProvider = null;
this.id = true;
this.mappingType = MappingType.BASIC;
this.subqueryExpression = null;
this.subqueryAlias = null;
this.correlationBasis = null;
this.correlationResult = null;
this.correlationProvider = null;
this.correlated = null;
this.correlationKeyAlias = null;
this.correlationExpression = null;
} else if (mappingAnnotation instanceof Mapping) {
Mapping m = (Mapping) mappingAnnotation;
this.mapping = m.value();
this.fetches = m.fetches();
this.fetchStrategy = m.fetch();
this.batchSize = batchSize;
this.subqueryProvider = null;
this.id = false;
this.mappingType = MappingType.BASIC;
this.subqueryExpression = null;
this.subqueryAlias = null;
this.correlationBasis = null;
this.correlationResult = null;
this.correlationProvider = null;
this.correlated = null;
this.correlationKeyAlias = null;
this.correlationExpression = null;
} else if (mappingAnnotation instanceof MappingParameter) {
this.mapping = ((MappingParameter) mappingAnnotation).value();
this.fetches = EMPTY;
this.fetchStrategy = FetchStrategy.JOIN;
this.batchSize = -1;
this.subqueryProvider = null;
this.id = false;
this.mappingType = MappingType.PARAMETER;
this.subqueryExpression = null;
this.subqueryAlias = null;
this.correlationBasis = null;
this.correlationResult = null;
this.correlationProvider = null;
this.correlated = null;
this.correlationKeyAlias = null;
this.correlationExpression = null;
} else if (mappingAnnotation instanceof MappingSubquery) {
MappingSubquery mappingSubquery = (MappingSubquery) mappingAnnotation;
this.mapping = null;
this.fetches = EMPTY;
this.subqueryProvider = mappingSubquery.value();
this.fetchStrategy = FetchStrategy.JOIN;
this.batchSize = -1;
this.id = false;
this.mappingType = MappingType.SUBQUERY;
this.subqueryExpression = mappingSubquery.expression();
this.subqueryAlias = mappingSubquery.subqueryAlias();
this.correlationBasis = null;
this.correlationResult = null;
this.correlationProvider = null;
this.correlated = null;
this.correlationKeyAlias = null;
this.correlationExpression = null;
if (!subqueryExpression.isEmpty() && subqueryAlias.isEmpty()) {
context.addError("The subquery alias is empty although the subquery expression is not " + mapping.getErrorLocation());
}
if (subqueryProvider.getEnclosingClass() != null && !Modifier.isStatic(subqueryProvider.getModifiers())) {
context.addError("The subquery provider is defined as non-static inner class. Make it static, otherwise it can't be instantiated: " + mapping.getErrorLocation());
}
} else if (mappingAnnotation instanceof MappingCorrelated) {
MappingCorrelated mappingCorrelated = (MappingCorrelated) mappingAnnotation;
this.mapping = null;
this.fetches = mappingCorrelated.fetches();
this.fetchStrategy = mappingCorrelated.fetch();
if (fetchStrategy == FetchStrategy.SELECT) {
this.batchSize = batchSize;
} else {
this.batchSize = -1;
}
this.subqueryProvider = null;
this.id = false;
this.mappingType = MappingType.CORRELATED;
this.subqueryExpression = null;
this.subqueryAlias = null;
this.correlationBasis = mappingCorrelated.correlationBasis();
this.correlationResult = mappingCorrelated.correlationResult();
this.correlationProvider = mappingCorrelated.correlator();
this.correlated = null;
this.correlationKeyAlias = null;
this.correlationExpression = null;
if (correlationProvider.getEnclosingClass() != null && !Modifier.isStatic(correlationProvider.getModifiers())) {
context.addError("The correlation provider is defined as non-static inner class. Make it static, otherwise it can't be instantiated: " + mapping.getErrorLocation());
}
} else if (mappingAnnotation instanceof MappingCorrelatedSimple) {
MappingCorrelatedSimple mappingCorrelated = (MappingCorrelatedSimple) mappingAnnotation;
this.mapping = null;
this.fetches = mappingCorrelated.fetches();
this.fetchStrategy = mappingCorrelated.fetch();
if (fetchStrategy == FetchStrategy.SELECT) {
this.batchSize = batchSize;
} else {
this.batchSize = -1;
}
this.subqueryProvider = null;
this.id = false;
this.mappingType = MappingType.CORRELATED;
this.subqueryExpression = null;
this.subqueryAlias = null;
this.correlationProvider = CorrelationProviderHelper.createCorrelationProvider(mappingCorrelated.correlated(), mappingCorrelated.correlationKeyAlias(), mappingCorrelated.correlationExpression(), context);
this.correlationBasis = mappingCorrelated.correlationBasis();
this.correlationResult = mappingCorrelated.correlationResult();
this.correlated = mappingCorrelated.correlated();
this.correlationKeyAlias = mappingCorrelated.correlationKeyAlias();
this.correlationExpression = mappingCorrelated.correlationExpression();
if (mappingCorrelated.correlationBasis().isEmpty()) {
context.addError("Illegal empty correlation basis in the " + getLocation());
}
} else {
context.addError("No mapping annotation could be found " + mapping.getErrorLocation());
this.mapping = null;
this.fetches = EMPTY;
this.fetchStrategy = null;
this.batchSize = Integer.MIN_VALUE;
this.subqueryProvider = null;
this.id = false;
this.mappingType = null;
this.subqueryExpression = null;
this.subqueryAlias = null;
this.correlationBasis = null;
this.correlationResult = null;
this.correlationProvider = null;
this.correlated = null;
this.correlationKeyAlias = null;
this.correlationExpression = null;
}
}
public static String stripThisFromMapping(String mapping) {
return replaceThisFromMapping(mapping, "");
}
public static String replaceThisFromMapping(String mapping, String root) {
if (mapping == null) {
return null;
}
mapping = mapping.trim();
if (mapping.startsWith(THIS)) {
// Special case when the mapping start with "this"
if (mapping.length() == THIS.length()) {
// Return the empty string if it essentially equals "this"
return root;
}
if (root.isEmpty()) {
char nextChar = mapping.charAt(THIS.length());
if (nextChar == '.') {
// Only replace if it isn't a prefix
mapping = mapping.substring(THIS.length() + 1);
}
} else {
mapping = root + mapping.substring(THIS.length());
}
}
String replacement;
if (root.isEmpty()) {
replacement = "$1";
} else {
replacement = "$1" + root + ".";
}
mapping = PREFIX_THIS_REPLACE_PATTERN.matcher(mapping)
.replaceAll(replacement);
return mapping;
}
/**
* Collects all mappings that involve the use of a collection attribute for duplicate usage checks.
*
* @param managedType The JPA type against which to evaluate the mapping
* @param context The metamodel context
* @return The mappings which contain collection attribute uses
*/
public Set<String> getCollectionJoinMappings(ManagedType<?> managedType, MetamodelBuildingContext context) {
if (mapping == null || isQueryParameter() || getAttributeType() == AttributeType.SINGULAR) {
// Subqueries and parameters can't be checked. When a collection is remapped to a singular attribute, we don't check it
return Collections.emptySet();
}
CollectionJoinMappingGathererExpressionVisitor visitor = new CollectionJoinMappingGathererExpressionVisitor(managedType, context.getEntityMetamodel());
String expression = stripThisFromMapping(mapping);
if (expression.isEmpty()) {
return Collections.emptySet();
}
context.getExpressionFactory().createSimpleExpression(expression, false).accept(visitor);
Set<String> mappings = new HashSet<String>();
for (String s : visitor.getPaths()) {
mappings.add(s);
}
return mappings;
}
public boolean hasJoinFetchedCollections() {
return isCollection() && getFetchStrategy() == FetchStrategy.JOIN
|| getElementType() instanceof ManagedViewTypeImpl<?> && ((ManagedViewTypeImpl<?>) getElementType()).hasJoinFetchedCollections()
|| getKeyType() instanceof ManagedViewTypeImpl<?> && ((ManagedViewTypeImpl<?>) getKeyType()).hasJoinFetchedCollections();
}
private static enum ExpressionLocation {
MAPPING("mapping expression"),
CORRELATION_BASIS("correlation basis"),
CORRELATION_RESULT("correlation result"),
CORRELATION_EXPRESSION("correlation expression");
private final String location;
ExpressionLocation(String location) {
this.location = location;
}
@Override
public String toString() {
return location;
}
}
private static boolean isCompatible(TargetType t, Class<?> targetType, Class<?> targetElementType, boolean subtypesAllowed) {
if (t.hasCollectionJoin()) {
return isCompatible(t.getLeafBaseClass(), t.getLeafBaseValueClass(), targetType, targetElementType, subtypesAllowed);
} else {
return isCompatible(t.getLeafBaseClass(), null, targetType, targetElementType, subtypesAllowed);
}
}
/**
* Checks if <code>possibleTargetType</code> with an optional element type <code>possibleTargetElementType</code>
* can be mapped to <code>targetType</code> with the optional element type <code>targetElementType</code> and the given <code>subtypesAllowed</code> config.
*
* A <code>possibleTargetType</code> of <code>NULL</code> represents the <i>any type</i> which makes it always compatible i.e. returning <code>true</code>.
* A type is compatible if the source types given by <code>possibleTargetType</code>/<code>possibleTargetElementType</code>
* are subtypes of the target types <code>targetType</code>/<code>targetElementType</code>.
*
* A source collection type it is also compatible with non-collection targets if the source element type is a subtype of the target type.
* A source non-collection type is also compatible with a collection target if the source type is a subtype of the target element type.
*
* @param possibleTargetType The source type
* @param possibleTargetElementType The optional source element type
* @param targetType The target type
* @param targetElementType The optional target element type
* @param subtypesAllowed Whether a more specific source type is allowed to map to a general target type
* @return True if mapping from <code>possibleTargetType</code>/<code>possibleTargetElementType</code> to <code>targetType</code>/<code>targetElementType</code> is possible
*/
private static boolean isCompatible(Class<?> possibleTargetType, Class<?> possibleTargetElementType, Class<?> targetType, Class<?> targetElementType, boolean subtypesAllowed) {
// Null is the marker for ANY TYPE
if (possibleTargetType == null) {
return true;
}
if (subtypesAllowed) {
if (possibleTargetElementType != null) {
if (targetElementType != null) {
// Mapping a plural entity attribute to a plural view attribute
// Either possibleTargetType is a subtype of target type, or it is a subtype of map and the target type a subtype of Collection
// This allows mapping Map<?, Entity> to List<Subview>
// Anyway the possibleTargetElementType must be a subtype of the targetElementType
return (targetType.isAssignableFrom(possibleTargetType) || Map.class.isAssignableFrom(possibleTargetType) && Collection.class.isAssignableFrom(targetType))
&& targetElementType.isAssignableFrom(possibleTargetElementType);
} else {
// Mapping a plural entity attribute to a singular view attribute
return targetType.isAssignableFrom(possibleTargetElementType);
}
} else {
if (targetElementType != null) {
// Mapping a singular entity attribute to a plural view attribute
return targetElementType.isAssignableFrom(possibleTargetType);
} else {
// Mapping a singular entity attribute to a singular view attribute
return targetType.isAssignableFrom(possibleTargetType);
}
}
} else {
if (possibleTargetElementType != null) {
if (targetElementType != null) {
// Mapping a plural entity attribute to a plural view attribute
return targetType == possibleTargetType
&& targetElementType == possibleTargetElementType;
} else {
// Mapping a plural entity attribute to a singular view attribute
return targetType == possibleTargetElementType;
}
} else {
if (targetElementType != null) {
// Mapping a singular entity attribute to a plural view attribute
return targetElementType == possibleTargetType;
} else {
// Mapping a singular entity attribute to a singular view attribute
return targetType == possibleTargetType;
}
}
}
}
private static void validateTypesCompatible(ManagedType<?> managedType, String expression, Class<?> targetType, Class<?> targetElementType, boolean subtypesAllowed, MetamodelBuildingContext context, ExpressionLocation expressionLocation, String location) {
final Class<?> expressionType = targetType;
if (expression.isEmpty()) {
if (isCompatible(managedType.getJavaType(), null, targetType, targetElementType, subtypesAllowed)) {
return;
}
context.addError(typeCompatibilityError(
Arrays.<TargetType>asList(new ScalarTargetResolvingExpressionVisitor.TargetTypeImpl(
false, null, managedType.getJavaType(), null
)),
expressionType,
targetElementType,
expressionLocation,
location
));
return;
}
ScalarTargetResolvingExpressionVisitor visitor = new ScalarTargetResolvingExpressionVisitor(managedType, context.getEntityMetamodel(), context.getJpqlFunctions());
try {
context.getExpressionFactory().createSimpleExpression(expression, false).accept(visitor);
} catch (SyntaxErrorException ex) {
context.addError("Syntax error in " + expressionLocation + " '" + expression + "' of the " + location + ": " + ex.getMessage());
} catch (IllegalArgumentException ex) {
context.addError("An error occurred while trying to resolve the " + expressionLocation + " of the " + location + ": " + ex.getMessage());
}
List<TargetType> possibleTargets = visitor.getPossibleTargets();
if (!possibleTargets.isEmpty()) {
boolean error = true;
for (TargetType t : possibleTargets) {
if (isCompatible(t, targetType, targetElementType, subtypesAllowed)) {
error = false;
break;
}
}
if (error) {
if (targetType.isPrimitive()) {
targetType = ReflectionUtils.getObjectClassOfPrimitve(targetType);
} else {
targetType = ReflectionUtils.getPrimitiveClassOfWrapper(targetType);
}
if (targetType != null) {
for (TargetType t : possibleTargets) {
if (isCompatible(t, targetType, targetElementType, subtypesAllowed)) {
error = false;
break;
}
}
}
}
if (error) {
context.addError(typeCompatibilityError(possibleTargets, expressionType, targetElementType, expressionLocation, location));
}
}
}
private static String typeCompatibilityError(List<TargetType> possibleTargets, Class<?> targetType, Class<?> targetElementType, ExpressionLocation expressionLocation, String location) {
StringBuilder sb = new StringBuilder();
sb.append("The resolved possible types ");
sb.append('[');
for (TargetType t : possibleTargets) {
sb.append(t.getLeafBaseClass().getName());
if (t.getLeafBaseValueClass() != null && t.getLeafBaseClass() != t.getLeafBaseValueClass()) {
sb.append('<');
sb.append(t.getLeafBaseValueClass().getName());
sb.append('>');
}
sb.append(", ");
}
sb.setLength(sb.length() - 2);
sb.append(']');
sb.append(" are not assignable to the given expression type '");
sb.append(targetType.getName());
if (targetElementType != null && targetElementType != targetType) {
sb.append('<');
sb.append(targetElementType.getName());
sb.append('>');
}
sb.append("' of the ");
sb.append(expressionLocation);
sb.append(" declared by the ");
sb.append(location);
sb.append("!");
return sb.toString();
}
public void checkAttribute(ManagedType<?> managedType, MetamodelBuildingContext context) {
Class<?> expressionType = getJavaType();
Class<?> keyType = null;
Class<?> elementType = null;
if (fetches.length != 0) {
ManagedType<?> entityType = context.getEntityMetamodel().getManagedType(getElementType().getJavaType());
if (entityType == null) {
context.addError("Specifying fetches for non-entity attribute type [" + Arrays.toString(fetches) + "] at the " + getLocation() + " is not allowed!");
} else {
ScalarTargetResolvingExpressionVisitor visitor = new ScalarTargetResolvingExpressionVisitor(entityType, context.getEntityMetamodel(), context.getJpqlFunctions());
for (int i = 0; i < fetches.length; i++) {
final String fetch = fetches[i];
final String errorLocation;
if (fetches.length == 1) {
errorLocation = "the fetch expression";
} else {
errorLocation = "the " + (i + 1) + ". fetch expression";
}
visitor.clear();
try {
// Validate the fetch expression parses
context.getExpressionFactory().createPathExpression(fetch).accept(visitor);
} catch (SyntaxErrorException ex) {
context.addError("Syntax error in " + errorLocation + " '" + fetch + "' of the " + getLocation() + ": " + ex.getMessage());
} catch (IllegalArgumentException ex) {
context.addError("An error occurred while trying to resolve the " + errorLocation + " '" + fetch + "' of the " + getLocation() + ": " + ex.getMessage());
}
}
}
}
// TODO: key fetches?
if (isCollection()) {
elementType = getElementType().getJavaType();
if (isUpdatable()) {
// Updatable collection attributes currently must have the same collection type
} else {
if (isIndexed()) {
if (getCollectionType() == PluralAttribute.CollectionType.MAP) {
// All map types can be sourced from a map
expressionType = Map.class;
keyType = getKeyType().getJavaType();
} else {
// An indexed list can only be sourced from an indexed list
expressionType = List.class;
keyType = Integer.class;
}
} else {
// We can assign e.g. a Set to a List, so let's use the common supertype
expressionType = Collection.class;
}
}
}
if (isSubview()) {
ManagedViewTypeImpl<?> subviewType = (ManagedViewTypeImpl<?>) getElementType();
if (isCollection()) {
elementType = subviewType.getEntityClass();
} else {
expressionType = subviewType.getEntityClass();
}
}
if (isKeySubview()) {
keyType = ((ManagedViewTypeImpl<?>) getKeyType()).getEntityClass();
}
// TODO: Make use of the key type in type checks
if (isCorrelated()) {
if (isUpdatable()) {
context.addError("Illegal updatable correlated attribute " + getLocation());
}
// Validate that resolving "correlationBasis" on "managedType" is valid
validateTypesCompatible(managedType, stripThisFromMapping(correlationBasis), Object.class, null, true, context, ExpressionLocation.CORRELATION_BASIS, getLocation());
if (correlated != null) {
// Validate that resolving "correlationResult" on "correlated" is compatible with "expressionType" and "elementType"
validateTypesCompatible(context.getEntityMetamodel().managedType(correlated), stripThisFromMapping(correlationResult), expressionType, elementType, true, context, ExpressionLocation.CORRELATION_RESULT, getLocation());
// TODO: Validate the "correlationExpression" when https://github.com/Blazebit/blaze-persistence/issues/212 is implemented
try {
// Validate the expression parses
context.createMacroAwareExpressionFactory().createBooleanExpression(correlationExpression, false);
} catch (SyntaxErrorException ex) {
context.addError("Syntax error in " + ExpressionLocation.CORRELATION_EXPRESSION + " '" + correlationExpression + "' of the " + getLocation() + ": " + ex.getMessage());
} catch (IllegalArgumentException ex) {
context.addError("An error occurred while trying to resolve the " + ExpressionLocation.CORRELATION_EXPRESSION + " of the " + getLocation() + ": " + ex.getMessage());
}
}
} else if (isSubquery() || isQueryParameter()) {
// Subqueries and parameters can't be checked
} else {
boolean subtypesAllowed = !isUpdatable();
// Forcing singular via @MappingSingular
if (!isCollection() && Collection.class.isAssignableFrom(expressionType)) {
Class<?>[] typeArguments = getTypeArguments();
elementType = typeArguments[typeArguments.length - 1];
}
String mapping = stripThisFromMapping(this.mapping);
// Validate that resolving "mapping" on "managedType" is compatible with "expressionType" and "elementType"
validateTypesCompatible(managedType, mapping, expressionType, elementType, subtypesAllowed, context, ExpressionLocation.MAPPING, getLocation());
if (isUpdatable() && declaringType.isUpdatable()) {
UpdatableExpressionVisitor visitor = new UpdatableExpressionVisitor(managedType.getJavaType());
try {
// NOTE: Not supporting "this" here because it doesn't make sense to have an updatable mapping that refers to this
// The only thing that might be interesting is supporting "this" when we support cascading as properties could be nested
// But not sure yet if the embeddable attributes would then be modeled as "updatable".
// I guess these attributes are not "updatable" but that probably depends on the decision regarding collections as they have a similar problem
// A collection itself might not be "updatable" but it's elements could be. This is roughly the same problem
context.getExpressionFactory().createPathExpression(mapping).accept(visitor);
Map<Method, Class<?>[]> possibleTargets = visitor.getPossibleTargets();
if (possibleTargets.size() > 1) {
context.addError("Multiple possible target type for the mapping in the " + getLocation() + ": " + possibleTargets);
}
} catch (SyntaxErrorException ex) {
context.addError("Syntax error in mapping expression '" + mapping + "' of the " + getLocation() + ": " + ex.getMessage());
} catch (IllegalArgumentException ex) {
context.addError("There is an error for the " + getLocation() + ": " + ex.getMessage());
}
}
}
}
public void checkNestedAttribute(List<AbstractAttribute<?, ?>> parents, ManagedType<?> managedType, MetamodelBuildingContext context) {
if (!parents.isEmpty()) {
if (getDeclaringType().getMappingType() == Type.MappingType.FLAT_VIEW) {
// When this attribute is part of a flat view
if (isCollection() && getFetchStrategy() == FetchStrategy.JOIN) {
// And is a join fetched collection
// Traverse up and check if it has at least one non-embedded parent
for (int i = parents.size() - 1; i >= 0; i--) {
AbstractAttribute<?, ?> parentAttribute = parents.get(i);
if (parentAttribute.getDeclaringType().getMappingType() == Type.MappingType.FLAT_VIEW && !parentAttribute.isEmbedded()) {
String path = parentAttribute.getDeclaringType().getJavaType().getName();
for (i = i + 1; i < parents.size(); i++) {
path += " > " + parents.get(i).getLocation();
}
context.addError("Illegal mapping of join fetched collection in the " + getLocation() + ". The flat view '" + getJavaType().getName() + "' was via the path: " + path);
break;
}
}
}
}
}
// Go into subtypes for nested checking
if (isSubview()) {
Map<ManagedViewTypeImpl<?>, String> inheritanceSubtypeMappings = elementInheritanceSubtypeMappings();
if (inheritanceSubtypeMappings.isEmpty()) {
context.addError("Illegal empty inheritance subtype mappings for the " + getLocation() + ". Remove the @MappingInheritance annotation, set the 'onlySubtypes' attribute to false or add a @MappingInheritanceSubtype element!");
}
for (ManagedViewTypeImpl<?> subviewType : inheritanceSubtypeMappings.keySet()) {
parents.add(this);
subviewType.checkNestedAttributes(parents, context);
parents.remove(parents.size() - 1);
}
}
if (isKeySubview()) {
Map<ManagedViewTypeImpl<?>, String> inheritanceSubtypeMappings = keyInheritanceSubtypeMappings();
if (inheritanceSubtypeMappings.isEmpty()) {
context.addError("Illegal empty inheritance subtype mappings for the " + getLocation() + ". Remove the @MappingInheritance annotation, set the 'onlySubtypes' attribute to false or add a @MappingInheritanceSubtype element!");
}
for (ManagedViewTypeImpl<?> subviewType : inheritanceSubtypeMappings.keySet()) {
parents.add(this);
subviewType.checkNestedAttributes(parents, context);
parents.remove(parents.size() - 1);
}
}
}
protected boolean isEmbedded() {
return getDeclaringType().getMappingType() == Type.MappingType.FLAT_VIEW && "this".equals(mapping);
}
protected abstract Class[] getTypeArguments();
protected abstract String getLocation();
protected abstract boolean isUpdatable();
protected abstract boolean isIndexed();
protected abstract PluralAttribute.CollectionType getCollectionType();
protected abstract Type<?> getElementType();
protected abstract Map<ManagedViewTypeImpl<?>, String> elementInheritanceSubtypeMappings();
protected abstract Type<?> getKeyType();
protected abstract Map<ManagedViewTypeImpl<?>, String> keyInheritanceSubtypeMappings();
protected abstract boolean isKeySubview();
@Override
public final MappingType getMappingType() {
return mappingType;
}
public final boolean isQueryParameter() {
return mappingType == MappingType.PARAMETER;
}
public final boolean isId() {
return id;
}
public final Class<? extends SubqueryProvider> getSubqueryProvider() {
return subqueryProvider;
}
public final String getSubqueryExpression() {
return subqueryExpression;
}
public final String getSubqueryAlias() {
return subqueryAlias;
}
public final Class<? extends CorrelationProvider> getCorrelationProvider() {
return correlationProvider;
}
public final String getCorrelationBasis() {
return correlationBasis;
}
public final String getCorrelationResult() {
return correlationResult;
}
public final FetchStrategy getFetchStrategy() {
return fetchStrategy;
}
public final int getBatchSize() {
return batchSize;
}
public final String getMapping() {
return mapping;
}
@Override
public final boolean isSubquery() {
return mappingType == MappingType.SUBQUERY;
}
@Override
public final ManagedViewTypeImpl<X> getDeclaringType() {
return declaringType;
}
@Override
public final Class<Y> getJavaType() {
return javaType;
}
@Override
public final String[] getFetches() {
return fetches;
}
}