Package org.dspace.browse

Source Code of org.dspace.browse.BrowseEngine

/*
* BrowseEngine.java
*
* Version: $Revision: 4365 $
*
* Date: $Date: 2009-10-05 23:52:42 +0000 (Mon, 05 Oct 2009) $
*
* Copyright (c) 2002-2009, The DSpace Foundation.  All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of the DSpace Foundation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/

package org.dspace.browse;

import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;

import org.apache.log4j.Logger;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.sort.SortOption;
import org.dspace.sort.OrderFormat;

/**
* This class does most of the actual grunt work of preparing a browse
* result.  It takes in to a couple of available methods (depending on your
* desired browse type) a BrowserScope object, and uses this to produce a
* BrowseInfo object which is sufficient to describe to the User Interface
* the results of the requested browse
*
* @author Richard Jones
*
*/
public class BrowseEngine
{
    /** the logger for this class */
    private static Logger log = Logger.getLogger(BrowseEngine.class);

    /** the browse scope which is the basis for our browse */
    private BrowserScope scope;

    /** the DSpace context */
    private Context context;

    /** The Data Access Object for the browse tables */
    private BrowseDAO dao;

    /** The Browse Index associated with the Browse Scope */
    private BrowseIndex browseIndex;

    /**
     * Create a new instance of the Browse engine, using the given DSpace
     * Context object.  This will automatically assign a Data Access Object
     * for the Browse Engine, based on the dspace.cfg setting for db.name
     *
     * @param context       the DSpace context
     * @throws BrowseException
     */
    public BrowseEngine(Context context)
        throws BrowseException
    {
        // set the context
        this.context = context;

        // prepare the data access object
        dao = BrowseDAOFactory.getInstance(context);
    }

    /**
     * Perform a standard browse, which will return a BrowseInfo
     * object that represents the results for the current page, the
     * total number of results, the range, and information to construct
     * previous and next links on any web page
     *
     * @param bs    the scope of the browse
     * @return      the results of the browse
     * @throws BrowseException
     */
    public BrowseInfo browse(BrowserScope bs)
        throws BrowseException
    {
        log.debug(LogManager.getHeader(context, "browse", ""));

        // first, load the browse scope into the object
        this.scope = bs;

        // since we use it so much, get the browse index out of the
        // scope and store as a member
        browseIndex = scope.getBrowseIndex();

        // now make the decision as to how to browse
        if (browseIndex.isMetadataIndex() && !scope.isSecondLevel())
        {
            // this is a single value browse type that has not gone to
            // the second level (i.e. authors, not items by a given author)
            return browseByValue(scope);
        }
        else
        {
            // this is the full browse type or a browse that has gone to
            // the second level
            return browseByItem(scope);
        }
    }

    /**
     * Perform a limited browse, which only returns the results requested,
     * without any extraneous information.  To perform a full browse, use
     * BrowseEngine.browse() above.  This supports Item browse only, and does
     * not currently support focus or values.  This method is used, for example,
     * to generate the Recently Submitted Items results.
     *
     * @param bs    the scope of the browse
     * @return      the results of the browse
     */
    public BrowseInfo browseMini(BrowserScope bs)
        throws BrowseException
    {
        log.info(LogManager.getHeader(context, "browse_mini", ""));

        // load the scope into the object
        this.scope = bs;

        // since we use it so much, get the browse index out of the
        // scope and store as a member
        browseIndex = scope.getBrowseIndex();

        // get the table name that we are going to be getting our data from
        dao.setTable(browseIndex.getTableName());

        // tell the browse query whether we are ascending or descending on the value
        dao.setAscending(scope.isAscending());

        // define a clause for the WHERE clause which will allow us to constraine
        // our browse to a specified community or collection
        if (scope.inCollection() || scope.inCommunity())
        {
            if (scope.inCollection())
            {
                Collection col = (Collection) scope.getBrowseContainer();
                dao.setContainerTable("collection2item");
                dao.setContainerIDField("collection_id");
                dao.setContainerID(col.getID());
            }
            else if (scope.inCommunity())
            {
                Community com = (Community) scope.getBrowseContainer();
                dao.setContainerTable("communities2item");
                dao.setContainerIDField("community_id");
                dao.setContainerID(com.getID());
            }
        }

        dao.setOffset(scope.getOffset());
        dao.setLimit(scope.getResultsPerPage());

        // assemble the ORDER BY clause
        String orderBy = browseIndex.getSortField(scope.isSecondLevel());
        if (scope.getSortBy() > 0)
        {
            orderBy = "sort_" + Integer.toString(scope.getSortBy());
        }
        dao.setOrderField(orderBy);

        // now run the query
        List results = dao.doQuery();

        // construct the mostly empty BrowseInfo object to pass back
        BrowseInfo browseInfo = new BrowseInfo(results, 0, scope.getResultsPerPage(), 0);

        // add the browse index to the Browse Info
        browseInfo.setBrowseIndex(browseIndex);

        // set the sort option for the Browse Info
        browseInfo.setSortOption(scope.getSortOption());

        // tell the Browse Info which way we are sorting
        browseInfo.setAscending(scope.isAscending());

        // tell the browse info what the container for the browse was
        if (scope.inCollection() || scope.inCommunity())
        {
            browseInfo.setBrowseContainer(scope.getBrowseContainer());
        }

        browseInfo.setResultsPerPage(scope.getResultsPerPage());

        browseInfo.setEtAl(scope.getEtAl());

        return browseInfo;
    }

    /**
     * Browse the archive by the full item browse mechanism.  This produces a
     * BrowseInfo object which contains full BrowseItem objects as its result
     * set.
     *
     * @param bs        the scope of the browse
     * @return          the results of the browse
     * @throws BrowseException
     */
    private BrowseInfo browseByItem(BrowserScope bs)
        throws BrowseException
    {
        log.info(LogManager.getHeader(context, "browse_by_item", ""));
        try
        {
            // get the table name that we are going to be getting our data from
            dao.setTable(browseIndex.getTableName());

            // tell the browse query whether we are ascending or descending on the value
            dao.setAscending(scope.isAscending());

            // assemble the value clause
            String rawValue = null;
            if (scope.hasFilterValue() && scope.isSecondLevel())
            {
                String value = scope.getFilterValue();
                rawValue = value;

                // make sure the incoming value is normalised
                value = OrderFormat.makeSortString(value, scope.getFilterValueLang(),
                            scope.getBrowseIndex().getDataType());

                dao.setAuthorityValue(scope.getAuthorityValue());

                // set the values in the Browse Query
                if (scope.isSecondLevel())
                {
                    dao.setFilterValueField("value");
                    dao.setFilterValue(rawValue);   
                }
                else
                {
                  dao.setFilterValueField("sort_value");
                  dao.setFilterValue(value);
                }
                dao.setFilterValuePartial(scope.getFilterValuePartial());

                // to apply the filtering, we need the distinct and map tables for the index
                dao.setFilterMappingTables(browseIndex.getDistinctTableName(),
                                           browseIndex.getMapTableName());
            }

            // define a clause for the WHERE clause which will allow us to constraine
            // our browse to a specified community or collection
            if (scope.inCollection() || scope.inCommunity())
            {
                if (scope.inCollection())
                {
                    Collection col = (Collection) scope.getBrowseContainer();
                    dao.setContainerTable("collection2item");
                    dao.setContainerIDField("collection_id");
                    dao.setContainerID(col.getID());
                }
                else if (scope.inCommunity())
                {
                    Community com = (Community) scope.getBrowseContainer();
                    dao.setContainerTable("communities2item");
                    dao.setContainerIDField("community_id");
                    dao.setContainerID(com.getID());
                }
            }

            // this is the total number of results in answer to the query
            int total = getTotalResults();

            // assemble the ORDER BY clause
            String orderBy = browseIndex.getSortField(scope.isSecondLevel());
            if (scope.getSortBy() > 0)
            {
                orderBy = "sort_" + Integer.toString(scope.getSortBy());
            }
            dao.setOrderField(orderBy);

            int offset = scope.getOffset();
            String rawFocusValue = null;
            if (offset < 1 && (scope.hasJumpToItem() || scope.hasJumpToValue() || scope.hasStartsWith()))
            {
                // We need to convert these to an offset for the actual browse query.
                // First, get a value that we can look up in the ordering field
                rawFocusValue = getJumpToValue();

                // make sure the incoming value is normalised
                String focusValue = normalizeJumpToValue(rawFocusValue);

                log.debug("browsing using focus: " + focusValue);

                // Now we have a value to focus on, we need to find out where it is
                String focusField = browseIndex.getSortField(scope.isSecondLevel());
                if (scope.getSortBy() > 0)
                {
                    focusField = "sort_" + Integer.toString(scope.getSortBy());
                }

                // Convert the focus value into an offset
                offset = getOffsetForValue(focusValue);
            }

            dao.setOffset(offset);

            // assemble the LIMIT clause
            dao.setLimit(scope.getResultsPerPage());

            // Holder for the results
            List results = null;

            // Does this browse have any contents?
            if (total > 0)
            {
                // now run the query
                results = dao.doQuery();

                // now, if we don't have any results, we are at the end of the browse.  This will
                // be because a starts_with value has been supplied for which we don't have
                // any items.
                if (results.size() == 0)
                {
                    // In this case, we will calculate a new offset for the last page of results
                    offset = total - scope.getResultsPerPage();
                    if (offset < 0)
                        offset = 0;

                    // And rerun the query
                    dao.setOffset(offset);
                    results = dao.doQuery();
                }
            }
            else
            {
                // No records, so make an empty list
                results = new ArrayList();
            }

            // construct the BrowseInfo object to pass back
//            BrowseInfo browseInfo = new BrowseInfo(results, position, total, offset);
            BrowseInfo browseInfo = new BrowseInfo(results, offset, total, offset);

            if (offset + scope.getResultsPerPage() < total)
            {
                browseInfo.setNextOffset(offset + scope.getResultsPerPage());
            }

            if (offset - scope.getResultsPerPage() > -1)
            {
                browseInfo.setPrevOffset(offset - scope.getResultsPerPage());
            }

            // add the browse index to the Browse Info
            browseInfo.setBrowseIndex(browseIndex);

            // set the sort option for the Browse Info
            browseInfo.setSortOption(scope.getSortOption());

            // tell the Browse Info which way we are sorting
            browseInfo.setAscending(scope.isAscending());

            // tell the Browse Info which level of browse we are at
            browseInfo.setBrowseLevel(scope.getBrowseLevel());

            // set the browse value if there is one
            browseInfo.setValue(rawValue);

            // set the browse authority key if there is one
            browseInfo.setAuthority(scope.getAuthorityValue());

            // set the focus value if there is one
            browseInfo.setFocus(rawFocusValue);

            if (scope.hasJumpToItem())
            {
                browseInfo.setFocusItem(scope.getJumpToItem());
            }

            // tell the browse info if it is working from a starts with parameter
            browseInfo.setStartsWith(scope.hasStartsWith());

            // tell the browse info what the container for the browse was
            if (scope.inCollection() || scope.inCommunity())
            {
                browseInfo.setBrowseContainer(scope.getBrowseContainer());
            }

            browseInfo.setResultsPerPage(scope.getResultsPerPage());

            browseInfo.setEtAl(scope.getEtAl());

            return browseInfo;
        }
        catch (SQLException e)
        {
            log.error("caught exception: ", e);
            throw new BrowseException(e);
        }
    }

    /**
     * Browse the archive by single values (such as the name of an author).  This
     * produces a BrowseInfo object that contains Strings as the results of
     * the browse
     *
     * @param bs        the scope of the browse
     * @return          the results of the browse
     * @throws BrowseException
     */
    private BrowseInfo browseByValue(BrowserScope bs)
        throws BrowseException
    {
        log.info(LogManager.getHeader(context, "browse_by_value", "focus=" + bs.getJumpToValue()));

        try
        {
            // get the table name that we are going to be getting our data from
            // this is the distinct table constrained to either community or collection
            dao.setTable(browseIndex.getDistinctTableName());

            // remind the DAO that this is a distinct value browse, so it knows what sort
            // of query to build
            dao.setDistinct(true);

            // tell the browse query whether we are ascending or descending on the value
            dao.setAscending(scope.isAscending());

            // set our constraints on community or collection
            if (scope.inCollection() || scope.inCommunity())
            {
                // Scoped browsing of distinct metadata requires the mapping
                // table to be specified.
                dao.setFilterMappingTables(null, browseIndex.getMapTableName());

                if (scope.inCollection())
                {
                    Collection col = (Collection) scope.getBrowseContainer();
                    dao.setContainerTable("collection2item");
                    dao.setContainerIDField("collection_id");
                    dao.setContainerID(col.getID());
                }
                else if (scope.inCommunity())
                {
                    Community com = (Community) scope.getBrowseContainer();
                    dao.setContainerTable("communities2item");
                    dao.setContainerIDField("community_id");
                    dao.setContainerID(com.getID());
                }
            }

            // this is the total number of results in answer to the query
            int total = getTotalResults(true);

            // set the ordering field (there is only one option)
            dao.setOrderField("sort_value");

            // assemble the focus clase if we are to have one
            // it will look like one of the following
            // - sort_value < myvalue
            // = sort_1 > myvalue
            dao.setJumpToField("sort_value");
            int offset = scope.getOffset();
            String rawFocusValue = null;
            if (offset < 1 && scope.hasJumpToValue() || scope.hasStartsWith())
            {
                String focusValue = getJumpToValue();

                // store the value to tell the Browse Info object which value we are browsing on
                rawFocusValue = focusValue;

                // make sure the incoming value is normalised
                focusValue = normalizeJumpToValue(focusValue);

                offset = getOffsetForDistinctValue(focusValue);
            }


            // assemble the offset and limit
            dao.setOffset(offset);
            dao.setLimit(scope.getResultsPerPage());

            // Holder for the results
            List results = null;

            // Does this browse have any contents?
            if (total > 0)
            {
                // now run the query
                results = dao.doValueQuery();

                // now, if we don't have any results, we are at the end of the browse.  This will
                // be because a starts_with value has been supplied for which we don't have
                // any items.
                if (results.size() == 0)
                {
                    // In this case, we will calculate a new offset for the last page of results
                    offset = total - scope.getResultsPerPage();
                    if (offset < 0)
                        offset = 0;

                    // And rerun the query
                    dao.setOffset(offset);
                    results = dao.doValueQuery();
                }
            }
            else
            {
                // No records, so make an empty list
                results = new ArrayList();
            }

            // construct the BrowseInfo object to pass back
            BrowseInfo browseInfo = new BrowseInfo(results, offset, total, offset);

            if (offset + scope.getResultsPerPage() < total)
            {
                browseInfo.setNextOffset(offset + scope.getResultsPerPage());
            }

            if (offset - scope.getResultsPerPage() > -1)
            {
                browseInfo.setPrevOffset(offset - scope.getResultsPerPage());
            }

            // add the browse index to the Browse Info
            browseInfo.setBrowseIndex(browseIndex);

            // set the sort option for the Browse Info
            browseInfo.setSortOption(scope.getSortOption());

            // tell the Browse Info which way we are sorting
            browseInfo.setAscending(scope.isAscending());

            // tell the Browse Info which level of browse we are at
            browseInfo.setBrowseLevel(scope.getBrowseLevel());

            // set the browse value if there is one
            browseInfo.setFocus(rawFocusValue);

            // tell the browse info if it is working from a starts with parameter
            browseInfo.setStartsWith(scope.hasStartsWith());

            // tell the browse info what the container for the browse was
            if (scope.inCollection() || scope.inCommunity())
            {
                browseInfo.setBrowseContainer(scope.getBrowseContainer());
            }

            browseInfo.setResultsPerPage(scope.getResultsPerPage());

            return browseInfo;
        }
        catch (SQLException e)
        {
            log.error("caught exception: ", e);
            throw new BrowseException(e);
        }
    }

    /**
     * Return the focus value.
     *
     * @return  the focus value to use
     * @throws BrowseException
     */
    private String getJumpToValue()
        throws BrowseException
    {
        log.debug(LogManager.getHeader(context, "get_focus_value", ""));

        // if the focus is by value, just return it
        if (scope.hasJumpToValue())
        {
            log.debug(LogManager.getHeader(context, "get_focus_value_return", "return=" + scope.getJumpToValue()));
            return scope.getJumpToValue();
        }

        // if the focus is to start with, then we need to return the value of the starts with
        if (scope.hasStartsWith())
        {
            log.debug(LogManager.getHeader(context, "get_focus_value_return", "return=" + scope.getStartsWith()));
            return scope.getStartsWith();
        }

        // since the focus is not by value, we need to obtain it

        // get the id of the item to focus on
        int id = scope.getJumpToItem();

        // get the table name.  We don't really need to care about whether we are in a
        // community or collection at this point.  This is only for full or second
        // level browse, so there is no need to worry about distinct value browsing
        String tableName = browseIndex.getTableName();

        // we need to make sure that we select from the correct column.  If the sort option
        // is the 0th option then we use sort_value, but if it is one of the others we have
        // to select from that column instead.  Otherwise, we end up missing the focus value
        // to do comparisons in other columns.  The use of the focus value needs to be consistent
        // across the browse
        SortOption so = scope.getSortOption();
        if (so == null || so.getNumber() == 0)
        {
            if (browseIndex.getSortOption() != null)
                so = browseIndex.getSortOption();
        }

        String col = "sort_1";
        if (so.getNumber() > 0)
        {
            col = "sort_" + Integer.toString(so.getNumber());
        }


        // now get the DAO to do the query for us, returning the highest
        // string value in the given column in the given table for the
        // item (I think)
        String max = dao.doMaxQuery(col, tableName, id);

        log.debug(LogManager.getHeader(context, "get_focus_value_return", "return=" + max));

        return max;
    }

    /**
     * Convert the value into an offset into the table for this browse
     *
     * @return  the focus value to use
     * @throws BrowseException
     */
    private int getOffsetForValue(String value)
        throws BrowseException
    {
        // get the table name.  We don't really need to care about whether we are in a
        // community or collection at this point.  This is only for full or second
        // level browse, so there is no need to worry about distinct value browsing
        String tableName = browseIndex.getTableName();

        // we need to make sure that we select from the correct column.  If the sort option
        // is the 0th option then we use sort_value, but if it is one of the others we have
        // to select from that column instead.  Otherwise, we end up missing the focus value
        // to do comparisons in other columns.  The use of the focus value needs to be consistent
        // across the browse
        SortOption so = scope.getSortOption();
        if (so == null || so.getNumber() == 0)
        {
            if (browseIndex.getSortOption() != null)
                so = browseIndex.getSortOption();
        }

        String col = "sort_1";
        if (so.getNumber() > 0)
        {
            col = "sort_" + Integer.toString(so.getNumber());
        }

        // now get the DAO to do the query for us, returning the highest
        // string value in the given column in the given table for the
        // item (I think)
        return dao.doOffsetQuery(col, value, scope.isAscending());
    }

    /**
     * Convert the value into an offset into the table for this browse
     *
     * @return  the focus value to use
     * @throws BrowseException
     */
    private int getOffsetForDistinctValue(String value)
        throws BrowseException
    {
        if (!browseIndex.isMetadataIndex())
            throw new IllegalArgumentException("getOffsetForDistinctValue called when not a metadata index");

        // get the table name.  We don't really need to care about whether we are in a
        // community or collection at this point.  This is only for full or second
        // level browse, so there is no need to worry about distinct value browsing
        String tableName = browseIndex.getTableName();

        // now get the DAO to do the query for us, returning the highest
        // string value in the given column in the given table for the
        // item (I think)
        return dao.doDistinctOffsetQuery("sort_value", value, scope.isAscending());
    }

    /**
     * Return a normalized focus value. If there is no normalization that can be performed,
     * return the focus value that is passed in.
     *
     * @param value a focus value to normalize
     * @return  the normalized focus value
     * @throws BrowseException
     */
    private String normalizeJumpToValue(String value)
        throws BrowseException
    {
        // If the scope has a focus value (focus by value)
        if (scope.hasJumpToValue())
        {
            // Normalize it based on the specified language as appropriate for this index
            return OrderFormat.makeSortString(scope.getJumpToValue(), scope.getJumpToValueLang(), scope.getBrowseIndex().getDataType());
        }
        else if (scope.hasStartsWith())
        {
            // Scope has a starts with, so normalize that instead
            return OrderFormat.makeSortString(scope.getStartsWith(), null, scope.getBrowseIndex().getDataType());
        }

        // No focus value on the scope (ie. focus by id), so just return the passed focus value
        // This is useful in cases where we have pulled a focus value from the index
        // which will already be normalized, and avoids another DB lookup
        return value;
    }

    /**
     * Get the total number of results for the browse.  This is the same as
     * calling getTotalResults(false)
     *
     * @return
     * @throws SQLException
     * @throws BrowseException
     */
    private int getTotalResults()
        throws SQLException, BrowseException
    {
        return getTotalResults(false);
    }

    /**
     * Get the total number of results.  The argument determines whether this is a distinct
     * browse or not as this has an impact on how results are counted
     *
     * @param distinct  is this a distinct browse or not
     * @return          the total number of results available in this type of browse
     * @throws SQLException
     * @throws BrowseException
     */
    private int getTotalResults(boolean distinct)
        throws SQLException, BrowseException
    {
        log.debug(LogManager.getHeader(context, "get_total_results", "distinct=" + distinct));

        // tell the browse query whether we are distinct
        dao.setDistinct(distinct);

        // ensure that the select is set to "*"
        String[] select = { "*" };
        dao.setCountValues(select);

        // FIXME: it would be nice to have a good way of doing this in the DAO
        // now reset all of the fields that we don't want to have constraining
        // our count, storing them locally to reinstate later
        String focusField = dao.getJumpToField();
        String focusValue = dao.getJumpToValue();
        String orderField = dao.getOrderField();
        int limit = dao.getLimit();
        int offset = dao.getOffset();

        dao.setJumpToField(null);
        dao.setJumpToValue(null);
        dao.setOrderField(null);
        dao.setLimit(-1);
        dao.setOffset(-1);

        // perform the query and get the result
        int count = dao.doCountQuery();

        // now put back the values we removed for this method
        dao.setJumpToField(focusField);
        dao.setJumpToValue(focusValue);
        dao.setOrderField(orderField);
        dao.setLimit(limit);
        dao.setOffset(offset);
        dao.setCountValues(null);

        log.debug(LogManager.getHeader(context, "get_total_results_return", "return=" + count));

        return count;
    }

    /**
     * Get the position of the current start point of the browse in the field of total
     * objects relevant to this browse.  This is integrally related to how results are
     * presented in pages to the User Interface.  The argument tells us whether this
     * is a distinct browse or not, as this has an impact on how results are calculated
     *
     * @param distinct      is this a distinct browse
     * @return              the offset of the first result from the start of the set
     * @throws SQLException
     * @throws BrowseException
     */
    private int getPosition(boolean distinct)
        throws SQLException, BrowseException
    {
        log.debug(LogManager.getHeader(context, "get_position", "distinct=" + distinct));

        // if there isn't a focus then we are at the start
        if (dao.getJumpToValue() == null)
        {
            log.debug(LogManager.getHeader(context, "get_position_return", "return=0"));
            return 0;
        }

        // get the table name that we are going to be getting our data from
        String tableName = browseIndex.getTableName(distinct, scope.inCommunity(), scope.inCollection());

        // ensure that the select is set to "*"
        String[] select = { "*" };
        dao.setCountValues(select);

        // FIXME: it would be nice to have a good way of doing this in the DAO
        // now reset all of the fields that we don't want to have constraining
        // our count, storing them locally to reinstate later
        boolean isAscending = dao.isAscending();
        boolean useEquals = dao.useEqualsComparator();
        String orderField = dao.getOrderField();
        int limit = dao.getLimit();
        int offset = dao.getOffset();

        // reverse the direction of the query, and remove the equal comparator
        // (if it exists), as well as nullifying a few other unnecessary parameters
        dao.setAscending(!isAscending);
        dao.setEqualsComparator(false);
        dao.setOrderField(null);
        dao.setLimit(-1);
        dao.setOffset(-1);

        // perform the query and get the result
        int count = dao.doCountQuery();

        // now put back the values we removed for this method
        dao.setAscending(isAscending);
        dao.setEqualsComparator(useEquals);
        dao.setOrderField(orderField);
        dao.setLimit(limit);
        dao.setOffset(offset);

        log.debug(LogManager.getHeader(context, "get_position_return", "return=" + count));

        return count;
    }

    /**
     * Get the database id of the item at the top of what will be the previous page
     * of results.  This is so that a "back" button can be generated by the User
     * Interface when paging through results.  The callback argument is there so that
     * if the caller wishes the actual results from the previous page can also be returned
     * (this is useful, for example, if you are on the very last page of results, and need
     * some previous results to pad out the full number of results per page).  If the
     * callback is null, then no results are kept
     *
     * @param callback  A List object for holding BrowseItem objects indexed numerically in the correct order
     * @return          the database id of the top of the previous page
     * @throws SQLException
     * @throws BrowseException
     */
    private int getPreviousPageID(List callback)
        throws SQLException, BrowseException
    {
        log.debug(LogManager.getHeader(context, "get_previous_page_id", ""));

        // do we want to capture the results?
        boolean capture = false;
        if (callback != null)
        {
            capture = true;
        }

        // the only thing we need to do is reverse the direction
        // of the query, and set it to not use the "=" part of the
        // comparator (i.e. < and > not <= and >=).
        boolean isAscending = dao.isAscending();
        dao.setAscending(!isAscending);

        boolean useEquals = dao.useEqualsComparator();
        dao.setEqualsComparator(false);

        // store in local scope the things that we are going to change
        // during this method
        int resultLimit = dao.getLimit();

        // if the database supports it (postgres does), we use limit
        // and offset to minimise the work it has to do.

        // the limit will be the size of a page, or double the size of the
        // page if we are capturing the result set (because the first page's worth
        // will be the results, so the previous link will need to come from the
        // page *before* that)
        int limit = scope.getResultsPerPage();
        if (capture)
        {
            limit *= 2;
        }
        dao.setLimit(limit);

        // now we have a query which is exactly the opposite of the
        // original query.  So lets execute it:
        List results = dao.doQuery();

        // before we continue, put back the variables we messed with
        dao.setAscending(isAscending);
        dao.setEqualsComparator(useEquals);
        dao.setLimit(resultLimit);

        Iterator itr = results.iterator();

        // work our way through the list, capturing if necessary, and finally
        // having the last result, which will be the top of the previous page
        int i = 0;
        BrowseItem prev = null;
        while (itr.hasNext())
        {
            BrowseItem browseItem = (BrowseItem) itr.next();

            // we need to copy this, because of the scoping vs by-reference issue
            prev = browseItem;

            // if we need to capture the results in the call back, do it here.
            // Note that since the results will come in backwards, we place
            // each one at the start of the array so that it displaces the others
            // in the right direction.  By the end, they should be sorted correctly
            // we use the index i to be sure that we only grab one page's worth
            // of results (see note above about limit)
            if (capture && i < scope.getResultsPerPage())
            {
                callback.add(0, browseItem);
                i++;
            }
        }

        if (prev != null)
        {
            return prev.getID();
        }
        else
        {
            return -1;
        }
    }

    /**
     * Get the value (for single value browses) for the result that appears at the top
     * of the previous page, for when the User Interface is performing result set paging.
     * The callback argument allows for this method to populate the List with the results
     * obtained from the query, which can be useful when, for example, reaching the final
     * browse page and needing additional results to pad up to the number of results per
     * page
     *
     * @param callback      A List object for holding String objects indexed numerically in the correct order
     * @return              the value of the top of the previous page
     * @throws SQLException
     * @throws BrowseException
     */
    private String getPreviousPageValue(List callback)
        throws SQLException, BrowseException
    {
        // do we want to capture the results?
        boolean capture = false;
        if (callback != null)
        {
            capture = true;
        }

        log.debug(LogManager.getHeader(context, "get_previous_page_value", "capture_results=" + capture));

        // the only thing we need to do is reverse the direction
        // of the query, and set it to not use the "=" part of the
        // comparator (i.e. < and > not <= and >=).
        boolean isAscending = dao.isAscending();
        dao.setAscending(!isAscending);

        boolean useEquals = dao.useEqualsComparator();
        dao.setEqualsComparator(false);

        // store in local scope the things that we are going to change
        // during this method
        int resultLimit = dao.getLimit();

        // if the database supports it (postgres does), we use limit
        // and offset to minimise the work it has to do.

        // the limit will be the size of a page, or double the size of the
        // page if we are capturing the result set (because the first page's worth
        // will be the results, so the previous link will need to come from the
        // page *before* that)
        int limit = scope.getResultsPerPage();
        if (capture)
        {
            limit *= 2;
        }
        dao.setLimit(limit);

        // now we have a query which is exactly the opposite of the
        // original query.  So lets execute it:
        List results = dao.doValueQuery();

        // before we continue, put back the variables we messed with
        dao.setAscending(isAscending);
        dao.setEqualsComparator(useEquals);
        dao.setLimit(resultLimit);

        Iterator itr = results.iterator();

        // work our way through the list, capturing if necessary, and finally
        // having the last result, which will be the top of the previous page
        int i = 0;
        String prev = null;
        while (itr.hasNext())
        {
            String value = (String) itr.next();

            // we need to copy this, because of the scoping vs by-reference issue
            prev = value;

            // if we need to capture the results in the call back, do it here.
            // Note that since the results will come in backwards, we place
            // each one at the start of the array so that it displaces the others
            // in the right direction.  By the end, they should be sorted correctly
            // we use the index i to be sure that we only grab one page's worth
            // of results (see note above about limit)
            if (capture && i < scope.getResultsPerPage())
            {
                callback.add(0, value);
                i++;
            }
        }

        log.debug(LogManager.getHeader(context, "get_previous_page_value_return", "return=" + prev));

        return prev;
    }
}
TOP

Related Classes of org.dspace.browse.BrowseEngine

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.