/*********************************************************************
 * Copyright © 2005 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.fw.ui.valuelist;

// Java Classes
import java.util.List;
import java.util.Map;
import java.util.Iterator;
import java.util.Collections;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

// Library Classes
import net.mlw.vlh.ValueListAdapter;
import net.mlw.vlh.ValueList;
import net.mlw.vlh.ValueListInfo;
import net.mlw.vlh.DefaultListBackedValueList;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.collections.comparators.NullComparator;
import org.apache.commons.collections.comparators.ComparatorChain;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.logging.Log;
import org.apache.struts.action.ActionForm;
import org.springframework.web.context.WebApplicationContext;

// Framework Classes
import gov.va.med.fw.service.pagination.PaginatedSearchService;
import gov.va.med.fw.service.pagination.SearchQueryInfo;
import gov.va.med.fw.model.AbstractEntity;
import gov.va.med.fw.persistent.MaxRecordsExceededException;
import gov.va.med.fw.ui.DelegatingActionUtils;
import gov.va.med.fw.ui.UIConstants;
import gov.va.med.fw.util.SortElement;
import gov.va.med.fw.util.InvalidConfigurationException;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.fw.service.MissingCapabilityException;
import gov.va.med.fw.service.TimeoutServiceException;
import gov.va.med.fw.service.InvalidArgumentException;

/**
 * This is a generic search ValueListAdapter.  It is called by the ValueListHandler to perform a search and return a
 * ValueList.  It will convert parameters from UI specific ActionForms and ValueListInfo objects to a generic
 * SearchQueryInfo object.  It will then call a PaginatedSearchService to perform the search.
 * <p/>
 * This class follows the template design pattern where all the core functionality is performed by this class, but
 * specific hooks have been provided for more specialized processing, if required. Specifically, there are two methods
 * that can overridden if needed if specific functionality is desired:
 * <p/>
 * <p/>
 * 1) <i>populateFormAttributes</i> can be overridden if the default implementation of BeanUtils.copyProperties is not
 * sufficient when copying form specific attributes to the specific SearchQueryInfo implementation.
 * <p/>
 * 2) <i>performPostQueryProcessing</i> can be overridden if any specific post-query processing is desired.  The default
 * implementation does nothing, but specific implementations may choose to modify returned data such as substituting
 * database values with text reference data lookups.
 *
 * @author Andrew Pach
 * @version 3.0
 */
public class SearchValueListAdapter extends AbstractEntity implements ValueListAdapter, UIConstants
{
    /**
     * An instance of serialVersionUID
     */
    private static final long serialVersionUID = -942293536461694530L;

    // ThreadLocal used to hold the adapter type.  This is needed since the adapter type is being
    // determined dynamically based on the results of the query.  If a ThreadLocal is not used, other
    // queries made by other users could change the value of the adapterType and unpredicable results
    // could occur.
    private InheritableThreadLocal adapterTypeThreadLocal = new InheritableThreadLocal();

    /**
     * The name of this class.
     */
    private static final String CLASS_NAME = SearchValueListAdapter.class.getName();

    /**
     * The key into the ValueListInfo filter map for the form.
     */
    public static final String FORM_KEY = CLASS_NAME + "formKey";

    /**
     * The key into the ValueListInfo filter map for the Http Request.
     */
    public static final String HTTP_REQUEST_KEY = CLASS_NAME + "httpRequestKey";

    /**
     * The key into the ValueListInfo filter map for the table Id.
     */
    public static final String TABLE_ID_KEY = CLASS_NAME + "tableIdKey";

    /**
     * A String to add to the table id to come up with the session key for caching the sort elements.
     */
    public static final String SORT_ELEMENTS_KEY = CLASS_NAME + "sortElements";

    /**
     * A String to add to the table id to come up with the session key for caching the results.
     */
    public static final String RESULTS_KEY = CLASS_NAME + "results";

    /**
     * A string to add to the table id to come up with the session key for determining the previous page.
     */
    public static final String PREV_PAGE_KEY = CLASS_NAME + "prevPage";
    
    
    public static final String PREV_PAGE_RESULTS_KEY = CLASS_NAME + "prevPageResults";

    /**
     * This search approach is based on the data retrieved.  Based on the size of data and the number of rows retrieved,
     * one of the other search approaches will be dynamically selected on the fly.  This is the prefered approach since
     * the the VM should not run out of memory and all memory will be read into memory when the resultant record set
     * isn't too large.
     */
    public String SEARCH_DATA_OPTIMIZED = SearchQueryInfo.SEARCH_DATA_OPTIMIZED;

    /**
     * This search approach will read all records in memory no matter how big the result set is. This approach should
     * only be used when the search criteria won't retrieve so many results that the VM will run out of memory. This
     * approach will also not display background data updates when the user is sorting or paging across the resultant
     * data.
     */
    public String SEARCH_READ_ALL = SearchQueryInfo.SEARCH_READ_ALL;

    /**
     * This search approach will only read one page's worth of data in memory at a time.  This approach will read the
     * initial page of data quicker than the "read all" approach, but is slightly slower when the user is sorting or
     * paging across data.
     */
    public String SEARCH_SINGLE_PAGE = SearchQueryInfo.SEARCH_SINGLE_PAGE;

    /**
     * The search optimization type to use.
     */
    private String searchOptimizationType = SEARCH_DATA_OPTIMIZED;

    /**
     * The number of entries per page.
     */
    private int numberPerPage = Integer.MAX_VALUE;

    /**
     * The maximum number of records that can be retrieved from a search.
     */
    private int maxAllowedRecords = Integer.MAX_VALUE;

    /**
     * The default sort elements.
     */
    private List defaultSortElements = null;

    /**
     * Determines whether the search should be case sensitive or not.  Default: false.
     */
    private boolean caseSensitive = false;

    /**
     * Determines whether search will be the default action when no action is specified.
     */
    private boolean defaultActionSearch = true;

    /**
     * If true, empty parameter string filters are skipped.  If false, empty filters are checked for null. Default:
     * false.
     */
    private boolean removeEmptyStrings = false;

    /**
     * The maximum number of estimated bytes allowed in memory per search for a full results set.
     */
    private int maxInMemoryEstimatedResultsSizeInBytes = -1;

    /**
     * The maximum number of sort columns that may be maintained when getting results.
     */
    private int maxSortColumns = 1;

    /**
     * The paginated search service.
     */
    private PaginatedSearchService paginatedSearchService;

    /**
     * The name of the SearchQueryInfo bean.
     */
    private String searchQueryInfoBeanName = null;

    /**
     * @throws ValueListException if there were any errors that occurred while retrieving the Value List.
     * @throws MaxRecordsExceededValueListException if the configured maximum number of retrieved records was exceeded.
     * @see net.mlw.vlh.ValueListAdapter#getValueList(String,net.mlw.vlh.ValueListInfo)
     */
    public ValueList getValueList(String name, ValueListInfo info) throws ValueListException,
        MaxRecordsExceededValueListException, MissingCapabilityValueListException, TimeoutValueListException,
        InvalidArgumentValueListException
    {
        try
        {
            Log logger = getLogger();
            if (logger.isDebugEnabled())
            {
                logger.debug("Getting value list for adapter \"" + name + "\".");
            }

            // Set paging defaults if none were specified
            if ((info.getPagingNumberPer() == Integer.MAX_VALUE) ||
                (info.getPagingNumberPer() <= 0))
            {
                info.setPagingNumberPer(numberPerPage);
            }

            // Get the filter map
            Map filterMap = info.getFilters();

            // Initialize the session keys
            final String SORT_ELEMENTS_SESSION_KEY = getSortElementsSessionKey(filterMap);
            final String RESULTS_SESSION_KEY = getResultsSessionKey(filterMap);
            final String PREV_PAGE_SESSION_KEY = getPrevPageSessionKey(filterMap);
            final String PREV_PAGE_DATA_SESSION_KEY = getPrevPageDataSessionKey(filterMap);

            // Get a handle to the request and session
            HttpServletRequest request = (HttpServletRequest)filterMap.get(HTTP_REQUEST_KEY);
            HttpSession session = request.getSession();

            // Determine what action the user is performing based on a caller key in request scope.
            // The request parameter is set in ExtendedDefaultLinkEncoder when building URL links
            // for the ValueList tags.  Default to doing a new search.
            String action = defaultActionSearch ? SearchQueryInfo.ACTION_SEARCH :
                SearchQueryInfo.ACTION_NONE;
            if (getAction(request) != null)
            {
                // User clicked one of the value list generated links (e.g. paging or sorting)
                action = getAction(request);
            }
            if (logger.isDebugEnabled())
            {
                logger.debug("Performing action: \"" + action + "\".");
            }

            // If a previous page was cached and we aren't doing a new search or going to
            // a new page, use the previous page.  This is in case we are performing an
            // action where a new page wasn't explicitly passed in, we don't want to return
            // back to the default page 1 again.
            Integer prevPage = getPrevPageFromSession(session, PREV_PAGE_SESSION_KEY);
            if ((prevPage != null) && (!action.equals(SearchQueryInfo.ACTION_SEARCH)) &&
                (!action.equals(SearchQueryInfo.ACTION_PAGE)))
            {
                info.setPagingPage(prevPage.intValue());
            }

            // Get any previously cached results (only if all the data was read previously)
            boolean resultsCached = false;
            List searchResults = getResultsFromSession(session, RESULTS_SESSION_KEY);
            if (searchResults != null)
            {
                resultsCached = true;
                info.setTotalNumberOfEntries(searchResults.size());
            }
            if (logger.isDebugEnabled())
            {
                logger.debug("Previous results found in cache flag: \"" + resultsCached + "\".");
            }

            // Assume a sort isn't required
            boolean inMemorySortRequired = false;
            List sortElements = getSortElements(info, session, SORT_ELEMENTS_SESSION_KEY);

            // If we have no cached results or if the user wants to refresh the data (i.e. they hit the search button),
            // then call the paginated search service to get new data
            if ((action.equals(SearchQueryInfo.ACTION_SEARCH)) || (!resultsCached))
            {
                if (logger.isDebugEnabled())
                {
                    logger.debug("We need to retrieve new data.");
                }

                // Get the form that has the query specific search parameters
                ActionForm form = (ActionForm)filterMap.get(FORM_KEY);

                // Get a search query info object that contains all query parameters based on the ValueListInfo
                // and the form.
                
                SearchQueryInfo searchQueryInfo =
                    getSearchQueryInfo(request, info, form, action, sortElements);

                // Call the paginated search service to perform the search
                searchResults = paginatedSearchService.search(searchQueryInfo);
                //System.out.println("ADD A PERSON - SearchValueListAdaptor.getValueList(): " + searchResults);

                // Perform post query processing
                searchResults = performPostQueryProcessing(searchResults, searchQueryInfo, info);
            	//System.out.println("SearchValueListAdapter::performPostQueryProcessing(): (after) searchResults= " + searchResults);

                // Set the sort columns and direction if we need to perform a post query sort later in this method.
                if (!searchQueryInfo.getSortPerformed())
                {
                    inMemorySortRequired = true;
                }

                // Update the total number of records retrieved
                info.setTotalNumberOfEntries(searchQueryInfo.getTotalNumberOfEntries());

                // See if all the data was read or just one page
                if (searchQueryInfo.getSearchTypePerformed() == null)
                {
                    throw new ValueListException(
                        "Paginated Search Service did not specify what type of search was performed.");
                }
                if (searchQueryInfo.getSearchTypePerformed().equals(SearchQueryInfo.SEARCH_READ_ALL))
                {
                    if (logger.isDebugEnabled())
                    {
                        logger.debug("All the results were retrieved so the data will be cached.");
                    }

                    // All the data was read and we have a session cache key so cache the search results
                    setResultsInSession(session, RESULTS_SESSION_KEY, searchResults);
                    resultsCached = true;
                }
                else
                {
                    if (logger.isDebugEnabled())
                    {
                        logger.debug(
                            "All the results were not retrieved so the data will NOT be cached.");
                    }

                    if ( SearchQueryInfo.SEARCH_SINGLE_PAGE.equals(getSearchOptimizationType()))
                    {
                        // use this for action select.
                        setResultsInSession(session, PREV_PAGE_DATA_SESSION_KEY, searchResults);
                    }
                    // All the data was not read or no session cache key was specified so do not cache
                    // the results
                    setResultsInSession(session, RESULTS_SESSION_KEY, null);
                    resultsCached = false;
                }
            }
            else
            {
                // The results were previously cached and the user is requesting a sort or page.
                // If a sort is requested, set the column and direction for later sorting in this method.
                if (action.equals(SearchQueryInfo.ACTION_SORT))
                {
                    inMemorySortRequired = true;
                }
            }

            // Perform an in-memory sort if the sort column map was set previously in this method.
            if (inMemorySortRequired)
            {
                performSort(searchResults, sortElements);
            }

            // Assume the paginated search service did sorting and paging for us so we won't need the handler
            // to do anything for us.
            setAdapterType(ValueListAdapter.DO_NOTHING);
            if (resultsCached)
            {
                setAdapterType(ValueListAdapter.DO_PAGE);
            }

            // Create a ValueList from the results and the ValueListInfo.
            ValueList returnValueList = getListBackedValueList(info, searchResults);

            // If a session table key was specified, update the session with the new sort elements.
            // This should occur right before returning the valuelist so we don't update the session when
            // an exception might still be thrown.
            if (SORT_ELEMENTS_SESSION_KEY != null)
            {
                setSortElementsInSession(session, SORT_ELEMENTS_SESSION_KEY, sortElements);
            }

            // Store the new page as the previous page for the next call.
            setPrevPageInSession(session, PREV_PAGE_SESSION_KEY, new Integer(info.getPagingPage()));

            if (logger.isDebugEnabled())
            {
                logger.debug("Current Sort Order: " + sortElements);
                logger.debug("Finished getting value list for adapter \"" + name + "\".");
            }

            // Return the final value list
            return returnValueList;
        }
        catch (MaxRecordsExceededException ex)
        {
            throw new MaxRecordsExceededValueListException(ex.getTotalRecords(), ex.getRecordLimit(), ex.getMessage(),
                ex);
        }
        catch (MissingCapabilityException ex)
        {
            throw new MissingCapabilityValueListException(ex.getMessage(), ex.getMissingCapability(), ex);
        }
        catch (TimeoutServiceException ex)
        {
            throw new TimeoutValueListException(ex.getMessage(), ex);
        }
        catch (InvalidArgumentException ex)
        {
            throw new InvalidArgumentValueListException(ex.getMessage(), ex);
        }
        catch (Exception ex)
        {
            throw new ValueListException(
                "An error occurred while attempting to get ValueList data.", ex);
        }
    }

    /**
     * Creates and returns a SearchQueryInfo object based on the contents of the passed in ValueListInfo.
     *
     * @param request The HttpServletRequest.  This is used to get the Spring application context.
     * @param valueListInfo query information contained within ValueListInfo
     * @param form The action form.
     * @param searchAction The user action being performed.  This should be one of the "ACTION" constants defined in the
     * SearchQueryInfo class.
     * @param sortElements The list of sort elements.
     *
     * @return new SearchQueryInfo object.
     * @throws ValueListException if the search query info bean name is not of type SearchQueryInfo.
     */
    protected SearchQueryInfo getSearchQueryInfo(HttpServletRequest request,
        ValueListInfo valueListInfo,
        ActionForm form, String searchAction, List sortElements) throws ValueListException
    {
        // Get a handle to the web application context
        WebApplicationContext ac =
            DelegatingActionUtils.findRequiredWebApplicationContext(request.getSession().getServletContext());

        // Get a handle to the specific SearchQueryInfo bean.
        Object beanComponent = ac.getBean(searchQueryInfoBeanName);
        if (!(beanComponent instanceof SearchQueryInfo))
        {
            throw new ValueListException(
                "The bean with name '" + searchQueryInfoBeanName + "' is of type '" +
                    beanComponent.getClass().getName() + "' and must be of type '" +
                    SearchQueryInfo.class.getName() +
                    "'.");
        }
        SearchQueryInfo searchQueryInfo = (SearchQueryInfo)beanComponent;

        // Populate many fields on the SearchQueryInfo bean.
        searchQueryInfo.setSearchOptimizationType(searchOptimizationType);
        searchQueryInfo.setNumberPerPage(numberPerPage);
        searchQueryInfo.setRemoveEmptyStrings(removeEmptyStrings);
        searchQueryInfo.setCaseSensitive(caseSensitive);
        searchQueryInfo.setPagingPage(valueListInfo.getPagingPage());
        searchQueryInfo.setSearchAction(searchAction);
        searchQueryInfo
            .setMaxInMemoryEstimatedResultsSizeInBytes(maxInMemoryEstimatedResultsSizeInBytes);
        searchQueryInfo.setMaxAllowedRecords(maxAllowedRecords);
        searchQueryInfo.setSortElements(sortElements);

        // Set the optional results that may have been passed in from the caller if no actual search is needed
        Map filterMap = valueListInfo.getFilters();
        searchQueryInfo.setResults((List)filterMap.get(RESULTS_KEY));

        // Populate any of the form specific attributes on the search query info object
        populateFormAttributes(form, searchQueryInfo);

        if (getLogger().isDebugEnabled())
        {
            getLogger().debug("SearchQueryInfo details: " + searchQueryInfo);
        }

        // Return the fully populated search query info object
        return searchQueryInfo;
    }


    /**
     * Populates the action form specific search criteria fields onto the SearchQueryInfo object. This implementation
     * uses BeanUtils.copyProperties to copy all fields that have matching getters and setters on the source and target
     * object respectively.  If a more specific implementation is needed, this method should be overridden.
     *
     * @param form The form that contains search parameters
     * @param searchQueryInfo The search query info object to populate with the search parameters.
     *
     * @throws ValueListException if there are any problems copying properties.
     */
    protected void populateFormAttributes(ActionForm form, SearchQueryInfo searchQueryInfo)
        throws ValueListException
    {
        try
        {
            if (form != null)
                BeanUtils.copyProperties(searchQueryInfo, form);
        }
        catch (Exception ex)
        {
            throw new ValueListException(
                "Unable to copy properties from FormBean to SearchQueryInfo.", ex);
        }
    }

    /**
     * 11593 in order to gain access to the session, created overloaded method so subclass can override using additional paramter
     * @param results
     * @param searchQueryInfo
     * @return
     * @throws ValueListException
     */
    protected List performPostQueryProcessing(List results, SearchQueryInfo searchQueryInfo, ValueListInfo info)
    throws ValueListException
    {
    	return results;
    }
    
   /**
     * Gets the sort elements to sort on. If the passed in valueListInfo contains a sorting column, it will be added to
     * any previous sort elements.  Otherwise, the defaultSortElements will be returned. The new list of sort elements
     * will be stored in session state.
     *
     * @param valueListInfo The value list info
     * @param session The HttpSession used to store and retrieve the previous sort order
     * @param sessionKey The session key to get the previous sort order
     *
     * @return The sort elements.
     */
    protected List getSortElements(ValueListInfo valueListInfo, HttpSession session,
        String sessionKey)
    {
        // Start with the default sort elements
        List sortElements = getDefaultSortElements();

        // If a session key was specified and the sort elements were previously cached, use them.
        if ((sessionKey != null) && (getSortElementsFromSession(session, sessionKey) != null))
        {
            sortElements = getSortElementsFromSession(session, sessionKey);
        }

        if ((valueListInfo.getSortingColumn() != null) &&
            (valueListInfo.getSortingColumn().length() > 0))
        {
            sortElements =
                updateSortElement(sortElements, new SortElement(valueListInfo.getSortingColumn(),
                    (valueListInfo.getSortingDirection().equals(ValueListInfo.DESCENDING) ?
                        SortElement.DESCENDING : SortElement.ASCENDING)));
        }
        else
        {
            // No new sort elements were specified (i.e. the user is performing a new search) so use the defaults.

            // If we wanted to use the previous sort elements and not the defaults, we could comment out the
            // following line which would use what came from the session (or the default if nothing was in the session).

            // For now, using the previously cached sort order since requirements are unclear whether this
            // should be cached.
            // sortElements = getDefaultSortElements();
        }

        // Trim the sort element list if we have more sort columns than allowed
        if (sortElements.size() > maxSortColumns)
        {
            for (int i = sortElements.size(); i > maxSortColumns; i--)
            {
                sortElements.remove(i - 1);
            }

        }

        // Return the sort elements
        return sortElements;
    }

    /**
     * Updates the sort element list by adding the passed in sort element to the beginning of the list. If that same
     * element was previously in the list, it will be removed at its old position.
     *
     * @param sortElements The list of sort elements.
     * @param sortElement The sort element to update.
     *
     * @return The updated list.
     */
    protected List updateSortElement(List sortElements, SortElement sortElement)
    {
        // Create a new updated list
        ArrayList updatedList = new ArrayList();

        // Add the passed in sort element
        updatedList.add(sortElement);

        // Go through the existing sort element list and add each element to the new list, as long as
        // it isn't the same sort element that was being updated.
        for (Iterator iterator = sortElements.iterator(); iterator.hasNext();)
        {
            SortElement holdSortElement = (SortElement)iterator.next();
            if (!sortElement.hasSameSortColumn(holdSortElement))
            {
                updatedList.add(holdSortElement);
            }
        }

        // Return the updated list
        return updatedList;
    }

    /**
     * Sorts the results list using the sort elements.
     *
     * @param results The results to sort
     * @param sortElements The sort elements
     */
    protected void performSort(List results, List sortElements)
    {
        if ((sortElements != null) && (sortElements.size() > 0) && (results != null) &&
            (results.size() > 0))
        {
            ComparatorChain chain = new ComparatorChain();
            for (Iterator iterator = sortElements.iterator(); iterator.hasNext();)
            {
                SortElement sortElement = (SortElement)iterator.next();
                chain.addComparator(
                    new BeanComparator(sortElement.getSortColumn(), new NullComparator()),
                    sortElement.getSortDirection().equals(SortElement.DESCENDING));
            }
            Collections.sort(results, chain);
        }
    }

    /**
     * Checks if all required properties are set.
     *
     * @throws InvalidConfigurationException if all required properties are not set.
     */
    public void afterPropertiesSet() throws InvalidConfigurationException
    {
        if (paginatedSearchService == null)
        {
            throw new InvalidConfigurationException("The paginated search service must be set.");
        }
        if (searchQueryInfoBeanName == null)
        {
            throw new InvalidConfigurationException("The search query info bean name must be set.");
        }
    }

    /**
     * Gets the sort elements session key based on the appropriate table Id in the filter map.
     *
     * @param filterMap The valueList filter map.
     *
     * @return The sort elements session key.
     */
    protected String getSortElementsSessionKey(Map filterMap)
    {
        return (String)filterMap.get(TABLE_ID_KEY) + SORT_ELEMENTS_KEY;
    }

    /**
     * Gets the results session key based on the appropriate table Id in the filter map.
     *
     * @param filterMap The valueList filter map.
     *
     * @return The results session key.
     */
    protected String getResultsSessionKey(Map filterMap)
    {
        return (String)filterMap.get(TABLE_ID_KEY) + RESULTS_KEY;
    }

    /**
     * Gets the previous page session key based on the appropriate table Id in the filter map.
     *
     * @param filterMap The valueList filter map.
     *
     * @return The previous page session key.
     */
    protected String getPrevPageSessionKey(Map filterMap)
    {
        return (String)filterMap.get(TABLE_ID_KEY) + PREV_PAGE_KEY;
    }

    /**
     * Gets the previous page session data key based on the appropriate table Id in the filter map.
     * @param filterMap
     * @return
     */
    protected String getPrevPageDataSessionKey(Map filterMap)
    {
        return (String)filterMap.get(TABLE_ID_KEY) + PREV_PAGE_RESULTS_KEY;
    }
    
    /**
     * Caches the sort elements in session.
     *
     * @param session The HttpSession.
     * @param sortElementsSessionKey The sort elements session key.
     * @param sortElements the sort elements to cache
     */
    public static void setSortElementsInSession(HttpSession session, String sortElementsSessionKey,
        List sortElements)
    {
        session.setAttribute(sortElementsSessionKey, sortElements);
    }

    /**
     * Caches the results in session.
     *
     * @param session The HttpSession.
     * @param resultsSessionKey The results session key.
     * @param results the results to cache
     */
    public static void setResultsInSession(HttpSession session, String resultsSessionKey,
        List results)
    {
    	session.setAttribute(resultsSessionKey, null);
        session.setAttribute(resultsSessionKey, results);
    }

    /**
     * Caches the previous page in session.
     *
     * @param session The HttpSession.
     * @param prevPageSessionKey The previous page session key.
     * @param prevPage the previous page to cache
     */
    public static void setPrevPageInSession(HttpSession session, String prevPageSessionKey,
        Integer prevPage)
    {
        
        session.setAttribute(prevPageSessionKey, prevPage);
    }

    /**
     * Gets the sort elements from the session.
     *
     * @param session The HttpSession
     * @param sortElementsSessionKey The session key.
     *
     * @return The sort elements.
     */
    public static List getSortElementsFromSession(HttpSession session, String sortElementsSessionKey)
    {
        return (List)session.getAttribute(sortElementsSessionKey);
    }

    /**
     * Gets the results from the session.
     *
     * @param session The HttpSession
     * @param resultsSessionKey The session key.
     *
     * @return The results.
     */
    public static List getResultsFromSession(HttpSession session, String resultsSessionKey)
    {
        return (List)session.getAttribute(resultsSessionKey);
    }

    /**
     * Gets the previous page from the session.
     *
     * @param session The HttpSession
     * @param prevPageSessionKey The session key.
     *
     * @return The previous page.
     */
    public static Integer getPrevPageFromSession(HttpSession session, String prevPageSessionKey)
    {
        return (Integer)session.getAttribute(prevPageSessionKey);
    }

    public static String getAction(HttpServletRequest request)
    {
        // Try getting the user action as an attribute
        String action = (String)request.getAttribute(ExtendedDefaultLinkEncoder.CALLER_KEY);

        // If an attribute isn't present, try getting it as a parameter
        if (StringUtils.isEmpty(action))
        {
            // Get the user action
            action = request.getParameter(ExtendedDefaultLinkEncoder.CALLER_KEY);
        }

        // Return the action
        return action;
    }

    /**
     * Sets an action to be taken.
     *
     * @param request The HttpServletRequest
     * @param action The action to set
     */
    public static void setAction(HttpServletRequest request, String action)
    {
        // Set the caller key action to "select"
        request.setAttribute(ExtendedDefaultLinkEncoder.CALLER_KEY, action);
    }

    /**
     * @param info value list info
     * @param list the list of results
     *
     * @return DefaultListBackValueList instance
     */
    public static ValueList getListBackedValueList(ValueListInfo info, List list)
    {
        return new DefaultListBackedValueList(list, info);
    }

    /**
     * Gets the value list adapter type.
     *
     * @see net.mlw.vlh.ValueListAdapter#getAdapterType()
     */
    public int getAdapterType()
    {
        Integer adapterTypeInteger = (Integer)adapterTypeThreadLocal.get();
        return (adapterTypeInteger == null) ? ValueListAdapter.DO_NOTHING :
            adapterTypeInteger.intValue();
    }

    /**
     * Sets the value list adatper type.  This doesn't need to be specified by the user since it will be automatically
     * set based on the outcome of a query.  However, searchOptimizationType can be set to determine what type of query
     * will be performed.
     * @param adapterType the adapter type.
     *
     * @see net.mlw.vlh.ValueListAdapter#getAdapterType()
     */
    public void setAdapterType(int adapterType)
    {
        adapterTypeThreadLocal.set(new Integer(adapterType));
    }

    /**
     * Sets the number of entries per page.
     *
     * @param numberPerPage The number of entries per page.
     */
    public void setNumberPerPage(int numberPerPage)
    {
        this.numberPerPage = numberPerPage;
    }

    /**
     * Gets the number of entries per page.
     *
     * @return Returns the number of entries per page.
     */
    public int getNumberPerPage()
    {
        return numberPerPage;
    }

    /**
     * Sets the default sort elements.
     *
     * @param defaultSortElements The default sort columns and directions to set.
     */
    public void setDefaultSortElements(List defaultSortElements)
    {
        this.defaultSortElements = defaultSortElements;
    }

    /**
     * Gets the default sort elements.
     *
     * @return Returns the default sort elements.
     */
    public List getDefaultSortElements()
    {
        if (defaultSortElements == null)
        {
            defaultSortElements = new ArrayList();
        }
        return defaultSortElements;
    }

    /**
     * Gets whether emptry strings for filter based on null or be skipped.
     *
     * @return Returns the remove empty strings attribute.
     */
    public boolean getRemoveEmptyStrings()
    {
        return removeEmptyStrings;
    }

    /**
     * If true, empty parameter string filters are skipped.  If false, empty filters are checked for null.
     *
     * @param removeEmptyStrings Whether empty strings should be removed.
     */
    public void setRemoveEmptyStrings(boolean removeEmptyStrings)
    {
        this.removeEmptyStrings = removeEmptyStrings;
    }

    /**
     * Sets whether the search should be case sensitive or not.
     *
     * @param caseSensitive case sensitive flag.
     */
    public void setCaseSensitive(boolean caseSensitive)
    {
        this.caseSensitive = caseSensitive;
    }

    /**
     * Gets whether the search will be case sensitive or not.
     *
     * @return the case sensitive flag.
     */
    public boolean getCaseSensitive()
    {
        return caseSensitive;
    }

    /**
     * Gets whether the default aciton is search or no action.
     *
     * @return True if the default action is search or false if not.
     */
    public boolean isDefaultActionSearch()
    {
        return defaultActionSearch;
    }

    /**
     * Sets whether the default action is search or not.
     *
     * @param defaultActionSearch If true, the default action will be search or no action if false.
     */
    public void setDefaultActionSearch(boolean defaultActionSearch)
    {
        this.defaultActionSearch = defaultActionSearch;
    }

    /**
     * Sets the search optimization type.  One of the SEARCH constants in this file should be used.
     *
     * @param searchOptimizationType The search optimization type.
     */
    public void setSearchOptimizationType(String searchOptimizationType)
    {
        this.searchOptimizationType = searchOptimizationType;
    }

    /**
     * Gets the search optimization type.
     *
     * @return The search optimization type.
     */
    public String getSearchOptimizationType()
    {
        return this.searchOptimizationType;
    }

    /**
     * Sets the maximum estimated size in bytes of the largest result set allowed in memory per query.
     *
     * @param bytes The number of bytes allowed.
     */
    public void setMaxInMemoryEstimatedResultsSizeInBytes(int bytes)
    {
        this.maxInMemoryEstimatedResultsSizeInBytes = bytes;
    }

    /**
     * Gets the maximum in memory estimated results size in bytes.
     *
     * @return The number of bytes allowed.
     */
    public int getMaxInMemoryEstimatedResultsSizeInBytes()
    {
        return maxInMemoryEstimatedResultsSizeInBytes;
    }

    /**
     * Sets the paginated search service that will be used to perform the search.
     *
     * @param paginatedSearchService The search service
     */
    public void setPaginatedSearchService(PaginatedSearchService paginatedSearchService)
    {
        this.paginatedSearchService = paginatedSearchService;
    }

    /**
     * Returns the paginated search service.
     *
     * @return The paginated search service.
     */
    public PaginatedSearchService getPaginatedSearchService()
    {
        return this.paginatedSearchService;
    }

    /**
     * Sets the search query info bean name.  This is used to create a specific search bean for this search. This bean
     * name should define a bean that extends the SearchQueryInfo bean.
     *
     * @param searchQueryInfoBeanName The bean name.
     */
    public void setSearchQueryInfoBeanName(String searchQueryInfoBeanName)
    {
        this.searchQueryInfoBeanName = searchQueryInfoBeanName;
    }

    /**
     * Gets the search query info bean name.
     *
     * @return The bean name.
     */
    public String getSearchQueryInfoBeanName()
    {
        return searchQueryInfoBeanName;
    }

    /**
     * Gets the maximum number of records that may be retrieved from a search.
     *
     * @return The maximum number of allowed records.
     */
    public int getMaxAllowedRecords()
    {
        return maxAllowedRecords;
    }

    /**
     * Sets the maximum number of records that may be retrieved from a search.
     *
     * @param maxAllowedRecords The maximum number of allowed records.
     */
    public void setMaxAllowedRecords(int maxAllowedRecords)
    {
        this.maxAllowedRecords = maxAllowedRecords;
    }

    /**
     * Gets the maximim number of sort columns that can be used when performing a search.
     *
     * @return The maximum number of sort columns.
     */
    public int getMaxSortColumns()
    {
        return maxSortColumns;
    }

    /**
     * Sets the maximum number of sort columns that can be used when performing a search.
     *
     * @param maxSortColumns The maximum number of sort columns
     */
    public void setMaxSortColumns(int maxSortColumns)
    {
        this.maxSortColumns = maxSortColumns;
    }

    /**
     * @see gov.va.med.fw.model.AbstractEntity#buildToString(org.apache.commons.lang.builder.ToStringBuilder)
     */
    protected void buildToString(ToStringBuilder builder)
    {
        builder.append("adapterType", getAdapterType());
        builder.append("numberPerPage", getNumberPerPage());
        builder.append("defaultSortElements", getDefaultSortElements());
        builder.append("removeEmptyStrings", getRemoveEmptyStrings());
        builder.append("caseSensitive", getCaseSensitive());
        builder.append("searchOptimizationType", getSearchOptimizationType());
        builder.append("maxInMemoryEstimatedResultsSizeInBytes",
            getMaxInMemoryEstimatedResultsSizeInBytes());
        builder.append("paginatedSearchService", getPaginatedSearchService());
        builder.append("searchQueryInfoBeanName", getSearchQueryInfoBeanName());
        builder.append("maxAllowedRecords", getMaxAllowedRecords());
        builder.append("maxSortColumns", getMaxSortColumns());
    }
}