/*
* Hibernate OGM, Domain model persistence for NoSQL datastores
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.ogm.datastore.neo4j.query.parsing.impl;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.AssertionFailure;
import org.hibernate.ogm.datastore.neo4j.dialect.impl.NodeLabel;
import org.hibernate.ogm.util.impl.StringHelper;
/**
* Keep track of the aliases needed to create the Cypher query.
*
* @author Davide D'Alto
* @author Guillaume Smet
*/
public class Neo4jAliasResolver {
private final Map<String, String> aliasByEntityName = new HashMap<String, String>();
private final Map<String, RelationshipAliasTree> relationshipAliases = new HashMap<String, RelationshipAliasTree>();
// Contains the aliases that will appear in the OPTIONAL MATCH clause of the query
private final Set<String> optionalMatches = new HashSet<String>();
// Contains the aliases that will appear in the MATCH clause of the query
private final Set<String> requiredMatches = new HashSet<String>();
private int relationshipCounter = 0;
public void registerEntityAlias(String entityName, String alias) {
aliasByEntityName.put( entityName, alias );
}
public String findAliasForType(String entityType) {
return aliasByEntityName.get( entityType );
}
/**
* Given the path to an embedded property, it will create an alias to use in the query for the embedded containing
* the property.
* <p>
* The alias will be saved and can be returned using the method {@link #findAlias(String, List)}.
* <p>
* For example, using n as entity alias and [embedded, anotherEmbedded] as path to the embedded will
* return "_n2" as alias for "n.embedded.anotherEmbedded".
* <p>
* Note that you need to create an alias for every embedded/association in the path before this one.
*
* @param entityAlias the alias of the entity that contains the embedded
* @param propertyPathWithoutAlias the path to the property without the alias
* @param optionalMatch if true, the alias does not represent a required match in the query (It will appear in the OPTIONAL MATCH clause)
* @return the alias of the embedded containing the property
*/
public String createAliasForEmbedded(String entityAlias, List<String> propertyPathWithoutAlias, boolean optionalMatch) {
return createAliasForRelationship( entityAlias, propertyPathWithoutAlias, NodeLabel.EMBEDDED.name(), optionalMatch );
}
/**
* Given the path to an association, it will create an alias to use in the query for the association.
* <p>
* The alias will be saved and can be returned using the method {@link #findAlias(String, List)}.
* <p>
* For example, using n as entity alias and [association, anotherAssociation] as path to the association will
* return "_n2" as alias for "n.association.anotherAssociation".
* <p>
* Note that you need to create an alias for every embedded/association in the path before this one.
*
* @param entityAlias the alias of the entity that contains the embedded
* @param propertyPathWithoutAlias the path to the property without the alias
* @param targetNodeType the name of the target node type
* @param optionalMatch if true, the alias does not represent a required match in the query (It will appear in the OPTIONAL MATCH clause)
* @return the alias of the embedded containing the property
*/
public String createAliasForAssociation(String entityAlias, List<String> propertyPathWithoutAlias, String targetNodeType,
boolean optionalMatch) {
return createAliasForRelationship( entityAlias, propertyPathWithoutAlias, targetNodeType, optionalMatch );
}
private String createAliasForRelationship(String entityAlias, List<String> propertyPathWithoutAlias, String targetEntityName,
boolean optionalMatch) {
RelationshipAliasTree relationshipAlias = relationshipAliases.get( entityAlias );
if ( relationshipAlias == null ) {
relationshipAlias = RelationshipAliasTree.root( entityAlias );
relationshipAliases.put( entityAlias, relationshipAlias );
}
for ( int i = 0; i < propertyPathWithoutAlias.size(); i++ ) {
String name = propertyPathWithoutAlias.get( i );
RelationshipAliasTree child = relationshipAlias.findChild( name );
if ( child == null ) {
if ( i != propertyPathWithoutAlias.size() - 1 ) {
throw new AssertionFailure( "The path to " + StringHelper.join( propertyPathWithoutAlias, "." )
+ " has not been completely constructed" );
}
relationshipCounter++;
String childAlias = "_" + entityAlias + relationshipCounter;
child = RelationshipAliasTree.relationship( childAlias, name, targetEntityName );
relationshipAlias.addChild( child );
}
relationshipAlias = child;
String alias = relationshipAlias.getAlias();
if ( optionalMatch && !requiredMatches.contains( alias ) ) {
optionalMatches.add( alias );
}
else {
requiredMatches.add( alias );
optionalMatches.remove( alias );
}
}
return relationshipAlias.getAlias();
}
/**
* Given the alias of the entity and the path to the relationship it will return the alias
* of the component.
*
* @param entityAlias the alias of the entity
* @param propertyPathWithoutAlias the path to the property without the alias
* @return the alias the relationship or null
*/
public String findAlias(String entityAlias, List<String> propertyPathWithoutAlias) {
RelationshipAliasTree aliasTree = relationshipAliases.get( entityAlias );
if ( aliasTree == null ) {
return null;
}
RelationshipAliasTree associationAlias = aliasTree;
for ( int i = 0; i < propertyPathWithoutAlias.size(); i++ ) {
associationAlias = associationAlias.findChild( propertyPathWithoutAlias.get( i ) );
if ( associationAlias == null ) {
return null;
}
}
return associationAlias.getAlias();
}
/**
* Given the alias of the entity it will return a tree structure containing all the aliases for the embedded
* properties and associations of the entity.
* <p>
* The tree has the entity alias as root and the embedded/association alias as children.
* For example, a path to two properties like:
* <ol>
* <li>n.first.anotherEmbedded</li>
* <li>n.second.anotherEmbedded</li>
* </ol>
*
* Might be represented with the following structure:
* <pre>
* n (alias = n)|- first (alias = _n1) -- anotherEmbedded (alias = _n2)
* |
* -- second (alias = _n3) -- anotherEmbedded (alias = _n4)
* </pre>
* @param entityAlias the alias of the entity that contains the embedded
* @return the corresponding {@link RelationshipAliasTree} or null
*/
public RelationshipAliasTree getRelationshipAliasTree(String entityAlias) {
return relationshipAliases.get( entityAlias );
}
/**
* Tells if the alias has to be used in the OPTIONAL MATCH part of the query.
*
* @param alias the alis to check
* @return {@code true} if the alias should be used in OPTIONAL MATCH part of the query, {@code false} otherwise
*/
public boolean isOptionalMatch(String alias) {
return optionalMatches.contains( alias );
}
}