/** * */ package org.minnal.instrument.entity; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.javalite.common.Inflector; import org.minnal.instrument.MinnalInstrumentationException; import org.minnal.instrument.NamingStrategy; import org.minnal.instrument.entity.EntityNode.EntityNodePath; import org.minnal.instrument.entity.metadata.AssociationMetaData; import org.minnal.instrument.entity.metadata.CollectionMetaData; import org.minnal.instrument.entity.metadata.EntityMetaData; import org.minnal.instrument.entity.metadata.EntityMetaDataProvider; import org.minnal.instrument.entity.metadata.ParameterMetaData; import org.minnal.utils.Node; import org.minnal.utils.reflection.ClassUtils; import org.minnal.utils.route.QueryParam; import org.minnal.utils.route.QueryParam.Type; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Splitter; /** * @author ganeshs * */ public class EntityNode extends Node<EntityNode, EntityNodePath, EntityMetaData> { private String name; private String resourceName; private Map<Class<?>, List<String>> visitedEntities = new HashMap<Class<?>, List<String>>(); private CollectionMetaData source; private NamingStrategy namingStrategy; private static final Logger logger = LoggerFactory.getLogger(EntityNode.class); public EntityNode(Class<?> entityClass, NamingStrategy namingStrategy) { this(entityClass, namingStrategy.getEntityName(entityClass), namingStrategy); } public EntityNode(Class<?> entityClass, String name, NamingStrategy namingStrategy) { super(EntityMetaDataProvider.instance().getEntityMetaData(entityClass)); this.name = name; this.resourceName = namingStrategy.getResourceName(name); this.namingStrategy = namingStrategy; } EntityNode(CollectionMetaData collection, NamingStrategy namingStrategy) { this(collection.getElementType(), namingStrategy.getEntityCollectionName(collection.getName()), namingStrategy); this.source = collection; } public void construct() { LinkedList<EntityNode> queue = new LinkedList<EntityNode>(); queue.offer(this); while (! queue.isEmpty()) { EntityNode node = queue.poll(); for (CollectionMetaData collection : node.getValue().getCollections()) { if (! collection.isEntity()) { continue; } EntityNode child = new EntityNode(collection, namingStrategy); if (node.addChild(child) != null) { queue.offer(child); } } } } /** * @return the resourceName */ public String getResourceName() { return resourceName; } @Override protected boolean visited(EntityNode node) { List<String> associations = visitedEntities.get(node.getValue().getEntityClass()); if (associations == null) { return false; } return associations.contains(node.getName()); } @Override protected void markVisited(EntityNode node) { logger.debug("Marking the node {} as visited in this node {}", node, this); List<String> associations = visitedEntities.get(node.getValue().getEntityClass()); if (associations == null) { associations = new ArrayList<String>(); visitedEntities.put(node.getValue().getEntityClass(), associations); } associations.add(node.getName()); } /** * @return the name */ public String getName() { return name; } /** * @return the entityMetaData */ public EntityMetaData getEntityMetaData() { return getValue(); } /** * @return */ public CollectionMetaData getSource() { return source; } @Override protected EntityNode getThis() { return this; } @Override protected EntityNodePath createNodePath(List<EntityNode> path) { return new EntityNodePath(path); } public EntityNodePath getEntityNodePath(String path) { Iterable<String> nodes = null; if (! StringUtils.isBlank(path)) { nodes = Splitter.on(".").split(path); } List<EntityNode> list = new ArrayList<EntityNode>(); EntityNode lastNode = this; if (nodes != null) { for (String node : nodes ) { list.add(lastNode); lastNode = lastNode.getChild(namingStrategy.getEntityCollectionName(node)); if (lastNode == null) { logger.error("Invalid path - {} for the node - {}", path, getName()); throw new MinnalInstrumentationException("Invalid path - " + path + " for the node - " + getName()); } } } list.add(lastNode); return new EntityNodePath(list); } protected EntityNode getChild(String name) { name = namingStrategy.getResourceName(name); for (EntityNode child : getChildren()) { if (child.getResourceName().equals(name)) { return child; } } return null; } @Override public String toString() { return "EntityNode [name=" + name + ", resourceName=" + resourceName + "]"; } /** * A path from the root node to a leaf node in the entity hierarchy. The path will be used to construct the uris for single and bulk resources. * It also identifies all the search fields marked using {@link Searchable} annotation in the entity hierarchy. * * @author ganeshs * */ public class EntityNodePath extends Node<EntityNode, EntityNodePath, EntityMetaData>.NodePath { private String bulkPath; private String singlePath; private String name; private boolean createAllowed = true; private boolean readAllowed = true; private boolean updateAllowed = true; private boolean deleteAllowed = true; private List<QueryParam> queryParams = new ArrayList<QueryParam>(); public EntityNodePath(List<EntityNode> path) { super(path); init(path); buildPath(path); } private void init(List<EntityNode> path) { if (path.size() == 1) { EntityMetaData data = path.get(0).getValue(); AggregateRoot root = ClassUtils.getAnnotation(data.getEntityClass(), AggregateRoot.class); if (root != null) { createAllowed = root.create(); readAllowed = root.read(); updateAllowed = root.update(); deleteAllowed = root.delete(); } } else { EntityNode node = path.get(size() - 1); if (node.source != null) { createAllowed = node.source.isCreateAllowed(); readAllowed = node.source.isReadAllowed(); updateAllowed = node.source.isUpdateAllowed(); deleteAllowed = node.source.isDeleteAllowed(); } } } private void buildPath(List<EntityNode> path) { StringWriter writer = new StringWriter(); Iterator<EntityNode> iterator = iterator(); StringBuffer prefix = new StringBuffer(); EntityNode parent = null; StringWriter pathName = new StringWriter(); while (iterator.hasNext()) { EntityNode node = iterator.next(); String name = node.getResourceName(); pathName.append(node.getName()); writer.append("/").append(name); if (iterator.hasNext()) { writer.append("/{" + namingStrategy.getPathSegment(node.getName() + "Id") + "}"); pathName.append("_"); } if (! iterator.hasNext()) { if (parent != null) { prefix = prefix.length() == 0 ? prefix.append(name) : prefix.append(".").append(name); } addSearchFields(prefix.toString(), node); } parent = node; } bulkPath = writer.toString(); singlePath = bulkPath + "/{id}"; name = Inflector.camelize(pathName.toString()); } private void addSearchFields(String prefix, EntityNode node) { QueryParam param = null; prefix = prefix.isEmpty() ? prefix : prefix + "."; for (ParameterMetaData meta : node.getEntityMetaData().getSearchFields()) { param = new QueryParam(prefix + namingStrategy.getQueryParamName(meta.getFieldName()), Type.typeOf(meta.getType()), "The " + meta.getFieldName()); queryParams.add(param); } for (AssociationMetaData meta : node.getEntityMetaData().getAssociations()) { if (meta.isEntity()) { String assocPrefix = prefix + namingStrategy.getQueryParamName(meta.getName()) + "."; EntityMetaData data = EntityMetaDataProvider.instance().getEntityMetaData(meta.getType()); for (ParameterMetaData paramMeta : data.getSearchFields()) { param = new QueryParam(assocPrefix + namingStrategy.getQueryParamName(paramMeta.getFieldName()), Type.typeOf(paramMeta.getType()), "The " + paramMeta.getFieldName()); queryParams.add(param); } } } for (EntityNode child : node.getChildren()) { String collectionPrefix = prefix + child.getResourceName(); addSearchFields(collectionPrefix, child); } } public String getBulkPath() { return bulkPath; } public String getSinglePath() { return singlePath; } public String getName() { return name; } /** * @return the queryParams */ public List<QueryParam> getQueryParams() { return queryParams; } public boolean isCreateAllowed() { return createAllowed; } public boolean isReadAllowed() { return readAllowed; } public boolean isUpdateAllowed() { return updateAllowed; } public boolean isDeleteAllowed() { return deleteAllowed; } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + getOuterType().hashCode(); result = prime * result + ((singlePath == null) ? 0 : singlePath.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; EntityNodePath other = (EntityNodePath) obj; if (!getOuterType().equals(other.getOuterType())) return false; if (singlePath == null) { if (other.singlePath != null) return false; } else if (!singlePath.equals(other.singlePath)) return false; return true; } @Override public String toString() { return getSinglePath(); } private EntityNode getOuterType() { return EntityNode.this; } } }