/*
* 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.query.parsing.impl;
import java.util.ArrayList;
import java.util.Iterator;
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.persister.impl.OgmCollectionPersister;
import org.hibernate.ogm.persister.impl.OgmEntityPersister;
import org.hibernate.ogm.type.spi.GridType;
import org.hibernate.ogm.type.spi.TypeTranslator;
import org.hibernate.ogm.util.impl.StringHelper;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.type.AbstractStandardBasicType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
/**
* {@link PropertyHelper} implementation containing common methods to obtain the type of the properties or the
* column names they are mapped to.
*
* @author Davide D'Alto
* @author Guillaume Smet
*/
public class ParserPropertyHelper implements PropertyHelper {
private final SessionFactoryImplementor sessionFactory;
private final EntityNamesResolver entityNames;
public ParserPropertyHelper(SessionFactoryImplementor sessionFactory, EntityNamesResolver entityNames) {
this.sessionFactory = sessionFactory;
this.entityNames = entityNames;
}
@Override
public Object convertToPropertyType(String entityType, List<String> propertyPath, String value) {
Type propertyType = getPropertyType( entityType, propertyPath );
if ( propertyType.isEntityType() ) {
throw new UnsupportedOperationException( "Queries on associated entities are not supported yet." );
}
if ( propertyType instanceof AbstractStandardBasicType ) {
return ( (AbstractStandardBasicType<?>) propertyType ).fromString( value );
}
else {
return value;
}
}
protected boolean isElementCollection(Type propertyType) {
if ( !propertyType.isCollectionType() ) {
return false;
}
Type elementType = ( (CollectionType) propertyType ).getElementType( sessionFactory );
return !elementType.isComponentType() && !elementType.isEntityType();
}
@Override
public Object convertToBackendType(String entityType, List<String> propertyPath, Object value) {
Type propertyType = getPropertyType( entityType, propertyPath );
GridType ogmType = sessionFactory.getServiceRegistry().getService( TypeTranslator.class ).getType( propertyType );
return ogmType.convertToBackendType( value, sessionFactory );
}
protected Type getPropertyType(String entityType, List<String> propertyPath) {
Iterator<String> pathIterator = propertyPath.iterator();
OgmEntityPersister persister = getPersister( entityType );
String propertyName = pathIterator.next();
Type propertyType = persister.getPropertyType( propertyName );
if ( !pathIterator.hasNext() ) {
return propertyType;
}
else if ( propertyType.isComponentType() ) {
// Embedded property
return getAssociationPropertyType( propertyType, pathIterator );
}
else if ( propertyType.isAssociationType() ) {
Joinable associatedJoinable = ( (AssociationType) propertyType ).getAssociatedJoinable( persister.getFactory() );
if ( associatedJoinable.isCollection() ) {
OgmCollectionPersister collectionPersister = (OgmCollectionPersister) associatedJoinable;
if ( collectionPersister.getType().isComponentType() ) {
// Collection of embeddables
return getAssociationPropertyType( collectionPersister.getType(), pathIterator );
}
}
else if ( propertyType.isEntityType() ) {
// @*ToOne associations
return getAssociationPropertyType( propertyType, pathIterator );
}
}
throw new UnsupportedOperationException( "Unrecognized property type: " + propertyType );
}
private Type getAssociationPropertyType(Type type, Iterator<String> pathIterator) {
if ( pathIterator.hasNext() ) {
String property = pathIterator.next();
Type subType = associationPropertyType( type, property );
if ( subType.isComponentType() ) {
return getAssociationPropertyType( subType, pathIterator );
}
else if ( subType.isAssociationType() ) {
Joinable associatedJoinable = ( (AssociationType) subType ).getAssociatedJoinable( sessionFactory );
if ( !associatedJoinable.isCollection() && subType.isEntityType() ) {
return getAssociationPropertyType( subType, pathIterator );
}
throw new UnsupportedOperationException( "Queries on collection in embeddables are not supported: " + property );
}
else {
return subType;
}
}
else {
return type;
}
}
private Type associationPropertyType(Type type, String property) {
if ( type instanceof ComponentType ) {
ComponentType componentType = (ComponentType) type;
return componentType.getSubtypes()[componentType.getPropertyIndex( property )];
}
else if ( type instanceof EntityType ) {
OgmEntityPersister persister = getPersister( type.getName() );
return persister.getPropertyType( property );
}
throw new UnsupportedOperationException( "Unrecognized property type: " + type );
}
protected OgmEntityPersister getPersister(String entityType) {
Class<?> targetedType = entityNames.getClassFromName( entityType );
if ( targetedType == null ) {
throw new IllegalStateException( "Unknown entity name " + entityType );
}
return (OgmEntityPersister) sessionFactory.getEntityPersister( targetedType.getName() );
}
/**
* Checks if the path leads to an embedded property or association.
*
* @param targetTypeName the entity with the property
* @param namesWithoutAlias the path to the property with all the aliases resolved
* @return {@code true} if the property is an embedded, {@code false} otherwise.
*/
public boolean isEmbeddedProperty(String targetTypeName, List<String> namesWithoutAlias) {
OgmEntityPersister persister = getPersister( targetTypeName );
Type propertyType = persister.getPropertyType( namesWithoutAlias.get( 0 ) );
if ( propertyType.isComponentType() ) {
// Embedded
return true;
}
else if ( propertyType.isAssociationType() ) {
Joinable associatedJoinable = ( (AssociationType) propertyType ).getAssociatedJoinable( persister.getFactory() );
if ( associatedJoinable.isCollection() ) {
OgmCollectionPersister collectionPersister = (OgmCollectionPersister) associatedJoinable;
return collectionPersister.getType().isComponentType();
}
}
return false;
}
/**
* Check if the path to the property correspond to an association.
*
* @param targetTypeName the name of the entity containing the property
* @param pathWithoutAlias the path to the property WITHOUT aliases
* @return {@code true} if the property is an association or {@code false} otherwise
*/
public boolean isAssociation(String targetTypeName, List<String> pathWithoutAlias) {
OgmEntityPersister persister = getPersister( targetTypeName );
Type propertyType = persister.getPropertyType( pathWithoutAlias.get( 0 ) );
return propertyType.isAssociationType();
}
/**
* Find the path to the first association in the property path.
*
* @param targetTypeName the entity with the property
* @param pathWithoutAlias the path to the property WITHOUT the alias
* @return the path to the first association or {@code null} if there isn't an association in the property path
*/
public List<String> findAssociationPath(String targetTypeName, List<String> pathWithoutAlias) {
List<String> subPath = new ArrayList<String>( pathWithoutAlias.size() );
for ( String name : pathWithoutAlias ) {
subPath.add( name );
if ( isAssociation( targetTypeName, subPath ) ) {
return subPath;
}
}
return null;
}
/**
* Check if the property path is a nested property.
* <p>
* Example: [anEmbeddable, anotherEmbeddedable, propertyName]
*
* @param propertyPathWithoutAlias the path to the property WITHOUT the aliases.
* @return {@code true} if it is a nested property, {@code false} otherwise
*/
public boolean isNestedProperty(List<String> propertyPathWithoutAlias) {
return propertyPathWithoutAlias.size() > 1;
}
/**
* Check if the property path is a simple property.
* <p>
* Example: [propertyName]
*
* @param propertyPathWithoutAlias the path to the property WITHOUT the aliases
* @return {@code true} if it is a simple property, {@code false} otherwise
*/
public boolean isSimpleProperty(List<String> propertyPathWithoutAlias) {
return propertyPathWithoutAlias.size() == 1;
}
/**
* Returns the names of all those columns which represent a collection to be stored within the owning entity
* structure (element collections and/or *-to-many associations, depending on the dialect's capabilities).
*/
protected String getColumn(OgmEntityPersister persister, List<String> propertyPath) {
Iterator<String> pathIterator = propertyPath.iterator();
String propertyName = pathIterator.next();
Type propertyType = persister.getPropertyType( propertyName );
if ( !pathIterator.hasNext() ) {
if ( isElementCollection( propertyType ) ) {
Joinable associatedJoinable = ( (AssociationType) propertyType ).getAssociatedJoinable( persister.getFactory() );
OgmCollectionPersister collectionPersister = (OgmCollectionPersister) associatedJoinable;
// Collection of elements
return collectionPersister.getElementColumnNames()[0];
}
return persister.getPropertyColumnNames( propertyName )[0];
}
else if ( propertyType.isComponentType() ) {
// Embedded property
String componentPropertyName = StringHelper.join( propertyPath, "." );
return persister.getPropertyColumnNames( componentPropertyName )[0];
}
else if ( propertyType.isAssociationType() ) {
Joinable associatedJoinable = ( (AssociationType) propertyType ).getAssociatedJoinable( persister.getFactory() );
if ( associatedJoinable.isCollection() ) {
OgmCollectionPersister collectionPersister = (OgmCollectionPersister) associatedJoinable;
if ( collectionPersister.getType().isComponentType() ) {
StringBuilder columnNameBuilder = new StringBuilder( propertyName );
columnNameBuilder.append( "." );
// Collection of embeddables
appendComponentCollectionPath( columnNameBuilder, collectionPersister, pathIterator );
return columnNameBuilder.toString();
}
}
}
throw new UnsupportedOperationException( "Unrecognized property type: " + propertyType );
}
private void appendComponentCollectionPath(StringBuilder columnNameBuilder, OgmCollectionPersister persister, Iterator<String> pathIterator) {
if ( pathIterator.hasNext() ) {
String property = pathIterator.next();
Type subType = associationPropertyType( persister.getType(), property );
if ( subType.isComponentType() ) {
property += "." + StringHelper.join( pathIterator, "." );
}
else if ( subType.isAssociationType() ) {
throw new UnsupportedOperationException( "Queries on collection in embeddables are not supported: " + property );
}
columnNameBuilder.append( persister.toColumns( property )[0] );
}
}
protected SessionFactoryImplementor getSessionFactory() {
return sessionFactory;
}
}