org.openmrs.web.dwr.DWRConceptService.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.web.dwr.DWRConceptService.java

Source

/**
 * 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.web.dwr;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Concept;
import org.openmrs.ConceptAnswer;
import org.openmrs.ConceptClass;
import org.openmrs.ConceptDatatype;
import org.openmrs.ConceptDescription;
import org.openmrs.ConceptName;
import org.openmrs.ConceptNumeric;
import org.openmrs.ConceptReferenceTerm;
import org.openmrs.ConceptSearchResult;
import org.openmrs.ConceptSet;
import org.openmrs.ConceptSource;
import org.openmrs.Drug;
import org.openmrs.Field;
import org.openmrs.api.APIException;
import org.openmrs.api.ConceptService;
import org.openmrs.api.ConceptsLockedException;
import org.openmrs.api.FormService;
import org.openmrs.api.context.Context;
import org.openmrs.messagesource.MessageSourceService;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;
import org.openmrs.validator.ConceptReferenceTermValidator;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;

/**
 * This class exposes some of the methods in org.openmrs.api.ConceptService via the dwr package
 */
public class DWRConceptService {

    protected static final Log log = LogFactory.getLog(DWRConceptService.class);

    /**
     * Gets a list of conceptListItems matching the given arguments
     * 
     * @param phrase the concept name string to match against
     * @param includeRetired boolean if false, will exclude retired concepts
     * @param includeClassNames List of ConceptClasses to restrict to
     * @param excludeClassNames List of ConceptClasses to leave out of results
     * @param includeDatatypeNames List of ConceptDatatypes to restrict to
     * @param excludeDatatypeNames List of ConceptDatatypes to leave out of results
     * @param includeDrugConcepts Specifies if drugs with matching conceptNames should be included
     * @return a list of conceptListItems matching the given arguments
     */
    public List<Object> findConcepts(String phrase, boolean includeRetired, List<String> includeClassNames,
            List<String> excludeClassNames, List<String> includeDatatypeNames, List<String> excludeDatatypeNames,
            boolean includeDrugConcepts) {
        return findBatchOfConcepts(phrase, includeRetired, includeClassNames, excludeClassNames,
                includeDatatypeNames, excludeDatatypeNames, null, null);
    }

    /**
     * Gets a list of conceptListItems matching the given arguments
     * 
     * @param phrase the concept name string to match against
     * @param includeRetired boolean if false, will exclude retired concepts
     * @param includeClassNames List of ConceptClasses to restrict to
     * @param excludeClassNames List of ConceptClasses to leave out of results
     * @param includeDatatypeNames List of ConceptDatatypes to restrict to
     * @param excludeDatatypeNames List of ConceptDatatypes to leave out of results
     * @param start the beginning index
     * @param length the number of matching concepts to return
     * @return a list of conceptListItems matching the given arguments
     * @should return concept by given id if exclude and include lists are empty
     * @should return concept by given id if classname is included
     * @should not return concept by given id if classname is not included
     * @should not return concept by given id if classname is excluded
     * @should return concept by given id if datatype is included
     * @should not return concept by given id if datatype is not included
     * @should not return concept by given id if datatype is excluded
     * @should include
     * @since 1.8
     */
    public List<Object> findBatchOfConcepts(String phrase, boolean includeRetired, List<String> includeClassNames,
            List<String> excludeClassNames, List<String> includeDatatypeNames, List<String> excludeDatatypeNames,
            Integer start, Integer length) {
        //TODO factor out the reusable code in this and findCountAndConcepts methods to a single utility method
        // List to return
        // Object type gives ability to return error strings
        List<Object> objectList = new ArrayList<Object>();

        // TODO add localization for messages

        Locale defaultLocale = Context.getLocale();

        // get the list of locales to search on
        List<Locale> searchLocales = Context.getAdministrationService().getSearchLocales();

        // debugging output
        if (log.isDebugEnabled()) {
            StringBuffer searchLocalesString = new StringBuffer();
            for (Locale loc : searchLocales) {
                searchLocalesString.append(loc.toString() + " ");
            }
            log.debug("searching locales: " + searchLocalesString);
        }

        if (includeClassNames == null) {
            includeClassNames = new ArrayList<String>();
        }
        if (excludeClassNames == null) {
            excludeClassNames = new ArrayList<String>();
        }
        if (includeDatatypeNames == null) {
            includeDatatypeNames = new ArrayList<String>();
        }
        if (excludeDatatypeNames == null) {
            excludeDatatypeNames = new ArrayList<String>();
        }

        try {
            ConceptService cs = Context.getConceptService();
            List<ConceptSearchResult> searchResults = new ArrayList<ConceptSearchResult>();

            if (phrase.matches("\\d+")) {
                // user searched on a number. Insert concept with
                // corresponding conceptId
                Concept c = cs.getConcept(Integer.valueOf(phrase));
                if (c != null && (!c.isRetired() || includeRetired)) {
                    String conceptClassName = null;
                    if (c.getConceptClass() != null) {
                        conceptClassName = c.getConceptClass().getName();
                    }
                    String conceptDatatypeName = null;
                    if (c.getDatatype() != null) {
                        conceptDatatypeName = c.getDatatype().getName();
                    }
                    if ((includeClassNames.isEmpty() || includeClassNames.contains(conceptClassName))
                            && (excludeClassNames.isEmpty() || !excludeClassNames.contains(conceptClassName))
                            && (includeDatatypeNames.isEmpty()
                                    || includeDatatypeNames.contains(conceptDatatypeName))
                            && (excludeDatatypeNames.isEmpty()
                                    || !excludeDatatypeNames.contains(conceptDatatypeName))) {
                        ConceptName cn = c.getName(defaultLocale);
                        ConceptSearchResult searchResult = new ConceptSearchResult(phrase, c, cn);
                        searchResults.add(searchResult);
                    }
                }
            }

            if (!StringUtils.isBlank(phrase)) {
                // turn classnames into class objects
                List<ConceptClass> includeClasses = new ArrayList<ConceptClass>();
                for (String name : includeClassNames) {
                    if (!"".equals(name)) {
                        includeClasses.add(cs.getConceptClassByName(name));
                    }
                }

                // turn classnames into class objects
                List<ConceptClass> excludeClasses = new ArrayList<ConceptClass>();
                for (String name : excludeClassNames) {
                    if (!"".equals(name)) {
                        excludeClasses.add(cs.getConceptClassByName(name));
                    }
                }

                // turn classnames into class objects
                List<ConceptDatatype> includeDatatypes = new ArrayList<ConceptDatatype>();
                for (String name : includeDatatypeNames) {
                    if (!"".equals(name)) {
                        includeDatatypes.add(cs.getConceptDatatypeByName(name));
                    }
                }

                // turn classnames into class objects
                List<ConceptDatatype> excludeDatatypes = new ArrayList<ConceptDatatype>();
                for (String name : excludeDatatypeNames) {
                    if (!"".equals(name)) {
                        excludeDatatypes.add(cs.getConceptDatatypeByName(name));
                    }
                }

                // perform the search
                searchResults.addAll(cs.getConcepts(phrase, searchLocales, includeRetired, includeClasses,
                        excludeClasses, includeDatatypes, excludeDatatypes, null, start, length));

                //TODO Should we still include drugs, if yes, smartly harmonize the paging between the two different DB tables
                //look ups to match the values of start and length not to go over the value of count of matches returned to the search widget
                //List<Drug> drugs = null;
                //if (includeDrugConcepts)
                //   drugs = cs.getDrugs(phrase, null, false, includeRetired, null, null);

            }

            if (searchResults.size() < 1) {
                objectList.add(Context.getMessageSourceService().getMessage("general.noMatchesFoundInLocale",
                        new Object[] { "<b>" + phrase + "</b>", OpenmrsUtil.join(searchLocales, ", ") },
                        Context.getLocale()));
            } else {
                // turn searchResults into concept list items
                // if user wants drug concepts included, append those
                for (ConceptSearchResult searchResult : searchResults) {
                    objectList.add(new ConceptListItem(searchResult));
                }
            }
        } catch (Exception e) {
            log.error("Error while finding concepts + " + e.getMessage(), e);
            objectList.add(
                    Context.getMessageSourceService().getMessage("Concept.search.error") + " - " + e.getMessage());
        }

        if (objectList.size() == 0) {
            objectList.add(Context.getMessageSourceService().getMessage("general.noMatchesFoundInLocale",
                    new Object[] { "<b>" + phrase + "</b>", defaultLocale }, Context.getLocale()));
        }

        return objectList;
    }

    /**
     * Get a {@link ConceptListItem} by its internal database id.
     * 
     * @param conceptId the id to look for
     * @return a {@link ConceptListItem} or null if conceptId is not found
     */
    public ConceptListItem getConcept(Integer conceptId) {
        Locale locale = Context.getLocale();
        ConceptService cs = Context.getConceptService();
        Concept c = cs.getConcept(conceptId);
        if (c == null) {
            return null;
        }

        ConceptName cn = c.getName(locale);

        return new ConceptListItem(c, cn, locale);
    }

    public List<ConceptListItem> findProposedConcepts(String text) {
        Locale locale = Context.getLocale();
        ConceptService cs = Context.getConceptService();

        List<Concept> concepts = cs.getProposedConcepts(text);
        List<ConceptListItem> cli = new ArrayList<ConceptListItem>();
        for (Concept c : concepts) {
            ConceptName cn = c.getName(locale);
            cli.add(new ConceptListItem(c, cn, locale));
        }
        return cli;
    }

    /**
     * Find a list of {@link ConceptListItem} or {@link ConceptDrugListItem}s that are answers to
     * the given question. The given question is determined by the given <code>conceptId</code>
     * 
     * @param text the text to search for within the answers
     * @param conceptId the conceptId of the question concept
     * @param includeVoided (this argument is ignored now. searching for voided answers is not
     *            logical)
     * @param includeDrugConcepts if true, drug concepts are searched too
     * @return list of {@link ConceptListItem} or {@link ConceptDrugListItem} answers that match the
     *         query
     * @throws Exception if given conceptId is not found
     * @should not fail if the specified concept has no answers (regression test for TRUNK-2807)
     * @should search for concept answers in all search locales
     * @should not return duplicates
     */
    public List<Object> findConceptAnswers(String text, Integer conceptId, boolean includeVoided,
            boolean includeDrugConcepts) throws Exception {

        if (includeVoided) {
            throw new APIException("You should not include voideds in the search.");
        }

        ConceptService cs = Context.getConceptService();

        Concept concept = cs.getConcept(conceptId);

        if (concept == null) {
            throw new Exception("Unable to find a concept with id: " + conceptId);
        }

        List<ConceptSearchResult> searchResults = new ArrayList<ConceptSearchResult>();
        List<Locale> locales = Context.getAdministrationService().getSearchLocales();

        for (Locale lc : locales) {
            List<ConceptSearchResult> results = cs.findConceptAnswers(text, lc, concept);
            if (results != null) {
                searchResults.addAll(results);
            }
        }

        List<Drug> drugAnswers = new ArrayList<Drug>();
        for (ConceptAnswer conceptAnswer : concept.getAnswers(false)) {
            if (conceptAnswer.getAnswerDrug() != null) {
                drugAnswers.add(conceptAnswer.getAnswerDrug());
            }
        }

        List<Object> items = new ArrayList<Object>();
        Set<Integer> uniqueItems = new HashSet<Integer>();
        for (ConceptSearchResult searchResult : searchResults) {
            if (!uniqueItems.add(searchResult.getConcept().getConceptId())) {
                continue; //Skip already added items
            }

            items.add(new ConceptListItem(searchResult));
            // add drugs for concept if desired
            if (includeDrugConcepts) {
                Integer classId = searchResult.getConcept().getConceptClass().getConceptClassId();
                if (classId.equals(OpenmrsConstants.CONCEPT_CLASS_DRUG)) {
                    for (Drug d : cs.getDrugsByConcept(searchResult.getConcept())) {
                        if (drugAnswers.contains(d)) {
                            items.add(new ConceptDrugListItem(d, Context.getLocale()));
                        }
                    }
                }
            }
        }

        return items;
    }

    public List<Object> getConceptSet(Integer conceptId) {
        Locale locale = Context.getLocale();
        ConceptService cs = Context.getConceptService();
        FormService fs = Context.getFormService();

        Concept concept = cs.getConcept(conceptId);

        List<Object> returnList = new ArrayList<Object>();

        if (concept.isSet()) {
            for (ConceptSet set : concept.getConceptSets()) {
                Field field = null;
                ConceptName cn = set.getConcept().getName(locale);
                ConceptDescription description = set.getConcept().getDescription(locale);
                if (description != null) {
                    for (Field f : fs.getFieldsByConcept(set.getConcept())) {
                        if (OpenmrsUtil.nullSafeEquals(f.getName(), cn.getName())
                                && OpenmrsUtil.nullSafeEquals(f.getDescription(), description.getDescription())
                                && f.isSelectMultiple().equals(false)) {
                            field = f;
                        }
                    }
                }

                if (field == null) {
                    returnList.add(new ConceptListItem(set.getConcept(), cn, locale));
                } else {
                    returnList.add(new FieldListItem(field, locale));
                }
            }
        }

        return returnList;
    }

    public List<ConceptListItem> getQuestionsForAnswer(Integer conceptId) {
        Locale locale = Context.getLocale();
        ConceptService cs = Context.getConceptService();

        Concept concept = cs.getConcept(conceptId);

        List<Concept> concepts = cs.getConceptsByAnswer(concept);

        List<ConceptListItem> items = new ArrayList<ConceptListItem>();
        for (Concept c : concepts) {
            ConceptName cn = c.getName(locale);
            items.add(new ConceptListItem(c, cn, locale));
        }

        return items;
    }

    public ConceptDrugListItem getDrug(Integer drugId) {
        Locale locale = Context.getLocale();
        ConceptService cs = Context.getConceptService();
        Drug d = cs.getDrug(drugId);

        return d == null ? null : new ConceptDrugListItem(d, locale);
    }

    public List<Object> getDrugs(Integer conceptId, boolean showConcept) {
        Locale locale = Context.getLocale();
        ConceptService cs = Context.getConceptService();
        Concept concept = cs.getConcept(conceptId);

        List<Object> items = new ArrayList<Object>();

        // Add this concept as the first option in the list
        // If there are no drugs to choose from, this will be automatically
        // selected
        // by the openmrsSearch.fillTable(objs) function
        if (showConcept) {
            ConceptDrugListItem thisConcept = new ConceptDrugListItem(null, conceptId,
                    concept.getName(locale, false).getName());
            items.add(thisConcept);
        }

        // find drugs for this concept
        List<Drug> drugs = cs.getDrugsByConcept(concept);

        // if there are drugs to choose from, add some instructions
        if (drugs.size() > 0 && showConcept) {
            items.add("Or choose a form of " + concept.getName(locale, false).getName());
        }

        // miniaturize our drug objects
        for (Drug drug : drugs) {
            items.add(new ConceptDrugListItem(drug, locale));
        }

        return items;
    }

    public List<Object> findDrugs(String phrase, boolean includeRetired) throws APIException {
        if (includeRetired) {
            throw new APIException("you.should.not.included.voideds", (Object[]) null);
        }
        Locale locale = Context.getLocale();
        ConceptService cs = Context.getConceptService();

        List<Object> items = new ArrayList<Object>();

        // find drugs for this concept
        Set<Drug> drugs = new HashSet<Drug>();

        // also find drugs by given phrase, assuming that 
        // this phrase is a list of concept words
        drugs.addAll(cs.getDrugs(phrase));

        // miniaturize our drug objects
        for (Drug drug : drugs) {
            items.add(new ConceptDrugListItem(drug, locale));
        }

        return items;
    }

    public boolean isValidNumericValue(Float value, Integer conceptId) {
        ConceptNumeric conceptNumeric = Context.getConceptService().getConceptNumeric(conceptId);

        return OpenmrsUtil.isValidNumericValue(value, conceptNumeric);
    }

    public String getConceptNumericUnits(Integer conceptId) {
        ConceptNumeric conceptNumeric = Context.getConceptService().getConceptNumeric(conceptId);

        return conceptNumeric.getUnits();
    }

    public List<ConceptListItem> getAnswersForQuestion(Integer conceptId) {
        List<ConceptListItem> ret = new ArrayList<ConceptListItem>();
        Concept c = Context.getConceptService().getConcept(conceptId);
        Collection<ConceptAnswer> answers = c.getAnswers(false);
        // TODO: deal with concept answers (e.g. drug) whose answer concept is null. (Not sure if this actually ever happens)
        Locale locale = Context.getLocale();
        for (ConceptAnswer ca : answers) {
            if (ca.getAnswerConcept() != null) {
                ConceptName cn = ca.getAnswerConcept().getName(locale);
                ret.add(new ConceptListItem(ca.getAnswerConcept(), cn, locale));
            }
        }
        return ret;
    }

    /**
     * Converts the datatype of a concept that already has Obs referencing it from boolean to coded
     * to support addition of more coded answers
     * 
     * @param conceptId the conceptId of the concept to be converted
     * @return String to act as a signal if successfully converted or an error message
     */
    public String convertBooleanConceptToCoded(Integer conceptId) {

        try {
            Context.getConceptService()
                    .convertBooleanConceptToCoded(Context.getConceptService().getConcept(conceptId));
            //this particular message isn't displayed in the browser rather it acts as
            //a signal that the concept was successfully converted and should refresh page. 
            return "refresh";
        } catch (ConceptsLockedException cle) {
            log.error("Tried to save/convert concept while concepts were locked", cle);
            return Context.getMessageSourceService().getMessage("Concept.concepts.locked");
        } catch (APIException e) {
            log.error("Error while trying to change the datatype of concept", e);
            return Context.getMessageSourceService().getMessage("Concept.cannot.save");
        }
    }

    /**
     * Returns a map of results with the values as count of matches and a partial list of the
     * matching concepts (depending on values of start and length parameters) while the keys are are
     * 'count' and 'objectList' respectively, if the length parameter is not specified, then all
     * matches will be returned from the start index if specified.
     * 
     * @param phrase concept name or conceptId
     * @param includeRetired boolean if false, will exclude retired concepts
     * @param includeClassNames List of ConceptClasses to restrict to
     * @param excludeClassNames List of ConceptClasses to leave out of results
     * @param includeDatatypeNames List of ConceptDatatypes to restrict to
     * @param excludeDatatypeNames List of ConceptDatatypes to leave out of results
     * @param start the beginning index
     * @param length the number of matching concepts to return
     * @param getMatchCount Specifies if the count of matches should be included in the returned map
     * @return a map of results
     * @throws APIException
     * @since 1.8
     */
    public Map<String, Object> findCountAndConcepts(String phrase, boolean includeRetired,
            List<String> includeClassNames, List<String> excludeClassNames, List<String> includeDatatypeNames,
            List<String> excludeDatatypeNames, Integer start, Integer length, boolean getMatchCount)
            throws APIException {
        //Map to return
        Map<String, Object> resultsMap = new HashMap<String, Object>();
        List<Object> objectList = new ArrayList<Object>();

        // get the list of locales to search on
        List<Locale> searchLocales = Context.getAdministrationService().getSearchLocales();

        // debugging output
        if (log.isDebugEnabled()) {
            StringBuffer searchLocalesString = new StringBuffer();
            for (Locale loc : searchLocales) {
                searchLocalesString.append(loc.toString() + " ");
            }
            log.debug("searching locales: " + searchLocalesString);
        }

        if (includeClassNames == null) {
            includeClassNames = new ArrayList<String>();
        }
        if (excludeClassNames == null) {
            excludeClassNames = new ArrayList<String>();
        }
        if (includeDatatypeNames == null) {
            includeDatatypeNames = new ArrayList<String>();
        }
        if (excludeDatatypeNames == null) {
            excludeDatatypeNames = new ArrayList<String>();
        }

        try {
            ConceptService cs = Context.getConceptService();

            if (!StringUtils.isBlank(phrase)) {
                // turn classnames into class objects
                List<ConceptClass> includeClasses = new ArrayList<ConceptClass>();
                for (String name : includeClassNames) {
                    if (!"".equals(name)) {
                        includeClasses.add(cs.getConceptClassByName(name));
                    }
                }

                // turn classnames into class objects
                List<ConceptClass> excludeClasses = new ArrayList<ConceptClass>();
                for (String name : excludeClassNames) {
                    if (!"".equals(name)) {
                        excludeClasses.add(cs.getConceptClassByName(name));
                    }
                }

                // turn classnames into class objects
                List<ConceptDatatype> includeDatatypes = new ArrayList<ConceptDatatype>();
                for (String name : includeDatatypeNames) {
                    if (!"".equals(name)) {
                        includeDatatypes.add(cs.getConceptDatatypeByName(name));
                    }
                }

                // turn classnames into class objects
                List<ConceptDatatype> excludeDatatypes = new ArrayList<ConceptDatatype>();
                for (String name : excludeDatatypeNames) {
                    if (!"".equals(name)) {
                        excludeDatatypes.add(cs.getConceptDatatypeByName(name));
                    }
                }

                int matchCount = 0;
                if (getMatchCount) {
                    //get the count of matches
                    matchCount += cs.getCountOfConcepts(phrase, searchLocales, includeRetired, includeClasses,
                            excludeClasses, includeDatatypes, excludeDatatypes, null);
                    if (phrase.matches("\\d+")) {
                        // user searched on a number. Insert concept with
                        // corresponding conceptId
                        Concept c = cs.getConcept(Integer.valueOf(phrase));
                        if (c != null && (!c.isRetired() || includeRetired)) {
                            matchCount++;
                        }

                    }

                    //if (includeDrugs)
                    //   matchCount += cs.getCountOfDrugs(phrase, null, false, includeRetired);
                }

                //if we have any matches or this isn't the first ajax call when the caller
                //requests for the count
                if (matchCount > 0 || !getMatchCount) {
                    objectList.addAll(findBatchOfConcepts(phrase, includeRetired, includeClassNames,
                            excludeClassNames, includeDatatypeNames, excludeDatatypeNames, start, length));
                }

                resultsMap.put("count", matchCount);
                resultsMap.put("objectList", objectList);
            } else {
                resultsMap.put("count", 0);
                objectList.add(Context.getMessageSourceService().getMessage("searchWidget.noMatchesFound"));
            }

        } catch (Exception e) {
            log.error("Error while searching for concepts", e);
            objectList.clear();
            objectList.add(
                    Context.getMessageSourceService().getMessage("Concept.search.error") + " - " + e.getMessage());
            resultsMap.put("count", 0);
            resultsMap.put("objectList", objectList);
        }

        return resultsMap;
    }

    /**
     * Get a {@link ConceptReferenceTerm} by its internal database id.
     * 
     * @param conceptReferenceTermId the id to look for
     * @return a {@link ConceptReferenceTermListItem} or null if conceptReferenceTermId is not found
     */
    public ConceptReferenceTermListItem getConceptReferenceTerm(Integer conceptReferenceTermId) {
        ConceptReferenceTerm term = Context.getConceptService().getConceptReferenceTerm(conceptReferenceTermId);
        if (term == null) {
            return null;
        }

        return new ConceptReferenceTermListItem(term);
    }

    /**
     * Gets a list of conceptListItems matching the given arguments
     * 
     * @param phrase the string to search against
     * @param sourceId the id of concept source where to look up reference terms
     * @param start the beginning index
     * @param length the number of matching concept reference terms to return
     * @param includeRetired Specifies if retired concept reference terms should be included or not
     * @return a {@link List} of {@link ConceptReferenceTermListItem}
     */
    public List<Object> findBatchOfConceptReferenceTerms(String phrase, Integer sourceId, Integer start,
            Integer length, boolean includeRetired) {
        List<Object> objectList = new ArrayList<Object>();
        MessageSourceService mss = Context.getMessageSourceService();
        try {
            ConceptService cs = Context.getConceptService();
            List<ConceptReferenceTerm> terms = new ArrayList<ConceptReferenceTerm>();

            if (StringUtils.isEmpty(phrase)) {
                objectList.add(mss.getMessage("searchWidget.searchPhraseCannotBeNull"));
                return objectList;
            }
            ConceptSource source = null;
            if (sourceId != null) {
                source = cs.getConceptSource(sourceId);
            }
            terms.addAll(cs.getConceptReferenceTerms(phrase, source, start, length, includeRetired));

            if (terms.size() == 0) {
                objectList.add(mss.getMessage("general.noMatchesFound", new Object[] { "'" + phrase + "'" },
                        Context.getLocale()));
            } else {
                objectList = new ArrayList<Object>(terms.size());
                for (ConceptReferenceTerm term : terms) {
                    objectList.add(new ConceptReferenceTermListItem(term));
                }
            }
        } catch (Exception e) {
            log.error("Error while searching for concept reference terms", e);
            objectList.add(mss.getMessage("ConceptReferenceTerm.search.error") + " - " + e.getMessage());
        }

        return objectList;
    }

    /**
     * @param phrase query the string to match against the reference term names
     * @param sourceId the id for the concept source from which the terms should be looked up
     * @param includeRetired specifies if the retired terms should be included
     * @param start beginning index for the batch
     * @param length number of terms to return in the batch
     * @param getMatchCount Specifies if the count of matches should be included in the returned map
     * @return map with keys "count" and "objectList"
     * @throws APIException
     */
    public Map<String, Object> findCountAndConceptReferenceTerms(String phrase, Integer sourceId, Integer start,
            Integer length, boolean includeRetired, boolean getMatchCount) throws APIException {
        //Map to return
        Map<String, Object> resultsMap = new HashMap<String, Object>();
        List<Object> objectList = new ArrayList<Object>();
        try {
            ConceptService cs = Context.getConceptService();
            int conceptReferenceTermCount = 0;
            if (getMatchCount) {
                ConceptSource source = null;
                if (sourceId != null) {
                    source = cs.getConceptSource(sourceId);
                }
                conceptReferenceTermCount += cs.getCountOfConceptReferenceTerms(phrase, source, includeRetired);
            }
            //If we have any matches, load them or if this is not the first ajax call
            //for displaying the results on the first page, the getMatchCount is expected to be zero
            if (conceptReferenceTermCount > 0 || !getMatchCount) {
                objectList = findBatchOfConceptReferenceTerms(phrase, sourceId, start, length, includeRetired);
            }

            resultsMap.put("count", conceptReferenceTermCount);
            resultsMap.put("objectList", objectList);
        } catch (Exception e) {
            log.error("Error while searching for conceptReferenceTerms", e);
            objectList.clear();
            objectList.add(Context.getMessageSourceService().getMessage("ConceptReferenceTerm.search.error") + " - "
                    + e.getMessage());
            resultsMap.put("count", 0);
            resultsMap.put("objectList", objectList);
        }
        return resultsMap;
    }

    /**
     * Process calls to create new reference terms
     * 
     * @param code the unique code for the reference term
     * @param conceptSourceId the concept source for the term
     * @param name the unique name for the reference term
     * @return a list of error messages
     */
    public List<String> createConceptReferenceTerm(String code, Integer conceptSourceId, String name) {
        List<String> errors = new ArrayList<String>();
        MessageSourceService mss = Context.getMessageSourceService();
        ConceptService cs = Context.getConceptService();

        ConceptSource source = null;
        if (conceptSourceId != null) {
            source = cs.getConceptSource(conceptSourceId);
        }

        ConceptReferenceTerm term = new ConceptReferenceTerm();
        term.setCode(code);
        term.setName(name);
        term.setConceptSource(source);

        Errors bindErrors = new BindException(term, "term");
        new ConceptReferenceTermValidator().validate(term, bindErrors);
        if (bindErrors.hasErrors()) {
            for (ObjectError objectError : bindErrors.getAllErrors()) {
                errors.add(mss.getMessage(objectError.getCode()));
            }
        } else {
            try {
                cs.saveConceptReferenceTerm(term);
                return null;//indicates that the term was saved successfully
            } catch (APIException e) {
                errors.add(mss.getMessage("ConceptReferenceTerm.save.error"));
            }
        }

        return errors;
    }
}