/*
* 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.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.ast.spi.EntityNamesResolver;
import org.hibernate.hql.ast.spi.PropertyHelper;
import org.hibernate.ogm.model.key.spi.EntityKeyMetadata;
import org.hibernate.ogm.persister.impl.OgmCollectionPersister;
import org.hibernate.ogm.persister.impl.OgmEntityPersister;
import org.hibernate.ogm.query.parsing.impl.ParserPropertyHelper;
import org.hibernate.ogm.type.spi.GridType;
import org.hibernate.ogm.type.spi.TypeTranslator;
import org.hibernate.ogm.util.impl.ArrayHelper;
import org.hibernate.ogm.util.impl.StringHelper;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.Type;
/**
* Property helper dealing with Neo4j.
*
* @author Davide D'Alto
* @author Guillaume Smet
*/
public class Neo4jPropertyHelper extends ParserPropertyHelper implements PropertyHelper {
private final SessionFactoryImplementor sessionFactory;
private final Neo4jAliasResolver aliasResolver;
public Neo4jPropertyHelper(SessionFactoryImplementor sessionFactory, EntityNamesResolver entityNames,
Neo4jAliasResolver aliasResolver) {
super( sessionFactory, entityNames );
this.aliasResolver = aliasResolver;
this.sessionFactory = sessionFactory;
}
@Override
public Object convertToBackendType(String entityType, List<String> propertyPath, Object value) {
if ( value instanceof Neo4jQueryParameter ) {
return value;
}
else {
Type propertyType = getPropertyType( entityType, propertyPath );
if ( isElementCollection( propertyType ) ) {
propertyType = ( (CollectionType) propertyType ).getElementType( sessionFactory );
}
GridType ogmType = sessionFactory.getServiceRegistry().getService( TypeTranslator.class ).getType( propertyType );
return ogmType.convertToBackendType( value, sessionFactory );
}
}
/**
* Returns the {@link PropertyIdentifier} for the given property path.
*
* In passing, it creates all the necessary aliases for embedded/associations.
*
* @param entityType the type of the entity
* @param propertyPath the path to the property without aliases
* @param requiredDepth it defines until where the aliases will be considered as required aliases (see {@link Neo4jAliasResolver} for more information)
* @return the {@link PropertyIdentifier}
*/
public PropertyIdentifier getPropertyIdentifier(String entityType, List<String> propertyPath, int requiredDepth) {
// we analyze the property path to find all the associations/embedded which are in the way and create proper
// aliases for them
String entityAlias = aliasResolver.findAliasForType( entityType );
String propertyEntityType = entityType;
String propertyAlias = entityAlias;
String propertyName;
List<String> currentPropertyPath = new ArrayList<>();
List<String> lastAssociationPath = Collections.emptyList();
OgmEntityPersister currentPersister = getPersister( entityType );
boolean isLastElementAssociation = false;
int depth = 1;
for ( String property : propertyPath ) {
currentPropertyPath.add( property );
Type currentPropertyType = getPropertyType( entityType, currentPropertyPath );
// determine if the current property path is still part of requiredPropertyMatch
boolean optionalMatch = depth > requiredDepth;
if ( currentPropertyType.isAssociationType() ) {
AssociationType associationPropertyType = (AssociationType) currentPropertyType;
Joinable associatedJoinable = associationPropertyType.getAssociatedJoinable( getSessionFactory() );
if ( associatedJoinable.isCollection()
&& !( (OgmCollectionPersister) associatedJoinable ).getType().isEntityType() ) {
// we have a collection of embedded
propertyAlias = aliasResolver.createAliasForEmbedded( entityAlias, currentPropertyPath, optionalMatch );
}
else {
propertyEntityType = associationPropertyType.getAssociatedEntityName( getSessionFactory() );
currentPersister = getPersister( propertyEntityType );
String targetNodeType = currentPersister.getEntityKeyMetadata().getTable();
propertyAlias = aliasResolver.createAliasForAssociation( entityAlias, currentPropertyPath, targetNodeType, optionalMatch );
lastAssociationPath = new ArrayList<>( currentPropertyPath );
isLastElementAssociation = true;
}
}
else if ( currentPropertyType.isComponentType()
&& !isIdProperty( currentPersister, propertyPath.subList( lastAssociationPath.size(), propertyPath.size() ) ) ) {
// we are in the embedded case and the embedded is not the id of the entity (the id is stored as normal
// properties)
propertyAlias = aliasResolver.createAliasForEmbedded( entityAlias, currentPropertyPath, optionalMatch );
}
else {
isLastElementAssociation = false;
}
depth++;
}
if ( isLastElementAssociation ) {
// even the last element is an association, we need to find a suitable identifier property
propertyName = getSessionFactory().getEntityPersister( propertyEntityType ).getIdentifierPropertyName();
}
else {
// the last element is a property so we can build the test with this property
propertyName = getColumnName( propertyEntityType, propertyPath.subList( lastAssociationPath.size(), propertyPath.size() ) );
}
return new PropertyIdentifier( propertyAlias, propertyName );
}
public String getColumnName(String entityType, List<String> propertyPathWithoutAlias) {
return getColumnName( getPersister( entityType ), propertyPathWithoutAlias );
}
public String getColumnName(Class<?> entityType, List<String> propertyName) {
OgmEntityPersister persister = (OgmEntityPersister) getSessionFactory().getEntityPersister( entityType.getName() );
return getColumnName( persister, propertyName );
}
private String getColumnName(OgmEntityPersister persister, List<String> propertyPathWithoutAlias) {
if ( isIdProperty( persister, propertyPathWithoutAlias ) ) {
return getColumn( persister, propertyPathWithoutAlias );
}
String columnName = getColumn( persister, propertyPathWithoutAlias );
if ( isNestedProperty( propertyPathWithoutAlias ) ) {
columnName = columnName.substring( columnName.lastIndexOf( '.' ) + 1, columnName.length() );
}
return columnName;
}
public boolean isIdProperty(String entityType, List<String> propertyPath) {
return isIdProperty( getPersister( entityType ), propertyPath );
}
/**
* Check if the property is part of the identifier of the entity.
*
* @param persister the {@link OgmEntityPersister} of the entity with the property
* @param namesWithoutAlias the path to the property with all the aliases resolved
* @return {@code true} if the property is part of the id, {@code false} otherwise.
*/
public boolean isIdProperty(OgmEntityPersister persister, List<String> namesWithoutAlias) {
String join = StringHelper.join( namesWithoutAlias, "." );
Type propertyType = persister.getPropertyType( namesWithoutAlias.get( 0 ) );
String[] identifierColumnNames = persister.getIdentifierColumnNames();
if ( propertyType.isComponentType() ) {
String[] embeddedColumnNames = persister.getPropertyColumnNames( join );
for ( String embeddedColumn : embeddedColumnNames ) {
if ( !ArrayHelper.contains( identifierColumnNames, embeddedColumn ) ) {
return false;
}
}
return true;
}
return ArrayHelper.contains( identifierColumnNames, join );
}
private EntityKeyMetadata getKeyMetaData(String entityType) {
OgmEntityPersister persister = (OgmEntityPersister) getSessionFactory().getEntityPersister( entityType );
return persister.getEntityKeyMetadata();
}
}