/*******************************************************************************
* * Copyright 2012 Impetus Infotech.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
******************************************************************************/
package com.impetus.client.rdbms.query;
import java.lang.reflect.Field;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PersistenceException;
import javax.persistence.Transient;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.impetus.client.rdbms.HibernateClient;
import com.impetus.kundera.client.Client;
import com.impetus.kundera.client.ClientBase;
import com.impetus.kundera.client.EnhanceEntity;
import com.impetus.kundera.metadata.MetadataUtils;
import com.impetus.kundera.metadata.model.ApplicationMetadata;
import com.impetus.kundera.metadata.model.EntityMetadata;
import com.impetus.kundera.metadata.model.MetamodelImpl;
import com.impetus.kundera.metadata.model.Relation;
import com.impetus.kundera.metadata.model.attributes.AbstractAttribute;
import com.impetus.kundera.metadata.model.type.AbstractManagedType;
import com.impetus.kundera.persistence.AbstractEntityReader;
import com.impetus.kundera.persistence.EntityManagerFactoryImpl.KunderaMetadata;
import com.impetus.kundera.persistence.EntityReader;
import com.impetus.kundera.property.PropertyAccessorHelper;
import com.impetus.kundera.query.KunderaQuery;
import com.impetus.kundera.query.KunderaQuery.FilterClause;
import com.impetus.kundera.query.QueryHandlerException;
/**
* The Class RDBMSEntityReader.
*
* @author vivek.mishra
*/
public class RDBMSEntityReader extends AbstractEntityReader implements EntityReader
{
/** the log used by this class. */
private static Logger log = LoggerFactory.getLogger(RDBMSEntityReader.class);
/** The conditions. */
private Queue conditions;
/** The filter. */
private String filter;
/** The jpa query. */
private String jpaQuery;
/**
* Instantiates a new rDBMS entity reader.
*
* @param luceneQuery
* the lucene query
* @param query
* the query
*/
public RDBMSEntityReader(String query, KunderaQuery kunderaQuery, final KunderaMetadata kunderaMetadata)
{
super(kunderaMetadata);
this.jpaQuery = query;
this.kunderaQuery = kunderaQuery;
}
/**
* Instantiates a new rDBMS entity reader.
*/
public RDBMSEntityReader(final KunderaMetadata kunderaMetadata)
{
super(kunderaMetadata);
}
/*
* (non-Javadoc)
*
* @see
* com.impetus.kundera.persistence.EntityReader#populateRelation(com.impetus
* .kundera.metadata.model.EntityMetadata, java.util.List, boolean,
* com.impetus.kundera.client.Client)
*/
@Override
public List<EnhanceEntity> populateRelation(EntityMetadata m, Client client, int maxResults)
{
// TODO: maxresults to be taken care after work on pagination.
List<EnhanceEntity> ls = null;
List<String> relationNames = m.getRelationNames();
boolean isParent = m.isParent();
if (!isParent)
{
// if it is not a parent.
String sqlQuery = null;
if (MetadataUtils.useSecondryIndex(((ClientBase) client).getClientMetadata()))
{
sqlQuery = getSqlQueryFromJPA(m, relationNames, null);
}
else
{
// prepare lucene query and find.
Set<String> rSet = fetchDataFromLucene(m.getEntityClazz(), client);
if (rSet != null && !rSet.isEmpty())
{
filter = "WHERE";
}
sqlQuery = getSqlQueryFromJPA(m, relationNames, rSet);
}
// call client with relation name list and convert to sql query.
ls = populateEnhanceEntities(m, relationNames, client, sqlQuery);
}
else
{
if (MetadataUtils.useSecondryIndex(((ClientBase) client).getClientMetadata()))
{
try
{
List entities = ((HibernateClient) client).find(getSqlQueryFromJPA(m, relationNames, null),
new ArrayList<String>(), m);
ls = new ArrayList<EnhanceEntity>(entities.size());
ls = transform(m, ls, entities);
}
catch (Exception e)
{
log.error("Error while executing handleAssociation for RDBMS, Caused by {}.", e);
throw new QueryHandlerException(e);
}
}
else
{
ls = onAssociationUsingLucene(m, client, ls);
}
}
return ls;
}
/**
* Populate enhance entities.
*
* @param m
* the m
* @param relationNames
* the relation names
* @param client
* the client
* @param sqlQuery
* the sql query
* @return the list
*/
private List<EnhanceEntity> populateEnhanceEntities(EntityMetadata m, List<String> relationNames, Client client,
String sqlQuery)
{
List<EnhanceEntity> ls = null;
List result = ((HibernateClient) client).find(sqlQuery, relationNames, m);
if (!result.isEmpty())
{
ls = new ArrayList<EnhanceEntity>(result.size());
for (Object o : result)
{
EnhanceEntity entity = null;
if (!o.getClass().isAssignableFrom(EnhanceEntity.class))
{
entity = new EnhanceEntity(o, getId(o, m), null);
}
else
{
entity = (EnhanceEntity) o;
}
ls.add(entity);
}
}
return ls;
}
/**
* Gets the sql query from jpa.
*
* @param entityMetadata
* the entity metadata
* @param relations
* the relations
* @param primaryKeys
* the primary keys
* @return the sql query from jpa
*/
public String getSqlQueryFromJPA(EntityMetadata entityMetadata, List<String> relations, Set<String> primaryKeys)
{
ApplicationMetadata appMetadata = kunderaMetadata.getApplicationMetadata();
Metamodel metaModel = appMetadata.getMetamodel(entityMetadata.getPersistenceUnit());
if (jpaQuery != null)
{
String query = appMetadata.getQuery(jpaQuery);
boolean isNative = kunderaQuery != null ? kunderaQuery.isNative() : false;
if (isNative)
{
return query != null ? query : jpaQuery;
}
}
// Suffixing the UNDERSCORE instead of prefix as Oracle 11g complains
// about invalid characters error while executing the request.
String aliasName = entityMetadata.getTableName() + "_";
StringBuilder queryBuilder = new StringBuilder("Select ");
EntityType entityType = metaModel.entity(entityMetadata.getEntityClazz());
Set<Attribute> attributes = entityType.getAttributes();
for (Attribute field : attributes)
{
if (!field.isAssociation() && !field.isCollection()
&& !((Field) field.getJavaMember()).isAnnotationPresent(ManyToMany.class)
&& !((Field) field.getJavaMember()).isAnnotationPresent(Transient.class)
&& !((MetamodelImpl) metaModel).isEmbeddable(((AbstractAttribute) field).getBindableJavaType()))
{
queryBuilder.append(aliasName);
queryBuilder.append(".");
queryBuilder.append(((AbstractAttribute) field).getJPAColumnName());
queryBuilder.append(", ");
}
}
// Handle embedded columns, add them to list.
Map<String, EmbeddableType> embeddedColumns = ((MetamodelImpl) metaModel).getEmbeddables(entityMetadata
.getEntityClazz());
for (EmbeddableType embeddedCol : embeddedColumns.values())
{
Set<Attribute> embeddedAttributes = embeddedCol.getAttributes();
for (Attribute column : embeddedAttributes)
{
queryBuilder.append(aliasName);
queryBuilder.append(".");
queryBuilder.append(((AbstractAttribute) column).getJPAColumnName());
queryBuilder.append(", ");
}
}
if (relations != null)
{
for (String relation : relations)
{
Relation rel = entityMetadata.getRelation(entityMetadata.getFieldName(relation));
String r = MetadataUtils.getMappedName(entityMetadata, rel, kunderaMetadata);
if (!((AbstractAttribute) entityMetadata.getIdAttribute()).getJPAColumnName().equalsIgnoreCase(
r != null ? r : relation)
&& rel != null
&& !rel.getProperty().isAnnotationPresent(ManyToMany.class)
&& !rel.getProperty().isAnnotationPresent(OneToMany.class)
&& (rel.getProperty().isAnnotationPresent(OneToOne.class)
&& StringUtils.isBlank(rel.getMappedBy()) || rel.getProperty().isAnnotationPresent(
ManyToOne.class)))
{
queryBuilder.append(aliasName);
queryBuilder.append(".");
queryBuilder.append(r != null ? r : relation);
queryBuilder.append(", ");
}
}
}
// Remove last ","
queryBuilder.deleteCharAt(queryBuilder.lastIndexOf(","));
queryBuilder.append(" From ");
if (entityMetadata.getSchema() != null && !entityMetadata.getSchema().isEmpty())
{
queryBuilder.append(entityMetadata.getSchema() + ".");
}
queryBuilder.append(entityMetadata.getTableName());
queryBuilder.append(" ");
queryBuilder.append(aliasName);
// add conditions
if (filter != null)
{
queryBuilder.append(" Where ");
}
// Append conditions
onCondition(entityMetadata, (MetamodelImpl) metaModel, primaryKeys, aliasName, queryBuilder, entityType);
return queryBuilder.toString();
}
/**
*
* @param entityMetadata
* @param primaryKeys
* @param aliasName
* @param queryBuilder
* @param entityType
*/
private void onCondition(EntityMetadata entityMetadata, MetamodelImpl metamodel, Set<String> primaryKeys,
String aliasName, StringBuilder queryBuilder, EntityType entityType)
{
if (primaryKeys == null)
{
for (Object o : conditions)
{
if (o instanceof FilterClause)
{
FilterClause clause = ((FilterClause) o);
Object value = clause.getValue().get(0);
String propertyName = clause.getProperty();
String condition = clause.getCondition();
if (StringUtils.contains(propertyName, '.'))
{
int indexOf = propertyName.indexOf(".");
String jpaColumnName = propertyName.substring(0, indexOf);
String embeddedColumnName = propertyName.substring(indexOf + 1, propertyName.length());
String fieldName = entityMetadata.getFieldName(jpaColumnName);
Attribute attribute = entityType.getAttribute(fieldName);
EmbeddableType embeddedType = metamodel.embeddable(((AbstractAttribute) attribute)
.getBindableJavaType());
Attribute embeddedAttribute = embeddedType.getAttribute(embeddedColumnName);
addClause(entityMetadata, aliasName, queryBuilder, entityType, value, condition, fieldName,
embeddedAttribute);
}
else
{
String fieldName = entityMetadata.getFieldName(propertyName);
Attribute attribute = entityType.getAttribute(fieldName);
if (metamodel.isEmbeddable(((AbstractAttribute) attribute).getBindableJavaType()))
{
EmbeddableType embeddedType = metamodel.embeddable(((AbstractAttribute) attribute)
.getBindableJavaType());
Set<Attribute> attributes = embeddedType.getAttributes();
for (Attribute embeddedAttribute : attributes)
{
Object embeddedAttributevalue = PropertyAccessorHelper.getObject(value,
(Field) embeddedAttribute.getJavaMember());
addClause(entityMetadata, aliasName, queryBuilder, entityType, embeddedAttributevalue,
condition, propertyName, embeddedAttribute);
queryBuilder.append(" and ");
}
queryBuilder.delete(queryBuilder.lastIndexOf("and"), queryBuilder.lastIndexOf("and") + 3);
}
else if (((AbstractAttribute) entityMetadata.getIdAttribute()).getJPAColumnName().equals(
propertyName))
{
addClause(entityMetadata, aliasName, queryBuilder, entityType, value, condition,
propertyName, entityMetadata.getIdAttribute());
}
else
{
addClause(entityMetadata, aliasName, queryBuilder, entityType, value, condition,
propertyName, attribute);
}
}
}
else
{
queryBuilder.append(" ");
queryBuilder.append(o);
queryBuilder.append(" ");
}
}
}
else
{
queryBuilder.append(aliasName);
queryBuilder.append(".");
queryBuilder.append(((AbstractAttribute) entityMetadata.getIdAttribute()).getJPAColumnName());
queryBuilder.append(" ");
queryBuilder.append("IN(");
int count = 0;
Attribute col = entityMetadata.getIdAttribute();
boolean isString = isStringProperty(entityType, col);
for (String key : primaryKeys)
{
appendStringPrefix(queryBuilder, isString);
queryBuilder.append(key);
appendStringPrefix(queryBuilder, isString);
if (++count != primaryKeys.size())
{
queryBuilder.append(",");
}
else
{
queryBuilder.append(")");
}
}
}
}
/**
*
* @param entityMetadata
* @param aliasName
* @param queryBuilder
* @param entityType
* @param value
* @param condition
* @param propertyName
* @param attribute
*/
private void addClause(EntityMetadata entityMetadata, String aliasName, StringBuilder queryBuilder,
EntityType entityType, Object value, String condition, String propertyName, Attribute attribute)
{
boolean isString = isStringProperty(entityType, attribute);
// queryBuilder.append(aliasName);
// queryBuilder.append(".");
queryBuilder.append(StringUtils.replace(((AbstractAttribute) attribute).getJPAColumnName(), aliasName,
aliasName));
queryBuilder.append(" ");
queryBuilder.append(condition);
if (condition.equalsIgnoreCase("like"))
{
queryBuilder.append("%");
}
queryBuilder.append(" ");
if (condition.equalsIgnoreCase("IN"))
{
buildINClause(queryBuilder, value, isString);
}
else
{
appendStringPrefix(queryBuilder, isString);
queryBuilder.append(value);
appendStringPrefix(queryBuilder, isString);
}
}
/**
*
* @param queryBuilder
* @param value
* @param isString
*/
private void buildINClause(StringBuilder queryBuilder, Object value, boolean isString)
{
if (List.class.isAssignableFrom(value.getClass()) || Set.class.isAssignableFrom(value.getClass()))
{
queryBuilder.append(" (");
Collection collection = ((Collection) value);
for (Object obj : collection)
{
if (isString)
{
appendStringPrefix(queryBuilder, isString);
}
queryBuilder.append(obj.toString());
if (isString)
{
appendStringPrefix(queryBuilder, isString);
}
queryBuilder.append(",");
}
if (!collection.isEmpty())
{
queryBuilder.deleteCharAt(queryBuilder.lastIndexOf(","));
}
queryBuilder.append(")");
}
else
{
queryBuilder.append(value.toString());
}
}
/**
* Append string prefix.
*
* @param queryBuilder
* the query builder
* @param isString
* the is string
*/
private void appendStringPrefix(StringBuilder queryBuilder, boolean isString)
{
if (isString)
{
queryBuilder.append("'");
}
}
/**
* Sets the conditions.
*
* @param q
* the new conditions
*/
public void setConditions(Queue q)
{
this.conditions = q;
}
/**
* Sets the filter.
*
* @param filter
* the new filter
*/
public void setFilter(String filter)
{
this.filter = filter;
}
/**
* Populate relations.
*
* @param relations
* the relations
* @param o
* the o
* @return the map
*/
private Map<String, Object> populateRelations(List<String> relations, Object[] o)
{
Map<String, Object> relationVal = new HashMap<String, Object>(relations.size());
int counter = 1;
for (String r : relations)
{
relationVal.put(r, o[counter++]);
}
return relationVal;
}
/*
* (non-Javadoc)
*
* @see
* com.impetus.kundera.persistence.EntityReader#findById(java.lang.Object,
* com.impetus.kundera.metadata.model.EntityMetadata, java.util.List,
* com.impetus.kundera.client.Client)
*/
@Override
public EnhanceEntity findById(Object primaryKey, EntityMetadata m, Client client)
{
List<String> relationNames = m.getRelationNames();
if (relationNames != null && !relationNames.isEmpty())
{
Set<String> keys = new HashSet<String>(1);
keys.add(primaryKey.toString());
String query = getSqlQueryFromJPA(m, relationNames, keys);
List<EnhanceEntity> results = populateEnhanceEntities(m, relationNames, client, query);
return results != null && !results.isEmpty() ? results.get(0) : null;
}
else
{
Object o;
try
{
o = client.find(m.getEntityClazz(), primaryKey);
}
catch (Exception e)
{
throw new PersistenceException(e);
}
return o != null ? new EnhanceEntity(o, getId(o, m), null) : null;
}
// return super.findById(primaryKey, m, client);
}
/**
* Checks if is string property.
*
* @param m
* the m
* @param fieldName
* the field name
* @return true, if is string property
*/
private boolean isStringProperty(EntityType entityType, Attribute attribute)
{
String discriminatorColumn = ((AbstractManagedType) entityType).getDiscriminatorColumn();
if (attribute.getName().equals(discriminatorColumn))
{
return true;
}
return attribute != null ? ((AbstractAttribute) attribute).getBindableJavaType().isAssignableFrom(String.class)
|| ((AbstractAttribute) attribute).getBindableJavaType().isAssignableFrom(Character.class)
|| ((AbstractAttribute) attribute).getBindableJavaType().isAssignableFrom(char.class)
|| ((AbstractAttribute) attribute).getBindableJavaType().isAssignableFrom(Date.class)
|| ((AbstractAttribute) attribute).getBindableJavaType().isAssignableFrom(java.util.Date.class) : false;
}
}