gt: 				eq = eq.ignoreCase();
context: /**
   * This Source Code Form is subject to the terms of the Mozilla Public License,
   * v. 2.0. If a copy of the MPL was not distributed with this file, You can
   * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
   * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
   *
   * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
   * graphic logo is a trademark of OpenMRS Inc.
   */
  package org.openmrs.api.db.hibernate;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Set;
  import java.util.Vector;
  
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.collections.Transformer;
  import org.apache.commons.lang.StringUtils;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  import org.hibernate.Criteria;
  import org.hibernate.FlushMode;
  import org.hibernate.Query;
  import org.hibernate.SQLQuery;
  import org.hibernate.SessionFactory;
  import org.hibernate.criterion.Criterion;
  import org.hibernate.criterion.MatchMode;
  import org.hibernate.criterion.Order;
  import org.hibernate.criterion.Projections;
  import org.hibernate.criterion.Restrictions;
  import org.hibernate.criterion.SimpleExpression;
  import org.hibernate.transform.DistinctRootEntityResultTransformer;
  import org.openmrs.Concept;
  import org.openmrs.ConceptAnswer;
  import org.openmrs.ConceptAttribute;
  import org.openmrs.ConceptClass;
  import org.openmrs.ConceptComplex;
  import org.openmrs.ConceptDatatype;
  import org.openmrs.ConceptDescription;
  import org.openmrs.ConceptMap;
  import org.openmrs.ConceptMapType;
  import org.openmrs.ConceptName;
  import org.openmrs.ConceptNameTag;
  import org.openmrs.ConceptNumeric;
  import org.openmrs.ConceptProposal;
  import org.openmrs.ConceptReferenceTerm;
  import org.openmrs.ConceptReferenceTermMap;
  import org.openmrs.ConceptSearchResult;
  import org.openmrs.ConceptSet;
  import org.openmrs.ConceptSource;
  import org.openmrs.ConceptStopWord;
  import org.openmrs.Drug;
  import org.openmrs.DrugIngredient;
  import org.openmrs.ConceptAttributeType;
  import org.openmrs.OpenmrsObject;
  import org.openmrs.api.APIException;
  import org.openmrs.api.ConceptService;
  import org.openmrs.api.context.Context;
  import org.openmrs.api.db.ConceptDAO;
  import org.openmrs.api.db.DAOException;
  import org.openmrs.api.db.hibernate.search.LuceneQuery;
  import org.openmrs.collection.ListPart;
  import org.openmrs.util.ConceptMapTypeComparator;
  import org.openmrs.util.OpenmrsConstants;
  
  /**
   * The Hibernate class for Concepts, Drugs, and related classes. <br>
   * <br>
   * Use the {@link ConceptService} to access these methods
   * 
   * @see ConceptService
   */
  public class HibernateConceptDAO implements ConceptDAO {
  	
  	protected final Log log = LogFactory.getLog(getClass());
  	
  	private SessionFactory sessionFactory;
  	
  	/**
  	 * Sets the session factory
  	 * 
  	 * @param sessionFactory
  	 */
  	public void setSessionFactory(SessionFactory sessionFactory) {
  		this.sessionFactory = sessionFactory;
  	}
  	
  	/**
  	 * @see org.openmrs.api.db.ConceptDAO#getConceptComplex(java.lang.Integer)
  	 */
  	public ConceptComplex getConceptComplex(Integer conceptId) {
  		ConceptComplex cc;
  		Object obj = sessionFactory.getCurrentSession().get(ConceptComplex.class, conceptId);
  		// If Concept has already been read & cached, we may get back a Concept instead of
  		// ConceptComplex.  If this happens, we need to clear the object from the cache
  		// and re-fetch it as a ConceptComplex
  		if (obj != null && !obj.getClass().equals(ConceptComplex.class)) {
  			// remove from cache
  			sessionFactory.getCurrentSession().evict(obj);
  			// session.get() did not work here, we need to perform a query to get a ConceptComplex
  			Query query = sessionFactory.getCurrentSession().createQuery("from ConceptComplex where conceptId = :conceptId")
  			        .setParameter("conceptId", conceptId);
  			obj = query.uniqueResult();
  		}
  		cc = (ConceptComplex) obj;
  		
  		return cc;
  	}
  	
  	/**
  	 * @see org.openmrs.api.db.ConceptDAO#saveConcept(org.openmrs.Concept)
  	 */
  	public Concept saveConcept(Concept concept) throws DAOException {
  		if ((concept.getConceptId() != null) && (concept.getConceptId() > 0)) {
  			// this method checks the concept_numeric, concept_derived, etc tables
  			// to see if a row exists there or not.  This is needed because hibernate
  			// doesn't like to insert into concept_numeric but update concept in the
  			// same go.  It assumes that its either in both tables or no tables
  			insertRowIntoSubclassIfNecessary(concept);
  		}
  		
  		sessionFactory.getCurrentSession().saveOrUpdate(concept);
  		return concept;
  	}
  	
  	/**
  	 * Convenience method that will check this concept for subtype values (ConceptNumeric,
  	 * ConceptDerived, etc) and insert a line into that subtable if needed. This prevents a
  	 * hibernate ConstraintViolationException
  	 * 
  	 * @param concept the concept that will be inserted
  	 */
  	private void insertRowIntoSubclassIfNecessary(Concept concept) {
  		
  		// check the concept_numeric table
  		if (concept instanceof ConceptNumeric) {
  			
  			String select = "SELECT 1 from concept_numeric WHERE concept_id = :conceptId";
  			Query query = sessionFactory.getCurrentSession().createSQLQuery(select);
  			query.setInteger("conceptId", concept.getConceptId());
  			
  			// Converting to concept numeric:  A single concept row exists, but concept numeric has not been populated yet.
  			if (query.uniqueResult() == null) {
  				// we have to evict the current concept out of the session because
  				// the user probably had to change the class of this object to get it
  				// to now be a numeric
  				// (must be done before the "insert into...")
  				sessionFactory.getCurrentSession().clear();
  				
  				//Just in case this was changed from concept_complex to numeric
  				//We need to add a delete line for each concept sub class that is not concept_numeric
  				deleteSubclassConcept("concept_complex", concept.getConceptId());
  				
  				String insert = "INSERT INTO concept_numeric (concept_id, precise) VALUES (:conceptId, false)";
  				query = sessionFactory.getCurrentSession().createSQLQuery(insert);
  				query.setInteger("conceptId", concept.getConceptId());
  				query.executeUpdate();
  				
  			} else {
  				// Converting from concept numeric:  The concept and concept numeric rows both exist, so we need to delete concept_numeric.
  				
  				// concept is changed from numeric to something else
  				// hence row should be deleted from the concept_numeric
  				if (!concept.isNumeric()) {
  					deleteSubclassConcept("concept_numeric", concept.getConceptId());
  				} else {
  					// it is indeed numeric now... don't delete
  				}
  			}
  		}
  		// check the concept complex table
  		else if (concept instanceof ConceptComplex) {
  			
  			String select = "SELECT 1 FROM concept_complex WHERE concept_id = :conceptId";
  			Query query = sessionFactory.getCurrentSession().createSQLQuery(select);
  			query.setInteger("conceptId", concept.getConceptId());
  			
  			// Converting to concept complex:  A single concept row exists, but concept complex has not been populated yet.
  			if (query.uniqueResult() == null) {
  				// we have to evict the current concept out of the session because
  				// the user probably had to change the class of this object to get it
  				// to now be a ConceptComplex
  				// (must be done before the "insert into...")
  				sessionFactory.getCurrentSession().clear();
  				
  				//Just in case this was changed from concept_numeric to complex
  				//We need to add a delete line for each concept sub class that is not concept_complex
  				deleteSubclassConcept("concept_numeric", concept.getConceptId());
  				
  				// Add an empty row into the concept_complex table
  				String insert = "INSERT INTO concept_complex (concept_id) VALUES (:conceptId)";
  				query = sessionFactory.getCurrentSession().createSQLQuery(insert);
  				query.setInteger("conceptId", concept.getConceptId());
  				query.executeUpdate();
  				
  			} else {
  				// Converting from concept complex:  The concept and concept complex rows both exist, so we need to delete the concept_complex row.
  				// no stub insert is needed because either a concept row doesn't exist OR a concept_complex row does exist
  				
  				// concept is changed from complex to something else
  				// hence row should be deleted from the concept_complex
  				if (!concept.isComplex()) {
  					deleteSubclassConcept("concept_complex", concept.getConceptId());
  				} else {
  					// it is indeed numeric now... don't delete
  				}
  			}
  		}
  	}
  	
  	/**
  	 * Deletes a concept from a sub class table
  	 * 
  	 * @param tableName the sub class table name
  	 * @param conceptId the concept id
  	 */
  	private void deleteSubclassConcept(String tableName, Integer conceptId) {
  		String delete = "DELETE FROM " + tableName + " WHERE concept_id = :conceptId";
  		Query query = sessionFactory.getCurrentSession().createSQLQuery(delete);
  		query.setInteger("conceptId", conceptId);
  		query.executeUpdate();
  	}
  	
  	/**
  	 * @see org.openmrs.api.db.ConceptDAO#purgeConcept(org.openmrs.Concept)
  	 */
  	public void purgeConcept(Concept concept) throws DAOException {
  		sessionFactory.getCurrentSession().delete(concept);
  	}
  	
  	/**
  	 * @see org.openmrs.api.db.ConceptDAO#getConcept(java.lang.Integer)
  	 */
  	public Concept getConcept(Integer conceptId) throws DAOException {
  		return (Concept) sessionFactory.getCurrentSession().get(Concept.class, conceptId);
  	}
  	
  	/**
  	 * @see org.openmrs.api.db.ConceptDAO#getConceptName(java.lang.Integer)
  	 */
  	public ConceptName getConceptName(Integer conceptNameId) throws DAOException {
  		return (ConceptName) sessionFactory.getCurrentSession().get(ConceptName.class, conceptNameId);
  	}
  	
  	/**
  	 * @see org.openmrs.api.db.ConceptDAO#getConceptAnswer(java.lang.Integer)
  	 */
  	public ConceptAnswer getConceptAnswer(Integer conceptAnswerId) throws DAOException {
  		return (ConceptAnswer) sessionFactory.getCurrentSession().get(ConceptAnswer.class, conceptAnswerId);
  	}
  	
  	/**
  	 * @see org.openmrs.api.db.ConceptDAO#getAllConcepts(java.lang.String, boolean, boolean)
  	 */
  	@SuppressWarnings("unchecked")
  	public List<Concept> getAllConcepts(String sortBy, boolean asc, boolean includeRetired) throws DAOException {
  		
  		boolean isNameField = false;
  		
  		try {
  			Concept.class.getDeclaredField(sortBy);
  		}
  		catch (NoSuchFieldException e) {
  			try {
  				ConceptName.class.getDeclaredField(sortBy);
  				isNameField = true;
  			}
  			catch (NoSuchFieldException e2) {
  				sortBy = "conceptId";
  			}
  		}
  		
  		String hql = "";
  		if (isNameField) {
  			hql += "select concept";
  		}
  		
  		hql += " from Concept as concept";
  		boolean hasWhereClause = false;
  		if (isNameField) {
  			hasWhereClause = true;
  			//This assumes every concept has a unique(avoid duplicates) fully specified name
  			//which should be true for a clean concept dictionary
  			hql += " left join concept.names as names where names.conceptNameType = 'FULLY_SPECIFIED'";
  		}
  		
  		if (!includeRetired) {
  			if (hasWhereClause) {
  				hql += " and";
  			} else {
  				hql += " where";
  			}
  			hql += " concept.retired = false";
  			
  		}
  		
  		if (isNameField) {
  			hql += " order by names." + sortBy;
  		} else {
  			hql += " order by concept." + sortBy;
  		}
  		
  		hql += asc ? " asc" : " desc";
  		Query query = sessionFactory.getCurrentSession().createQuery(hql);
  		return (List<Concept>) query.list();
  	}
  	
  	/**
  	 * @see org.openmrs.api.db.ConceptDAO#saveDrug(org.openmrs.Drug)
  	 */
  	public Drug saveDrug(Drug drug) throws DAOException {
  		sessionFactory.getCurrentSession().saveOrUpdate(drug);
  		return drug;
  	}
  	
  	/**
  	 * @see org.openmrs.api.db.ConceptDAO#getDrug(java.lang.Integer)
  	 */
  	public Drug getDrug(Integer drugId) throws DAOException {
  		return (Drug) sessionFactory.getCurrentSession().get(Drug.class, drugId);
  	}
  	
  	/**
  	 * @see org.openmrs.api.db.ConceptDAO#getDrugs(java.lang.String, org.openmrs.Concept, boolean)
  	 */
  	@SuppressWarnings("unchecked")
  	public List<Drug> getDrugs(String drugName, Concept concept, boolean includeRetired) throws DAOException {
  		Criteria searchCriteria = sessionFactory.getCurrentSession().createCriteria(Drug.class, "drug");
  		if (!includeRetired) {
  			searchCriteria.add(Restrictions.eq("drug.retired", false));
  		}
  		if (concept != null) {
  			searchCriteria.add(Restrictions.eq("drug.concept", concept));
  		}
  		if (drugName != null) {
  			SimpleExpression eq = Restrictions.eq("drug.name", drugName);
  			if (Context.getAdministrationService().isDatabaseStringComparisonCaseSensitive()) {
  