Skip to content

Commit

Permalink
Support 'hidden' flag for analytic models and fields. (#2357)
Browse files Browse the repository at this point in the history
* Created test to reproduce the issue

* Added more logic to hide tables and columns

* Added code to exclude hidden fields.

* More fixes.  Agg store almost builds

* Small change

* Filtering out what is bound by the aggregation store

* Added back excluded entities to entity dictionary

* Build passes

* Hidden Dimension metadata test working.

* Added hidden table test

* Added another hidden column test

* Added aggregation store integration tests for hidden columsn and tables

* Minor cleanup

* Fixed a few more issues and add more UTs

* Finished testing.

Co-authored-by: Aaron Klish <[email protected]>
  • Loading branch information
aklish and Aaron Klish authored Oct 20, 2021
1 parent 40481be commit 830b9f2
Show file tree
Hide file tree
Showing 33 changed files with 597 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ public class EntityBinding {
public final ConcurrentHashMap<Method, Boolean> requestScopeableMethods = new ConcurrentHashMap<>();
public final ConcurrentHashMap<AccessibleObject, Set<ArgumentType>> attributeArguments = new ConcurrentHashMap<>();
public final ConcurrentHashMap<String, ArgumentType> entityArguments = new ConcurrentHashMap<>();

public final ConcurrentHashMap<Object, Annotation> annotations = new ConcurrentHashMap<>();

public static final EntityBinding EMPTY_BINDING = new EntityBinding();
Expand Down Expand Up @@ -151,7 +150,7 @@ private EntityBinding() {
public EntityBinding(Injector injector,
Type<?> cls,
String type) {
this(injector, cls, type, NO_VERSION, new HashSet<>());
this(injector, cls, type, NO_VERSION, unused -> false);
}

/**
Expand All @@ -161,14 +160,14 @@ public EntityBinding(Injector injector,
* @param cls Entity class
* @param type Declared Elide type name
* @param apiVersion API version
* @param hiddenAnnotations Annotations for hiding a field in API
* @param isFieldHidden Function which determines if a given field should be in the dictionary but not exposed.
*/
public EntityBinding(Injector injector,
Type<?> cls,
String type,
String apiVersion,
Set<Class<? extends Annotation>> hiddenAnnotations) {
this(injector, cls, type, apiVersion, true, hiddenAnnotations);
Predicate<AccessibleObject> isFieldHidden) {
this(injector, cls, type, apiVersion, true, isFieldHidden);
}

/**
Expand All @@ -179,14 +178,14 @@ public EntityBinding(Injector injector,
* @param type Declared Elide type name
* @param apiVersion API version
* @param isElideModel Whether or not this type is an Elide model or not.
* @param hiddenAnnotations Annotations for hiding a field in API
* @param isFieldHidden Function which determines if a given field should be in the dictionary but not exposed.
*/
public EntityBinding(Injector injector,
Type<?> cls,
String type,
String apiVersion,
boolean isElideModel,
Set<Class<? extends Annotation>> hiddenAnnotations) {
Predicate<AccessibleObject> isFieldHidden) {
this.isElideModel = isElideModel;
this.injector = injector;
entityClass = cls;
Expand Down Expand Up @@ -225,7 +224,7 @@ public EntityBinding(Injector injector,
fieldOrMethodList.addAll(getInstanceMembers(cls.getMethods()));
}

bindEntityFields(cls, type, fieldOrMethodList, hiddenAnnotations);
bindEntityFields(cls, type, fieldOrMethodList, isFieldHidden);
bindTriggerIfPresent();

apiAttributes = dequeToList(attributesDeque);
Expand Down Expand Up @@ -291,11 +290,11 @@ public List<AccessibleObject> getAllMethods() {
* @param cls Class type to bind fields
* @param type JSON API type identifier
* @param fieldOrMethodList List of fields and methods on entity
* @param hiddenAnnotations Annotations for hiding a field in API
* @param isFieldHidden Function which determines if a given field should be in the dictionary but not exposed.
*/
private void bindEntityFields(Type<?> cls, String type,
Collection<AccessibleObject> fieldOrMethodList,
Set<Class<? extends Annotation>> hiddenAnnotations) {
Predicate<AccessibleObject> isFieldHidden) {
for (AccessibleObject fieldOrMethod : fieldOrMethodList) {
bindTriggerIfPresent(fieldOrMethod);

Expand All @@ -319,7 +318,7 @@ private void bindEntityFields(Type<?> cls, String type,
}
bindAttrOrRelation(
fieldOrMethod,
hiddenAnnotations.stream().anyMatch(fieldOrMethod::isAnnotationPresent));
isFieldHidden.test(fieldOrMethod));
}
}
}
Expand Down Expand Up @@ -735,6 +734,15 @@ public Set<Type<?>> getAttributes() {
.collect(Collectors.toSet());
}

/**
* Returns a list of fields filtered by a given predicate.
* @param filter The filter predicate.
* @return All fields that satisfy the predicate.
*/
public Set<AccessibleObject> getAllFields(Predicate<AccessibleObject> filter) {
return fieldsToValues.values().stream().filter(filter).collect(Collectors.toSet());
}

/**
* Returns the Collection of all attributes of an Entity.
* @return A Set of ArgumentType for the given entity.
Expand All @@ -743,7 +751,7 @@ public Set<ArgumentType> getEntityArguments() {
return new HashSet<>(entityArguments.values());
}

private static boolean isIdField(AccessibleObject field) {
public static boolean isIdField(AccessibleObject field) {
return (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(EmbeddedId.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
Expand Down Expand Up @@ -657,12 +658,12 @@ public boolean isMethodRequestScopeable(Type<?> entityClass, Method method) {
}

/**
* Get a list of all fields including both relationships and attributes.
* Get a list of all fields including both relationships and attributes (but excluding hidden fields).
*
* @param entityClass entity name
* @return List of all fields.
* @return List of all exposed fields.
*/
public List<String> getAllFields(Type<?> entityClass) {
public List<String> getAllExposedFields(Type<?> entityClass) {
List<String> fields = new ArrayList<>();

List<String> attrs = getAttributes(entityClass);
Expand All @@ -685,8 +686,8 @@ public List<String> getAllFields(Type<?> entityClass) {
* @param entity entity
* @return List of all fields.
*/
public List<String> getAllFields(Object entity) {
return getAllFields(getType(entity));
public List<String> getAllExposedFields(Object entity) {
return getAllExposedFields(getType(entity));
}

/**
Expand Down Expand Up @@ -965,33 +966,33 @@ public void bindEntity(Class<?> cls) {
* @param cls Entity bean class
*/
public void bindEntity(Type<?> cls) {
bindEntity(cls, new HashSet<>());
bindEntity(cls, unused -> false);
}

/**
* Add given Entity bean to dictionary.
*
* @param cls Entity bean class
* @param hiddenAnnotations Annotations for hiding a field in API
* @param isFieldHidden Function which determines if a given field should be in the dictionary but not exposed.
*/
public void bindEntity(Class<?> cls, Set<Class<? extends Annotation>> hiddenAnnotations) {
bindEntity(ClassType.of(cls), hiddenAnnotations);
public void bindEntity(Class<?> cls, Predicate<AccessibleObject> isFieldHidden) {
bindEntity(ClassType.of(cls), isFieldHidden);
}

/**
* Add given Entity bean to dictionary.
*
* @param cls Entity bean class
* @param hiddenAnnotations Annotations for hiding a field in API
* @param isFieldHidden Function which determines if a given field should be in the dictionary but not exposed.
*/
public void bindEntity(Type<?> cls, Set<Class<? extends Annotation>> hiddenAnnotations) {
if (entitiesToExclude.contains(cls)) {
public void bindEntity(Type<?> cls, Predicate<AccessibleObject> isFieldHidden) {
Type<?> declaredClass = lookupIncludeClass(cls);

if (entitiesToExclude.contains(declaredClass)) {
//Exclude Entity
return;
}

Type<?> declaredClass = lookupIncludeClass(cls);

if (declaredClass == null) {
log.trace("Missing include or excluded class {}", cls.getName());
return;
Expand All @@ -1007,7 +1008,7 @@ public void bindEntity(Type<?> cls, Set<Class<? extends Annotation>> hiddenAnnot

bindJsonApiToEntity.put(Pair.of(type, version), declaredClass);
apiVersions.add(version);
EntityBinding binding = new EntityBinding(injector, declaredClass, type, version, hiddenAnnotations);
EntityBinding binding = new EntityBinding(injector, declaredClass, type, version, isFieldHidden);
entityBindings.put(declaredClass, binding);

Include include = (Include) getFirstAnnotation(declaredClass, Arrays.asList(Include.class));
Expand Down Expand Up @@ -1467,7 +1468,7 @@ public AccessibleObject getAccessibleObject(Type<?> targetClass, String fieldNam
*/
public Set<String> getFieldsOfType(Type<?> targetClass, Type<?> targetType) {
HashSet<String> fields = new HashSet<>();
for (String field : getAllFields(targetClass)) {
for (String field : getAllExposedFields(targetClass)) {
if (getParameterizedType(targetClass, field).equals(targetType)) {
fields.add(field);
}
Expand Down Expand Up @@ -1862,7 +1863,7 @@ public <A extends Annotation> boolean attributeOrRelationAnnotationExists(
* @return {@code true} if the field exists in the entity
*/
public boolean isValidField(Type<?> cls, String fieldName) {
return getAllFields(cls).contains(fieldName);
return getAllExposedFields(cls).contains(fieldName);
}

private boolean isValidParameterizedMap(Map<?, ?> values, Class<?> keyType, Class<?> valueType) {
Expand Down Expand Up @@ -1988,7 +1989,7 @@ protected void discoverEmbeddedTypeBindings(Type<?> elideModel) {
next.getSimpleName(),
binding.getApiVersion(),
false,
new HashSet<>());
(unused) -> false);

entityBindings.put(next, nextBinding);

Expand Down Expand Up @@ -2222,14 +2223,14 @@ public EntityDictionary build() {
serdeLookup = CoerceUtil::lookup;
}

if (entitiesToExclude == null) {
entitiesToExclude = Collections.emptySet();
}

if (injector == null) {
injector = DEFAULT_INJECTOR;
}

if (entitiesToExclude == null) {
entitiesToExclude = Collections.emptySet();
}

return new EntityDictionary(
checks,
roleChecks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ private Expression buildAnyFieldExpression(final PermissionCondition condition,
Expression entityExpression = normalizedExpressionFromParseTree(classPermissions, checkFn);

OrExpression allFieldsExpression = new OrExpression(FAILURE, null);
List<String> fields = entityDictionary.getAllFields(resourceClass);
List<String> fields = entityDictionary.getAllExposedFields(resourceClass);

boolean entityExpressionUsed = false;
boolean fieldExpressionUsed = false;
Expand Down Expand Up @@ -363,7 +363,7 @@ private Expression buildAnyFieldOnlyExpression(final PermissionCondition conditi
Class<? extends Annotation> annotationClass = condition.getPermission();

OrExpression allFieldsExpression = new OrExpression(FAILURE, null);
List<String> fields = entityDictionary.getAllFields(resourceClass);
List<String> fields = entityDictionary.getAllExposedFields(resourceClass);

boolean fieldExpressionUsed = false;

Expand Down Expand Up @@ -416,7 +416,7 @@ public FilterExpression buildAnyFieldFilterExpression(
}

FilterExpression allFieldsFilterExpression = entityFilter;
List<String> fields = entityDictionary.getAllFields(forType).stream()
List<String> fields = entityDictionary.getAllExposedFields(forType).stream()
.filter(field -> requestedFields == null || requestedFields.contains(field))
.collect(Collectors.toList());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ public static boolean canPaginate(
canPaginateClass = visitor.visit(classPermissions);
}

List<String> fields = dictionary.getAllFields(resourceClass);
List<String> fields = dictionary.getAllExposedFields(resourceClass);

boolean canPaginate = true;
for (String field : fields) {
Expand Down
Loading

0 comments on commit 830b9f2

Please sign in to comment.