Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@Join and JoinPath #1185

Merged
merged 6 commits into from
Feb 14, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -677,9 +677,9 @@ public Swagger build() {
converters.addConverter(new JsonApiModelResolver(dictionary));

if (allClasses.isEmpty()) {
allClasses = dictionary.getBindings();
allClasses = dictionary.getBoundClasses();
} else {
allClasses = Sets.intersection(dictionary.getBindings(), allClasses);
allClasses = Sets.intersection(dictionary.getBoundClasses(), allClasses);
if (allClasses.isEmpty()) {
throw new IllegalArgumentException("None of the provided classes are exported by Elide");
}
Expand Down
88 changes: 71 additions & 17 deletions elide-core/src/main/java/com/yahoo/elide/core/EntityBinding.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ public class EntityBinding {
private AccessType accessType;

public final EntityPermissions entityPermissions;
public final List<String> attributes;
public final List<String> relationships;
public final List<String> apiAttributes;
public final List<String> apiRelationships;
public final List<Class<?>> inheritedTypes;
public final ConcurrentLinkedDeque<String> attributesDeque = new ConcurrentLinkedDeque<>();
public final ConcurrentLinkedDeque<String> relationshipsDeque = new ConcurrentLinkedDeque<>();
Expand All @@ -124,8 +124,8 @@ public class EntityBinding {
private EntityBinding() {
jsonApiType = null;
entityName = null;
attributes = new ArrayList<>();
relationships = new ArrayList<>();
apiAttributes = new ArrayList<>();
apiRelationships = new ArrayList<>();
inheritedTypes = new ArrayList<>();
idField = null;
idType = null;
Expand All @@ -134,7 +134,35 @@ private EntityBinding() {
idGenerated = false;
}

public EntityBinding(EntityDictionary dictionary, Class<?> cls, String type, String name) {
/**
* Constructor
*
* @param dictionary Dictionary to use
* @param cls Entity class
* @param type Declared Elide type name
* @param name Declared Entity name
*/
public EntityBinding(EntityDictionary dictionary,
Class<?> cls,
String type,
String name) {
this(dictionary, cls, type, name, new HashSet<>());
}

/**
* Constructor
*
* @param dictionary Dictionary to use
* @param cls Entity class
* @param type Declared Elide type name
* @param name Declared Entity name
* @param hiddenAnnotations Annotations for hiding a field in API
*/
public EntityBinding(EntityDictionary dictionary,
Class<?> cls,
String type,
String name,
Set<Class<? extends Annotation>> hiddenAnnotations) {
entityClass = cls;
jsonApiType = type;
entityName = name;
Expand Down Expand Up @@ -181,10 +209,10 @@ public EntityBinding(EntityDictionary dictionary, Class<?> cls, String type, Str
fieldOrMethodList.addAll(getInstanceMembers(cls.getMethods()));
}

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

attributes = dequeToList(attributesDeque);
relationships = dequeToList(relationshipsDeque);
apiAttributes = dequeToList(attributesDeque);
apiRelationships = dequeToList(relationshipsDeque);
entityPermissions = new EntityPermissions(dictionary, cls, fieldOrMethodList);
}

Expand Down Expand Up @@ -235,8 +263,11 @@ public List<AccessibleObject> getAllFields() {
* @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
*/
private void bindEntityFields(Class<?> cls, String type, Collection<AccessibleObject> fieldOrMethodList) {
private void bindEntityFields(Class<?> cls, String type,
Collection<AccessibleObject> fieldOrMethodList,
Set<Class<? extends Annotation>> hiddenAnnotations) {
for (AccessibleObject fieldOrMethod : fieldOrMethodList) {
bindTriggerIfPresent(OnCreatePreSecurity.class, fieldOrMethod);
bindTriggerIfPresent(OnDeletePreSecurity.class, fieldOrMethod);
Expand Down Expand Up @@ -269,7 +300,9 @@ private void bindEntityFields(Class<?> cls, String type, Collection<AccessibleOb
&& Modifier.isStatic(((Field) fieldOrMethod).getModifiers())) {
continue; // Field must have Column annotation?
}
bindAttrOrRelation(fieldOrMethod);
bindAttrOrRelation(
fieldOrMethod,
hiddenAnnotations.stream().anyMatch(fieldOrMethod::isAnnotationPresent));
}
}
}
Expand Down Expand Up @@ -320,8 +353,9 @@ private static List<String> dequeToList(final Deque<String> deque) {
* Bind an attribute or relationship.
*
* @param fieldOrMethod Field or method to bind
* @param isHidden Whether this field is hidden from API
*/
private void bindAttrOrRelation(AccessibleObject fieldOrMethod) {
private void bindAttrOrRelation(AccessibleObject fieldOrMethod, boolean isHidden) {
boolean isRelation = RELATIONSHIP_TYPES.stream().anyMatch(fieldOrMethod::isAnnotationPresent);

String fieldName = getFieldName(fieldOrMethod);
Expand All @@ -338,13 +372,21 @@ private void bindAttrOrRelation(AccessibleObject fieldOrMethod) {
}

if (isRelation) {
bindRelation(fieldOrMethod, fieldName, fieldType);
bindRelation(fieldOrMethod, fieldName, fieldType, isHidden);
} else {
bindAttr(fieldOrMethod, fieldName, fieldType);
bindAttr(fieldOrMethod, fieldName, fieldType, isHidden);
}
}

private void bindRelation(AccessibleObject fieldOrMethod, String fieldName, Class<?> fieldType) {
/**
* Bind a relationship to current class
*
* @param fieldOrMethod Field or method to bind
* @param fieldName Field name
* @param fieldType Field type
* @param isHidden Whether this field is hidden from API
*/
private void bindRelation(AccessibleObject fieldOrMethod, String fieldName, Class<?> fieldType, boolean isHidden) {
boolean manyToMany = fieldOrMethod.isAnnotationPresent(ManyToMany.class);
boolean manyToOne = fieldOrMethod.isAnnotationPresent(ManyToOne.class);
boolean oneToMany = fieldOrMethod.isAnnotationPresent(OneToMany.class);
Expand Down Expand Up @@ -386,13 +428,25 @@ private void bindRelation(AccessibleObject fieldOrMethod, String fieldName, Clas
relationshipToInverse.put(fieldName, mappedBy);
relationshipToCascadeTypes.put(fieldName, cascadeTypes);

relationshipsDeque.push(fieldName);
if (!isHidden) {
relationshipsDeque.push(fieldName);
}
fieldsToValues.put(fieldName, fieldOrMethod);
fieldsToTypes.put(fieldName, fieldType);
}

private void bindAttr(AccessibleObject fieldOrMethod, String fieldName, Class<?> fieldType) {
attributesDeque.push(fieldName);
/**
* Bind an attribute to current class
*
* @param fieldOrMethod Field or method to bind
* @param fieldName Field name
* @param fieldType Field type
* @param isHidden Whether this field is hidden from API
*/
private void bindAttr(AccessibleObject fieldOrMethod, String fieldName, Class<?> fieldType, boolean isHidden) {
if (!isHidden) {
attributesDeque.push(fieldName);
}
fieldsToValues.put(fieldName, fieldOrMethod);
fieldsToTypes.put(fieldName, fieldType);
}
Expand Down
60 changes: 49 additions & 11 deletions elide-core/src/main/java/com/yahoo/elide/core/EntityDictionary.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.commons.lang3.StringUtils;

Expand Down Expand Up @@ -62,7 +61,6 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
Expand Down Expand Up @@ -458,13 +456,22 @@ public AccessType getAccessType(Class<?> entityClass) {
return getEntityBinding(entityClass).getAccessType();
}

/**
* Get all bound classes.
*
* @return the bound classes
*/
public Set<Class<?>> getBoundClasses() {
return entityBindings.keySet();
}

/**
* Get all bindings.
*
* @return the bindings
*/
public Set<Class<?>> getBindings() {
return entityBindings.keySet();
public Set<EntityBinding> getBindings() {
return new HashSet<>(entityBindings.values());
}

/**
Expand All @@ -482,7 +489,7 @@ public Map<String, Class<? extends Check>> getCheckMappings() {
* @return List of attribute names for entity
*/
public List<String> getAttributes(Class<?> entityClass) {
return getEntityBinding(entityClass).attributes;
return getEntityBinding(entityClass).apiAttributes;
}

/**
Expand All @@ -502,7 +509,7 @@ public List<String> getAttributes(Object entity) {
* @return List of relationship names for entity
*/
public List<String> getRelationships(Class<?> entityClass) {
return getEntityBinding(entityClass).relationships;
return getEntityBinding(entityClass).apiRelationships;
}

/**
Expand All @@ -523,7 +530,7 @@ public List<String> getRelationships(Object entity) {
*/
public List<String> getElideBoundRelationships(Class<?> entityClass) {
return getRelationships(entityClass).stream()
.filter(relationName -> getBindings().contains(getParameterizedType(entityClass, relationName)))
.filter(relationName -> getBoundClasses().contains(getParameterizedType(entityClass, relationName)))
.collect(Collectors.toList());
}

Expand Down Expand Up @@ -842,6 +849,16 @@ public boolean isTransferable(Class<?> entityClass) {
* @param cls Entity bean class
*/
public void bindEntity(Class<?> cls) {
bindEntity(cls, new HashSet<>());
}

/**
* Add given Entity bean to dictionary.
*
* @param cls Entity bean class
* @param hiddenAnnotations Annotations for hiding a field in API
*/
public void bindEntity(Class<?> cls, Set<Class<? extends Annotation>> hiddenAnnotations) {
Class<?> declaredClass = lookupIncludeClass(cls);

if (declaredClass == null) {
Expand Down Expand Up @@ -872,7 +889,29 @@ public void bindEntity(Class<?> cls) {
}

bindJsonApiToEntity.put(type, declaredClass);
entityBindings.put(declaredClass, new EntityBinding(this, declaredClass, type, name));
entityBindings.put(declaredClass, new EntityBinding(this, declaredClass, type, name, hiddenAnnotations));
if (include.rootLevel()) {
bindEntityRoots.add(declaredClass);
}
}

/**
* Add an EntityBinding instance to dictionary.
*
* @param entityBinding EntityBinding instance
*/
public void bindEntity(EntityBinding entityBinding) {
Class<?> declaredClass = entityBinding.entityClass;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have this method call the new bindEntity method with an empty set (or a set that includes Exclude)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bindEntity method needs to keep all information from the existing binding, as the hiddenAnnotations are not tracked in EntityBinding, if we bind the class again in a different dictionary, previous hiddenAnnotations would not be applied.


if (isClassBound(declaredClass)) {
//Ignore duplicate bindings.
return;
}

Include include = (Include) getFirstAnnotation(declaredClass, Collections.singletonList(Include.class));

bindJsonApiToEntity.put(entityBinding.jsonApiType, declaredClass);
entityBindings.put(declaredClass, entityBinding);
if (include.rootLevel()) {
bindEntityRoots.add(declaredClass);
}
Expand Down Expand Up @@ -1144,7 +1183,6 @@ private boolean isClassBound(Class<?> objClass) {
return (entityBindings.getOrDefault(objClass, EMPTY_BINDING) != EMPTY_BINDING);
}


/**
* Check whether a class is a JPA entity
*
Expand Down Expand Up @@ -1211,11 +1249,11 @@ public Set<String> getFieldsOfType(Class<?> targetClass, Class<?> targetType) {
}

public boolean isRelation(Class<?> entityClass, String relationName) {
return getEntityBinding(entityClass).relationships.contains(relationName);
return getEntityBinding(entityClass).apiRelationships.contains(relationName);
}

public boolean isAttribute(Class<?> entityClass, String attributeName) {
return getEntityBinding(entityClass).attributes.contains(attributeName);
return getEntityBinding(entityClass).apiAttributes.contains(attributeName);
}

/**
Expand Down
Loading