head	1.23;
access;
symbols
	TIGRIS_1_1_0RC2:1.10.6.1
	TIGRIS_1_1_0RC1:1.10.6.1
	TIGRIS_1_1:1.10.6.1.0.2
	TIGRIS_1_0_8:1.10.6.1
	TIGRIS_1_0_8RC3:1.10.6.1
	TIGRIS_1_0_8RC2:1.10.6.1
	TIGRIS_1_0_8RC1:1.10.6.1
	TIGRIS_1_0_7:1.10.6.1
	TIGRIS_1_0_7RC3:1.10.6.1
	TIGRIS_1_0_7RC2:1.10.6.1
	TIGRIS_1_0_7RC1:1.10.6.1
	TIGRIS_1_0_6:1.10.6.1
	TIGRIS_1_0_6RC5:1.10.6.1
	TIGRIS_1_0_6RC4:1.10.6.1
	TIGRIS_1_0_6RC3:1.10.6.1
	TIGRIS_1_0_6RC2:1.10.6.1
	TIGRIS_1_0_6RC1:1.10.6.1
	TIGRIS_1_0_5:1.10.6.1
	TIGRIS_1_0_5RC6:1.10.6.1
	TIGRIS_1_0_5RC5:1.10.6.1
	TIGRIS_1_0_5RC4:1.10.6.1
	TIGRIS_1_0_5RC3:1.10.6.1
	TIGRIS_1_0_5RC2:1.10.6.1
	TIGRIS_1_0_5RC1:1.10.6.1
	TIGRIS_1_0_4:1.10.6.1
	TIGRIS_1_0_3:1.10
	TIGRIS_1_0_2:1.10
	TIGRIS_1_0_1:1.10
	TIGRIS_1_0:1.10.0.6
	TIGRIS_1_0_0:1.10
	TIGRIS_1_0_0_RC1:1.10.0.2
	dlr:1.1.1
	HELM_PEER_PORT_BRANCH:1.1.1.1;
locks; strict;
comment	@# @;


1.23
date	2001.08.13.19.04.33;	author leonardr;	state Exp;
branches;
next	1.22;

1.22
date	2001.08.11.09.08.10;	author dlr;	state Exp;
branches;
next	1.21;

1.21
date	2001.07.19.19.08.44;	author dlr;	state Exp;
branches;
next	1.20;

1.20
date	2001.07.02.15.34.36;	author leonardr;	state Exp;
branches;
next	1.19;

1.19
date	2001.06.29.22.53.37;	author leonardr;	state Exp;
branches;
next	1.18;

1.18
date	2001.06.29.00.57.49;	author leonardr;	state Exp;
branches;
next	1.17;

1.17
date	2001.06.25.17.09.28;	author leonardr;	state Exp;
branches;
next	1.16;

1.16
date	2001.06.13.15.39.45;	author leonardr;	state Exp;
branches;
next	1.15;

1.15
date	2001.06.13.00.53.18;	author leonardr;	state Exp;
branches;
next	1.14;

1.14
date	2001.06.13.00.21.57;	author leonardr;	state Exp;
branches;
next	1.13;

1.13
date	2001.06.12.23.10.43;	author leonardr;	state Exp;
branches;
next	1.12;

1.12
date	2001.05.21.23.11.28;	author dlr;	state Exp;
branches;
next	1.11;

1.11
date	2001.04.09.16.50.50;	author leonardr;	state Exp;
branches;
next	1.10;

1.10
date	2001.03.09.22.40.31;	author leonardr;	state Exp;
branches
	1.10.6.1;
next	1.9;

1.9
date	2001.02.28.00.21.39;	author leonardr;	state Exp;
branches;
next	1.8;

1.8
date	2001.02.24.04.04.52;	author dlr;	state Exp;
branches;
next	1.7;

1.7
date	2001.02.23.18.42.23;	author leonardr;	state Exp;
branches;
next	1.6;

1.6
date	2001.02.22.18.56.16;	author leonardr;	state Exp;
branches;
next	1.5;

1.5
date	2001.02.22.01.16.40;	author leonardr;	state Exp;
branches;
next	1.4;

1.4
date	2001.02.21.23.40.31;	author leonardr;	state Exp;
branches;
next	1.3;

1.3
date	2001.02.21.22.38.43;	author leonardr;	state Exp;
branches;
next	1.2;

1.2
date	2001.02.21.03.02.30;	author edk;	state Exp;
branches;
next	1.1;

1.1
date	2001.02.07.02.24.51;	author leonardr;	state dead;
branches
	1.1.1.1
	1.1.2.1;
next	;

1.1.1.1
date	2001.02.21.00.35.36;	author dlr;	state Exp;
branches;
next	;

1.1.2.1
date	2001.02.07.02.24.51;	author leonardr;	state Exp;
branches;
next	1.1.2.2;

1.1.2.2
date	2001.02.07.16.18.34;	author leonardr;	state Exp;
branches;
next	1.1.2.3;

1.1.2.3
date	2001.02.07.19.49.07;	author leonardr;	state Exp;
branches;
next	1.1.2.4;

1.1.2.4
date	2001.02.15.20.50.45;	author leonardr;	state Exp;
branches;
next	1.1.2.5;

1.1.2.5
date	2001.02.17.00.04.32;	author leonardr;	state Exp;
branches;
next	1.1.2.6;

1.1.2.6
date	2001.02.20.18.21.51;	author leonardr;	state Exp;
branches;
next	;

1.10.6.1
date	2001.05.02.15.47.36;	author leonardr;	state Exp;
branches;
next	;


desc
@@


1.23
log
@PeerBackedList now knows about objects that come in grouped flavors, and can explain behavior in which a list consists of separate alphabetical lists of groups and of users.
@
text
@/* ================================================================
 * Copyright (c) 2000-2001 CollabNet.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. 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.
 * 
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by CollabNet (http://www.collab.net/)."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of CollabNet.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 COLLABNET OR ITS 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.
 *
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */

package org.tigris.helm.tool.paginate;

import java.util.*;
import java.lang.reflect.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;

import org.apache.torque.om.BaseObject;
import org.apache.torque.util.Criteria;
import org.apache.torque.util.BasePeer;

import org.tigris.helm.security.AccessControlList;
import org.tigris.helm.om.HelmPeer;
import org.tigris.helm.om.GroupedObject;
import org.tigris.helm.om.ViewableBy;
import org.apache.turbine.util.Log;
import org.tigris.helm.util.URL;

/**
 * A class describing a PaginatableList which gets the objects it
 * paginates from a Peer and a Criteria.
 *
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a>
 * @@version $Id: PeerBackedList.java,v 1.22 2001/08/11 09:08:10 dlr Exp $
 */
public class PeerBackedList extends PaginatableList
{   
    /**
     * Whether or not to log debugging information.
     */
    private static final boolean DEBUG = false;

    /**
     * The name of the field containing the name of the table the Peer
     * manages.
     */
    public static final String TABLE_NAME_FIELD_NAME = "TABLE_NAME";

    /**
     * The name of the field in the peer which contains a mapping of
     * human-readable descriptions of fields to the actual database keys
     * themselves, for every database key you want to be OR-searched
     * when a simple filter request comes in.
     */
    public static final String FILTERABLE_FIELDS_FIELD_NAME = 
        "filterableFields";

    /**
     * The name of the field, if any, to be used in the order by clause
     * of the Criteria which makes the list.
     */
    public static final String ORDER_FIELD_NAME = "DEFAULT_ORDER_BY";

    /**
     * The name of the field, if any, which details whether or not the
     * ordering of the list is ascending.
     */
    public static final String ORDER_ASCENDING_FIELD_NAME = "ORDER_ASCENDING";

    /**
     * The filter check CGI key.
     */
    public static final String FILTER_CHECK = "FILTER CHECK";

    /**
     * The filter value CGI key.
     */
    public static final String FILTER_VALUE = "FILTER VALUE";

    /**
     * The simple filter value CGI key.
     */
    public static final String SIMPLE_FILTER_VALUE = "SIMPLE FILTER VALUE";

    /**
     * String used to distinguish groups in lists containing an
     * alphabetical list of groups and then an alphabetical list of
     * non-groups.
     */
    public static final String GROUP_GUIDE_PREFIX = "Group ";

    /**
     * The class of the Peer responsible for getting objects to put
     * into this list.
     */
    private Class peer;

    /**
     * The name of the table which the Peer manages.
     */
    private String tableName;

    /**
     * The name of the field by which to order list entries.
     */
    private String orderField;

    /**
     * Whether or not to order ascending.
     */
    private Boolean orderAscending;

    /**
     * Fields which can be searched by the simple or complex filters.
     */
    private Map searchableFields;

    /**
     * The program-specified criteria on which to select.
     */
    private Criteria baseCriteria;

    /**
     * The user-specified filter criteria.
     */
    private Criteria filterCriteria;

    /**
     * The user-specified simple filter value.
     */
    private String simpleFilterValue;

    /**
     * The ACL of the user viewing this list.
     */
    private AccessControlList acl;

    /**
     * How many entries matching the criteria are in the database.
     * Not neccessarily the same as the number of visible entries.
     */
    private int totalEntries;

    /**
     * How many entries matching the criteria have been fetched so
     * far. Not all of them have neccessarily made it into the list.
     */
    private int fetchedEntries;

    /**
     * A list of unfiltered list items.
     */
    private List unfiltered;

    /**
     * Sets up a list of objects matching certain criteria. Objects
     * will not be retrieved from the database until the getPage()
     * method is called.  
     */
    public PeerBackedList
        (Class peer, Integer itemsPerPage, Criteria base, Criteria filter)
        throws Exception
    {
        this (null, peer, itemsPerPage, base, filter);
    }

    /**
     * A basic constructor. Sets up a list of objects matching certain
     * criteria and visible by a certain user. Objects will not be
     * placed into the list until the getPage() method is called
     * (unless an itemsPerPage of 0 implies that the user is not going
     * to use this list as a paginated list).  
     */
    public PeerBackedList
        (AccessControlList viewingACL, Class peer, Integer itemsPerPage, 
         Criteria base, Criteria filter)
        throws Exception
    {
        if (itemsPerPage == null)
        {
            itemsPerPage = new Integer(0);
        }

        if (base == null)
        {
            base = new Criteria(0);
        }

        this.peer = peer;
        this.itemsPerPage = itemsPerPage.intValue();
        this.baseCriteria = base;
        this.filterCriteria = filter;
        this.acl = viewingACL;

        //Get the name of the table managed by the Peer.
        this.tableName = (String)(peer.getField(TABLE_NAME_FIELD_NAME))
            .get(null);

        //Get the field, if any, to be used in an order by statement and
        //to build the page guides.
        this.orderField = null;
        try 
        {
            Field field = peer.getField(ORDER_FIELD_NAME);
            this.orderField = (String)field.get(null);

            field = peer.getField(ORDER_ASCENDING_FIELD_NAME);
            this.orderAscending = (Boolean)field.get(null);
        } 
        catch (Exception ignore) 
        {
            //The peer has no such fields. The objects will be obtained
            //in any particular order. Nor will there be any page guides.
        }

        //If the filter criteria we were passed specifies a
        //peer-specific match of any of the specified fields, store
        //that in simpleFilterValue and add the appropriate Criterion
        //objects to base.
        simpleFilterValue = null;
        if (filter != null)
        {
            if (filter.containsKey(SIMPLE_FILTER_VALUE))
            {
                simpleFilterValue = filter.getString(SIMPLE_FILTER_VALUE);
                addSimpleFilterCriteria();
            } 
            else 
            {
                //A prefab filter value was provided. Does putAll work yet?
                //base.putAll(filter);
            }
        }

        //Add an order clause, if appropriate, to the base criterion.
        if (orderField != null && orderAscending != null 
            && !"".equals(orderField))
        {
            if (orderAscending.booleanValue())
            {
                base.addAscendingOrderByColumn(orderField);
            }
            else
            {
                base.addDescendingOrderByColumn(orderField);
            }
        }

        //See how many matching entries there are.
        totalEntries = HelmPeer.numberOfRecords(tableName);

        //Get unfiltered entries.
        Class [] critClass = {Criteria.class};
        Method doSelect = peer.getMethod("doSelect", critClass);
        Criteria [] crit = {baseCriteria};
        unfiltered = (List)doSelect.invoke(null, crit);            

        //If they're not interested in paginating the list, make the whole
        //list immediately so that they can use it.
        if (itemsPerPage.intValue() == 0)
        {
            fillListToPage(new Integer(1));
        }
    }

    /**
     * Given a string, adds to the base criteria Criterion objects
     * which will match that string in any of the fields which the
     * Peer considers filter-worthy.
     */
    private void addSimpleFilterCriteria()
    {
        try 
        {
            Criteria.Criterion c = null;
            Map map = (Map)peer.getField(FILTERABLE_FIELDS_FIELD_NAME)
                .get(null);                
            for (Iterator i = map.keySet().iterator(); i.hasNext();)
            {
                String description = (String)i.next();
                String value = (String)map.get(description);
                //Turn the value into a table name and a value.
                int splitPoint = value.indexOf(".");
                String tableName = value.substring(0, splitPoint);
                value = value.substring(splitPoint+1);
                Criteria.Criterion p = baseCriteria.getNewCriterion
                    (tableName, value, "%"+simpleFilterValue+"%", 
                     Criteria.LIKE);
                if (c == null)
                {
                    c = p;
                } 
                else
                {
                    c.or(p);
                }
            }
            baseCriteria.add(c);
        } 
        catch (Exception ignore) 
        {
            //The peer has no mappable fields. This simple filter
            //request will be ignored.
            Log.error
                ("Attempt to do a simple filter using the " + peer 
                 + ", which has no filterable fields.");
        }                    
    }

    /**
     * Returns a sublist of this list, the contents determined by
     * the page requested.
     */
    public List getPage(Integer page) 
        throws Exception
    {
        fillListToPage(page);
        return getPageNoFill(page);        
    }

    /**
     * Returns a sublist of this list, the contents determined by
     * the page requested.
     */
    public List getPageNoFill(Integer page) 
        throws Exception
    {
        return super.getPage(page);
    }

    /**
     * Fills the list with enough entries to display the given page,
     * including the navigation bar.
     */
    public void fillListToPage(Integer page)
        throws Exception
    { 
        int required = unfiltered.size();
        if (itemsPerPage != 0 && page.intValue() != 0)
        {
            required = entriesRequired(page.intValue());
        }       
        
        if (!(itemsPerPage == 0 && size() > 0))
        {
            Boolean runACLCheck = Boolean.FALSE;
            if (acl != null && unfiltered.size() > 0
                && unfiltered.get(0) instanceof ViewableBy)
            {
                for (Iterator i = unfiltered.iterator(); i.hasNext() 
                         && size() < required;)
                {
                    ViewableBy obj = (ViewableBy)i.next();
                    if (obj.isViewableBy(acl))
                    {                
                        add(obj);
                    }
                }
            }
            else
            {
                clear();
                addAll(unfiltered);
            }
        }
    }

    /**
     * Returns the number of entries that will be required to populate the
     * specified page and give it a navigation guide.
     */
    private int entriesRequired(int page)
    {
        //If we're displaying guides, the last page we need is the
        //page corresponding to the last guide. Otherwise, the page to
        //be displayed is the last page we need.
        int lastPageRequired=page;
        if (displayGuides())
        {
            lastPageRequired += 5;
            //We always need at least 10 pages.
            if (lastPageRequired < 10)
            {
                lastPageRequired = 10;
            }
        }        
        
        //We need sufficient entries to fill up the last page required
        //to be full for the display of this page, plus a fudge factor.
        int required = (int)(lastPageRequired * itemsPerPage * 1.2);

        if (required > totalEntries)
        {
            required = totalEntries;
        }

        return required;
    }

    /**
     * Creates a text key for the specified field (i.e.
     * <code>FILTER MATCHTYPE/userID</code>).  Useful for CGI message passing.
     *
     * @@param attrib The filter attribute.
     * @@param field  The name of the field.
     * @@return       The CGI key built from the filter attribute and field.
     */
    public static final String cgiKey(String attrib, String field)
    {
        return attrib + '/' + field;
    }

    public String toString()
    {
        StringBuffer d = new StringBuffer();
        d.append("PeerBackedList holding ").append(size())
            .append(" entries.\n");
        return d.toString();
    }

    /**
     * Return true iff this list is to have pagination guides.
     */
    private boolean displayGuides()
    {
        return (orderField != null);
    }
   
    /**
     * Makes a guide to the given page for use in the navbar.
     */
    public String makeGuide(Integer page)
        throws Exception
    {
        if (!displayGuides()) return "";
        String orderFieldName = orderField;

        //Turn "TABLE.FIELD DESC" into "TABLE.FIELD"
        int pos = orderFieldName.indexOf(' ');
        if (pos != -1)
        {
            orderFieldName = orderFieldName.substring(0, pos);
        }
        
        List thisPage = getPageNoFill(page);
        
        BaseObject first = (BaseObject)thisPage.get(0);
        BaseObject last = (BaseObject)thisPage.get(thisPage.size()-1);
        String firstVal = null;
        String lastVal = null;
        
        firstVal = makeGuideValue(first.getByPeerName(orderFieldName));
        lastVal = makeGuideValue(last.getByPeerName(orderFieldName));

        if (firstVal == null || lastVal == null || 
            firstVal=="" || lastVal == "") return "";

        GroupedObject f = null;
        GroupedObject l = null;
        if (first instanceof GroupedObject)
        {
            f = (GroupedObject)first;
        }
        if (last instanceof GroupedObject)
        {
            l = (GroupedObject)last;
        }

        if (f != null)
        {
            String prefix = f.getHumanReadableType();
            if (l != null && f.isGroup() == l.isGroup())
            {
                prefix += 's';
            }
            firstVal = prefix + ' ' + firstVal;
        }

        if (f != null && l != null && f.isGroup() != l.isGroup())
        {
            lastVal = l.getHumanReadableType() + ' ' + lastVal;
        }
                
        StringBuffer guide = new StringBuffer("(");
        guide.append(firstVal);
        if (!firstVal.equals(lastVal))
        {
            guide.append("..").append(lastVal);
        }
        guide.append(")");
        return guide.toString();
    }

    /**
     * Turns the value of a field into part of a navbar guide.
     */
    private static String makeGuideValue(Object val)
    {
        String strVal = "";
        if (val != null)
        {
            if (val instanceof Date)
            {
                SimpleDateFormat formatter
                    = new SimpleDateFormat("yyyy-MM-dd");
                strVal = formatter.format((Date)val);
            } else if (val instanceof String) {
                strVal = val.toString().substring(0,1).toUpperCase();
            }
        }
        return strVal;
    }

    /**
     * Ensure that the link describes the search criterion properly. 
     */
    public String appendQueryString()
    {
        StringBuffer extra = new StringBuffer();
        if (filterCriteria != null)
        {
            if (filterCriteria.containsKey(SIMPLE_FILTER_VALUE))
            {
                String simpleQueryString = 
                    (String)filterCriteria.get(SIMPLE_FILTER_VALUE);
                URL.appendCGIParam(extra, SIMPLE_FILTER_VALUE,
                                   simpleQueryString);
            } 
            else 
            {
                Iterator i = filterCriteria.keySet().iterator();
                while (i.hasNext())
                {
                    String key = (String)i.next();
                    String value = (String)filterCriteria.get(key);
                    URL.appendCGIParam(extra, cgiKey(FILTER_VALUE, key),
                                       value);
                }
                if (extra.length() > 0)
                {
                    URL.appendCGIParam(extra, FILTER_CHECK, "TRUE");
                }
            }
        }    
        return extra.toString();
    }    
}
@


1.22
log
@Upgrading to Turbine 3.0-dev, Fulcrum 1.0, and Torque 1.0.
@
text
@d60 1
d70 1
a70 1
 * @@version $Id: PeerBackedList.java,v 1.21 2001/07/19 19:08:44 dlr Exp $
d122 7
d490 1
a490 1
        
d493 27
a519 1
        
@


1.21
log
@Updated to use Helm URL class (just ported from Joist).
@
text
@d54 3
a56 4
import org.apache.turbine.util.db.Criteria;
import org.apache.turbine.util.Log;
import org.apache.turbine.om.*;    
import org.apache.turbine.om.peer.BasePeer;    
d61 1
a61 1

d69 1
a69 1
 * @@version $Id: PeerBackedList.java,v 1.20 2001/07/02 15:34:36 leonardr Exp $
@


1.20
log
@I was checking the size of the wrong list, causing lists to appear
empty when they shouldn't have.
@
text
@d63 1
a63 1
import org.joist.util.URL;
d70 1
a70 1
 * @@version $Id: PeerBackedList.java,v 1.19 2001/06/29 22:53:37 leonardr Exp $
@


1.19
log
@Corrected flawed logic.
@
text
@d70 1
a70 1
 * @@version $Id: PeerBackedList.java,v 1.18 2001/06/29 00:57:49 leonardr Exp $
d373 1
a373 1
        if (!(itemsPerPage == 0 && unfiltered.size() > 0))
@


1.18
log
@Implemented basic project group and category nesting.
@
text
@d70 1
a70 1
 * @@version $Id: PeerBackedList.java,v 1.17 2001/06/25 17:09:28 leonardr Exp $
d371 3
a373 2
        }
        else if (!(itemsPerPage == 0 && unfiltered.size() > 0))
d392 1
a392 1
            addAll(unfiltered);
@


1.17
log
@Handle the null case inside the method instead of in a million places outside it.
@
text
@d70 1
a70 1
 * @@version $Id: PeerBackedList.java,v 1.16 2001/06/13 15:39:45 leonardr Exp $
d368 1
a368 1
        if (itemsPerPage != 0)
d372 1
a372 4

        Boolean runACLCheck = Boolean.FALSE;
        if (acl != null && unfiltered.size() > 0
            && unfiltered.get(0) instanceof ViewableBy)
d374 3
a376 2
            for (Iterator i = unfiltered.iterator(); i.hasNext() 
                     && size() < required;)
d378 8
a385 4
                ViewableBy obj = (ViewableBy)i.next();
                if (obj.isViewableBy(acl))
                {                
                    add(obj);
d388 3
a390 3
        }
        else
        {
d392 1
@


1.16
log
@If the user is not interested in paginating the list, they're not
going to call getPage(), they're just going to use it as though it had
entries in it. So fill up the list before returning from the
constructor.
@
text
@d70 1
a70 1
 * @@version $Id: PeerBackedList.java,v 1.15 2001/06/13 00:53:18 leonardr Exp $
d208 5
@


1.15
log
@Greatly simplified code since tests indicated that the more
complicated code was not helping performance and actually hurting it
in some cases.
@
text
@d70 1
a70 1
 * @@version $Id: PeerBackedList.java,v 1.14 2001/06/13 00:21:57 leonardr Exp $
d198 4
a201 2
     * criteria and visible by a certain user. Objects will not be retrieved
     * from the database until the getPage() method is called.
d281 7
d362 5
a366 1
        int required = entriesRequired(page.intValue());
@


1.14
log
@Cast to int at the appropriate time.
@
text
@d70 1
a70 1
 * @@version $Id: PeerBackedList.java,v 1.13 2001/06/12 23:10:43 leonardr Exp $
d180 5
d261 1
a261 1
            if (orderAscending == Boolean.TRUE)
d273 6
a352 5
        //long before = System.currentTimeMillis();
        Class [] critClass = {Criteria.class};
        Method doSelect = peer.getMethod("doSelect", critClass);

        int position = 0;        
a353 3
        int limit = required;

        Boolean runACLCheck = null;
d355 3
a357 8
        if (DEBUG)
        {
            Log.debug("Filling list to " + required + " entries.");
            Log.debug("Size: " + size() + " Required: " + required
                      + " fetchedEntries: " + fetchedEntries 
                      + " totalEntries: " + totalEntries);
        }
        while (size() < required && fetchedEntries < totalEntries)
d359 2
a360 1
            if (DEBUG)
d362 4
a365 18
                Log.debug("Fetching " + limit + " records.");
            }
            baseCriteria.setLimit(limit);
            Criteria [] crit = {baseCriteria};
            List unfiltered = (List)doSelect.invoke(null, crit);            
            fetchedEntries = unfiltered.size();

            //If an ACL was given, get rid of objects that aren't visible
            //to it.
            if (runACLCheck == null)
            {
                runACLCheck = Boolean.FALSE;
                if (acl != null && unfiltered.size() > 0)
                {
                    if (unfiltered.get(0) instanceof ViewableBy)
                    {
                        runACLCheck = Boolean.TRUE;
                    }
a367 44
            
            if (runACLCheck.booleanValue())
            {
                for (; position < unfiltered.size(); position++)
                {
                    ViewableBy obj = (ViewableBy)unfiltered.get(position);
                    if (obj.isViewableBy(acl))
                    {
                        add(obj);
                        if (size() >= required)
                        {
                            break;
                        }
                    }
                }

                if (DEBUG)
                {
                    Log.debug("After ACL checking, I have " + size() 
                              + " records out of " + position);
                }

                //Increment the position and increase the limit
                position = limit;

                //We retrieved [limit] records, and size() matched. We
                //need [required] records. By the law of proportion,
                //we should then try to get required * (limit/size())
                //records. If size() is zero, then we do well to get
                //all the entries, since most likely all none of them
                //are visible (though we need to make sure).
                if (size() == 0)
                {
                    limit = totalEntries;
                }
                else
                {
                    limit = (required * (limit/size()));
                }
            }
            else
            {                
                addAll(unfiltered);
            }            
d369 4
a372 3
        //long after = System.currentTimeMillis();
        //long took = after-before;
        //Log.debug("That took " + took + " ms.");
@


1.13
log
@PeerBackedList performance boost and refactoring. This may be more complicated than it needs to be; rather than getting ever-increasing proportions of the table, it might be faster in most cases to get the whole table, so long as you stop processing results when the list is full enough.
@
text
@d70 1
a70 1
 * @@version $Id: PeerBackedList.java,v 1.12 2001/05/21 23:11:28 dlr Exp $
d342 1
d428 3
d455 1
a455 1
        int required = lastPageRequired * itemsPerPage * 1.2;
@


1.12
log
@Broke long lines.
@
text
@d59 4
d70 1
a70 1
 * @@version $Id: PeerBackedList.java,v 1.11 2001/04/09 16:50:50 leonardr Exp $
d74 4
a77 1
    private static final boolean debug = true;
d79 5
a83 7
    private Class peer;
    private String orderField;
    private Map searchableFields;

    private Criteria baseCriteria;
    private Criteria filterCriteria;
    private String simpleFilterString;
d91 2
a92 1
    public static final String FILTERABLE_FIELDS_FIELD_NAME = "filterableFields";
d101 6
d121 45
d168 15
a182 1
     * A basic constructor.
d188 13
a205 5
        if (filter == null)
        {
            filter = new Criteria(0);
        }

d207 1
d210 5
a214 1
        this.itemsPerPage = itemsPerPage.intValue();
d223 3
d229 1
a229 1
            //The peer has no such field. The objects will be obtained
d235 3
a237 3
        //that in simpleMatchString and build the peer-appropriate
        //searchCriteria.
        String simpleFilterValue = null;
d243 134
a376 10
                
                //A simple filter value was provided. Build a Criteria
                //which will match the simple filter value in any of the fields
                //which the provided Peer considers filter-worthy.
                try 
                {
                    Criteria.Criterion c = null;
                    Map map = (Map)peer.getField(FILTERABLE_FIELDS_FIELD_NAME)
                        .get(null);                
                    for (Iterator i = map.keySet().iterator(); i.hasNext();)
d378 14
a391 14
                        String description = (String)i.next();
                        String value = (String)map.get(description);
                        //Turn the value into a table name and a value.
                        int splitPoint = value.indexOf(".");
                        String tableName = value.substring(0, splitPoint);
                        value = value.substring(splitPoint+1);
                        Criteria.Criterion p = filterCriteria.getNewCriterion
                            (tableName, value, "%"+simpleFilterValue+"%", 
                             Criteria.LIKE);
                        if (c == null)
                        {
                            c = p;
                        } 
                        else
d393 1
a393 1
                            c.or(p);
d396 22
a417 3
                    base.add(c);
                } 
                catch (Exception ignore) 
d419 2
a420 9
                    Log.error
                        ("Attempt to do a simple filter using the " + peer 
                         + ", which has no filterable fields.");
                    //The peer has no mappable fields. This simple filter
                    //request will be ignored.
                }            
            } else {
                //A prefab filter value was provided. Hopefully putAll
                //will work soon and we can use this.
d422 4
d427 25
d453 1
a453 2
        //Add an order clause, if appropriate, to the big criterion.
        if (orderField != null && !"".equals(orderField))
d455 1
a455 1
            base.addOrderByColumn(orderField);
d458 1
a458 8
        //Get the doSelect method for the given peer.
        Criteria [] crit = {base};
        Class [] critClass = {Criteria.class};
        Method doSelect = peer.getMethod("doSelect", critClass);

        //Run the doSelect method.
        clear();
        addAll((List)doSelect.invoke(null, crit));
d481 8
d494 1
d496 1
a496 1
        if (orderField == null) return "";
d506 1
a506 1
        List thisPage = getPage(page);
@


1.11
log
@Pagination on filtered data should work now.
@
text
@d66 1
a66 1
 * @@version $Id: PeerBackedList.java,v 1.10 2001/03/09 22:40:31 leonardr Exp $
d308 2
a309 1
                URL.appendCGIParam(extra, SIMPLE_FILTER_VALUE, simpleQueryString);
d313 2
a314 1
                for (Iterator i = filterCriteria.keySet().iterator(); i.hasNext();)
d318 2
a319 1
                    URL.appendCGIParam(extra, cgiKey(FILTER_VALUE, key), value);
@


1.10
log
@Handle empty strings.
@
text
@d66 1
a66 1
 * @@version $Id: PeerBackedList.java,v 1.9 2001/02/28 00:21:39 leonardr Exp $
a155 1
                Log.debug("Simple filter value: " + simpleFilterValue);
a259 1
        //TODO: change to getByPeerName; this doesn't work.
d302 1
a302 1
        if (simpleFilterString != null)
d304 1
a304 5
            URL.appendCGIParam(extra, SIMPLE_FILTER_VALUE, simpleFilterString);
        } 
        else if (filterCriteria != null)
        {
            for (Iterator i = filterCriteria.keySet().iterator(); i.hasNext();)
d306 5
a310 5
                String key = (String)i.next();
                String value = (String)filterCriteria.get(key);
                URL.appendCGIParam(extra, cgiKey(FILTER_VALUE, key), value);
            }
            if (extra.length() > 0)
d312 10
a321 2
                Log.debug("a5");
                URL.appendCGIParam(extra, FILTER_CHECK, "TRUE");
d323 1
a323 1
        }        
@


1.10.6.1
log
@Backporting fixes for pagination of filtered data.
@
text
@d66 1
a66 1
 * @@version $Id: PeerBackedList.java,v 1.11 2001/04/09 16:50:50 leonardr Exp $
d156 1
d261 1
d304 1
a304 1
        if (filterCriteria != null)
d306 5
a310 1
            if (filterCriteria.containsKey(SIMPLE_FILTER_VALUE))
d312 5
a316 5
                String simpleQueryString = 
                    (String)filterCriteria.get(SIMPLE_FILTER_VALUE);
                URL.appendCGIParam(extra, SIMPLE_FILTER_VALUE, simpleQueryString);
            } 
            else 
d318 2
a319 10
                for (Iterator i = filterCriteria.keySet().iterator(); i.hasNext();)
                {
                    String key = (String)i.next();
                    String value = (String)filterCriteria.get(key);
                    URL.appendCGIParam(extra, cgiKey(FILTER_VALUE, key), value);
                }
                if (extra.length() > 0)
                {
                    URL.appendCGIParam(extra, FILTER_CHECK, "TRUE");
                }
d321 1
a321 1
        }    
@


1.9
log
@Page guides work! (Pending my patch to Torque).
@
text
@d66 1
a66 1
 * @@version $Id: PeerBackedList.java,v 1.8 2001/02/24 04:04:52 dlr Exp $
d203 1
a203 1
        if (orderField != null)
@


1.8
log
@Sync'd with latest Turbine changes.  Mostly this consists of the use of the new SimpleKey class to handle primary database keys.
@
text
@d52 1
d66 1
a66 1
 * @@version $Id: PeerBackedList.java,v 1.7 2001/02/23 18:42:23 leonardr Exp $
d255 1
a255 1

d262 2
a263 2
        firstVal = makeGuideValue(first.getByName(orderFieldName));
        lastVal = makeGuideValue(last.getByName(orderFieldName));
d272 1
a272 1
            guide.append("-").append(lastVal);
d288 3
a290 1
                strVal = val.toString();
@


1.7
log
@Turn an ORDER BY criteria into a guide field.
@
text
@d55 1
a55 1
import org.apache.turbine.om.BaseObject;    
d65 1
a65 1
 * @@version $Id: PeerBackedList.java,v 1.6 2001/02/22 18:56:16 leonardr Exp $
@


1.6
log
@Handle null criteria better.
@
text
@d65 1
a65 1
 * @@version $Id: PeerBackedList.java,v 1.5 2001/02/22 01:16:40 leonardr Exp $
d244 9
d255 20
a274 21
         BaseObject first = (BaseObject)thisPage.get(0);
         BaseObject last = (BaseObject)thisPage.get(thisPage.size()-1);
         String firstVal = null;
         String lastVal = null;
         
         //TODO: change to getByPeerName; this doesn't work unless you
         //hack it.
         firstVal = makeGuideValue(first.getByName(orderField));
         lastVal = makeGuideValue(last.getByName(orderField));

         if (firstVal == null || lastVal == null || 
             firstVal=="" || lastVal == "") return "";

         StringBuffer guide = new StringBuffer("(");
         guide.append(firstVal);
         if (!firstVal.equals(lastVal))
         {
             guide.append("-").append(lastVal);
         }
         guide.append(")");
         return guide.toString();
@


1.5
log
@Rearranged filtering stuff again. Hopefully the error message it's now capable of generating will forestall future annoyances.
@
text
@d65 1
a65 1
 * @@version $Id: PeerBackedList.java,v 1.4 2001/02/21 23:40:31 leonardr Exp $
d116 10
@


1.4
log
@Yet more pagination fixes.
@
text
@d65 1
a65 1
 * @@version $Id: PeerBackedList.java,v 1.3 2001/02/21 22:38:43 leonardr Exp $
d118 1
d140 1
a140 1
        if (filter != null && filter.containsKey(SIMPLE_FILTER_VALUE))
d142 1
a142 9
            simpleFilterValue = filter.getString(SIMPLE_FILTER_VALUE);
        }

        if (simpleFilterValue == null)
        {
            //A simple filter value was provided. Build a Criteria
            //which will match the simple filter value in any of the fields
            //which the provided Peer considers filter-worthy.
            try 
d144 7
a150 4
                Criteria.Criterion c = null;
                Map map = (Map)peer.getField(FILTERABLE_FIELDS_FIELD_NAME)
                    .get(null);                
                for (Iterator i = map.keySet().iterator(); i.hasNext();)
d152 4
a155 10
                    String description = (String)i.next();
                    String value = (String)map.get(description);
                    //Turn the value into a table name and a value.
                    int splitPoint = value.indexOf(".");
                    String tableName = value.substring(0, splitPoint);
                    value = value.substring(splitPoint+1);
                    Criteria.Criterion p = filterCriteria.getNewCriterion
                        (tableName, value, "%"+simpleFilterValue+"%", 
                         Criteria.LIKE);
                    if (c == null)
d157 17
a173 5
                        c = p;
                    } 
                    else
                    {
                        c.or(p);
d175 14
a188 8
                }
                base.add(c);
            } 
            catch (Exception ignore) 
            {
                //The peer has no mappable fields. This simple filter
                //request will be ignored.
            }            
@


1.3
log
@Commented out nonfunctional fancy filter code.
@
text
@d65 1
a65 1
 * @@version $Id: PeerBackedList.java,v 1.2 2001/02/21 03:02:30 edk Exp $
d144 1
a144 2
        filterCriteria = base;        
        if (filter != null)
d146 4
a149 1
            if (simpleFilterValue == null)
d151 4
a154 8
                //A more specific filter was provided.
                //TODO: This doesn't work.
                //filterCriteria.putAll(filter);
            } else {
                //A simple filter value was provided. Build a Criteria
                //which will match the simple filter value in any of the fields
                //which the provided Peer considers filter-worthy.
                try 
d156 10
a165 4
                    Criteria.Criterion c = null;
                    Map map = (Map)peer.getField(FILTERABLE_FIELDS_FIELD_NAME)
                        .get(null);                
                    for (Iterator i = map.keySet().iterator(); i.hasNext();)
d167 5
a171 17
                        String description = (String)i.next();
                        String value = (String)map.get(description);
                        //Turn the value into a table name and a value.
                        int splitPoint = value.indexOf(".");
                        String tableName = value.substring(0, splitPoint);
                        value = value.substring(splitPoint+1);
                        Criteria.Criterion p = filterCriteria.getNewCriterion
                            (tableName, value, "%"+simpleFilterValue+"%", 
                             Criteria.LIKE);
                        if (c == null)
                        {
                            c = p;
                        } 
                        else
                        {
                            c.or(p);
                        }
d173 8
a180 8
                    filterCriteria.add(c);
                } 
                catch (Exception ignore) 
                {
                    //The peer has no mappable fields. This simple filter
                    //request will be ignored.
                }            
            }
d186 1
a186 1
            filterCriteria.addOrderByColumn(orderField);
d190 1
a190 1
        Criteria [] crit = {filterCriteria};
d195 1
a195 1
        clear();       
d289 1
@


1.2
log
@adding files from the branch to the head
@
text
@d65 1
a65 1
 * @@version $Id: PeerBackedList.java,v 1.1.1.1 2001/02/21 00:35:36 dlr Exp $
a137 2
        if (filter == null) filter = new Criteria();
        
d139 1
a139 1
        if (filter.containsKey(SIMPLE_FILTER_VALUE))
d144 2
a145 2
        this.filterCriteria = base;
        if (simpleFilterValue == null)
d147 1
a147 7
            //A more specific filter was provided, or none at all.
            this.filterCriteria.putAll(filter);
        } else {
            //A simple filter value was provided. Build a Criteria
            //which will match the simple filter value in any of the fields
            //which the provided Peer considers filter-worthy.
            try 
d149 8
a156 4
                Criteria.Criterion c = null;
                Map map = (Map)peer.getField(FILTERABLE_FIELDS_FIELD_NAME)
                    .get(null);                
                for (Iterator i = map.keySet().iterator(); i.hasNext();)
d158 4
a161 10
                    String description = (String)i.next();
                    String value = (String)map.get(description);
                    //Turn the value into a table name and a value.
                    int splitPoint = value.indexOf(".");
                    String tableName = value.substring(0, splitPoint);
                    value = value.substring(splitPoint+1);
                    Criteria.Criterion p = this.filterCriteria.getNewCriterion
                        (tableName, value, "%"+simpleFilterValue+"%", 
                         Criteria.LIKE);
                    if (c == null)
d163 17
a179 5
                        c = p;
                    } 
                    else
                    {
                        c.or(p);
d181 8
a188 9
                }
                this.filterCriteria.add(c);
            } 
            catch (Exception ignore) 
            {
                //The peer has no mappable fields. This simple filter
                //request will be ignored, and an empty criteria
                //submitted.
            }            
d194 1
a194 1
            this.filterCriteria.addOrderByColumn(orderField);
d198 1
a198 1
        Criteria [] crit = {this.filterCriteria};
@


1.1
log
@file PeerBackedList.java was initially added on branch HELM_PEER_PORT_BRANCH.
@
text
@d1 301
@


1.1.1.1
log
@Replacing the HEAD with the HELM_PEER_PORT_BRANCH.
@
text
@a0 301
/* ================================================================
 * Copyright (c) 2000-2001 CollabNet.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. 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.
 * 
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by CollabNet (http://www.collab.net/)."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of CollabNet.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 COLLABNET OR ITS 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.
 *
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */

package org.tigris.helm.tool.paginate;

import java.util.*;
import java.lang.reflect.*;
import java.net.URLEncoder;

import org.apache.turbine.util.db.Criteria;
import org.apache.turbine.util.Log;
import org.apache.turbine.om.BaseObject;    
import org.apache.turbine.om.peer.BasePeer;    

import org.joist.util.URL;

/**
 * A class describing a PaginatableList which gets the objects it
 * paginates from a Peer and a Criteria.
 *
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a>
 * @@version $Id: PeerBackedList.java,v 1.1.2.6 2001/02/20 18:21:51 leonardr Exp $
 */
public class PeerBackedList extends PaginatableList
{   
    private static final boolean debug = true;

    private Class peer;
    private String orderField;
    private Map searchableFields;

    private Criteria baseCriteria;
    private Criteria filterCriteria;
    private String simpleFilterString;

    /**
     * The name of the field in the peer which contains a mapping of
     * human-readable descriptions of fields to the actual database keys
     * themselves, for every database key you want to be OR-searched
     * when a simple filter request comes in.
     */
    public static final String FILTERABLE_FIELDS_FIELD_NAME = "filterableFields";

    /**
     * The name of the field, if any, to be used in the order by clause
     * of the Criteria which makes the list.
     */
    public static final String ORDER_FIELD_NAME = "DEFAULT_ORDER_BY";

    /**
     * The filter check CGI key.
     */
    public static final String FILTER_CHECK = "FILTER CHECK";

    /**
     * The filter value CGI key.
     */
    public static final String FILTER_VALUE = "FILTER VALUE";

    /**
     * The simple filter value CGI key.
     */
    public static final String SIMPLE_FILTER_VALUE = "SIMPLE FILTER VALUE";


    /**
     * A basic constructor.
     */
    public PeerBackedList
        (Class peer, Integer itemsPerPage, Criteria base, Criteria filter)
        throws Exception
    {
        this.peer = peer;
        this.baseCriteria = base;
        this.itemsPerPage = itemsPerPage.intValue();

        //Get the field, if any, to be used in an order by statement and
        //to build the page guides.
        this.orderField = null;
        try 
        {
            Field field = peer.getField(ORDER_FIELD_NAME);
            this.orderField = (String)field.get(null);
        } 
        catch (Exception ignore) 
        {
            //The peer has no such field. The objects will be obtained
            //in any particular order. Nor will there be any page guides.
        }

        //If the filter criteria we were passed specifies a
        //peer-specific match of any of the specified fields, store
        //that in simpleMatchString and build the peer-appropriate
        //searchCriteria.
        if (filter == null) filter = new Criteria();
        
        String simpleFilterValue = null;
        if (filter.containsKey(SIMPLE_FILTER_VALUE))
        {
            simpleFilterValue = filter.getString(SIMPLE_FILTER_VALUE);
        }

        this.filterCriteria = base;
        if (simpleFilterValue == null)
        {
            //A more specific filter was provided, or none at all.
            this.filterCriteria.putAll(filter);
        } else {
            //A simple filter value was provided. Build a Criteria
            //which will match the simple filter value in any of the fields
            //which the provided Peer considers filter-worthy.
            try 
            {
                Criteria.Criterion c = null;
                Map map = (Map)peer.getField(FILTERABLE_FIELDS_FIELD_NAME)
                    .get(null);                
                for (Iterator i = map.keySet().iterator(); i.hasNext();)
                {
                    String description = (String)i.next();
                    String value = (String)map.get(description);
                    //Turn the value into a table name and a value.
                    int splitPoint = value.indexOf(".");
                    String tableName = value.substring(0, splitPoint);
                    value = value.substring(splitPoint+1);
                    Criteria.Criterion p = this.filterCriteria.getNewCriterion
                        (tableName, value, "%"+simpleFilterValue+"%", 
                         Criteria.LIKE);
                    if (c == null)
                    {
                        c = p;
                    } 
                    else
                    {
                        c.or(p);
                    }
                }
                this.filterCriteria.add(c);
            } 
            catch (Exception ignore) 
            {
                //The peer has no mappable fields. This simple filter
                //request will be ignored, and an empty criteria
                //submitted.
            }            
        }

        //Add an order clause, if appropriate, to the big criterion.
        if (orderField != null)
        {
            this.filterCriteria.addOrderByColumn(orderField);
        }

        //Get the doSelect method for the given peer.
        Criteria [] crit = {this.filterCriteria};
        Class [] critClass = {Criteria.class};
        Method doSelect = peer.getMethod("doSelect", critClass);

        //Run the doSelect method.
        clear();       
        addAll((List)doSelect.invoke(null, crit));
    }

    /**
     * Creates a text key for the specified field (i.e.
     * <code>FILTER MATCHTYPE/userID</code>).  Useful for CGI message passing.
     *
     * @@param attrib The filter attribute.
     * @@param field  The name of the field.
     * @@return       The CGI key built from the filter attribute and field.
     */
    public static final String cgiKey(String attrib, String field)
    {
        return attrib + '/' + field;
    }

    public String toString()
    {
        StringBuffer d = new StringBuffer();
        d.append("PeerBackedList holding ").append(size())
            .append(" entries.\n");
        return d.toString();
    }
   
    /**
     * Makes a guide to the given page for use in the navbar.
     */
    public String makeGuide(Integer page)
    {
        if (orderField == null) return "";
        List thisPage = getPage(page);

         BaseObject first = (BaseObject)thisPage.get(0);
         BaseObject last = (BaseObject)thisPage.get(thisPage.size()-1);
         String firstVal = null;
         String lastVal = null;
         
         //TODO: change to getByPeerName; this doesn't work unless you
         //hack it.
         firstVal = makeGuideValue(first.getByName(orderField));
         lastVal = makeGuideValue(last.getByName(orderField));

         if (firstVal == null || lastVal == null || 
             firstVal=="" || lastVal == "") return "";

         StringBuffer guide = new StringBuffer("(");
         guide.append(firstVal);
         if (!firstVal.equals(lastVal))
         {
             guide.append("-").append(lastVal);
         }
         guide.append(")");
         return guide.toString();
    }

    /**
     * Turns the value of a field into part of a navbar guide.
     */
    private static String makeGuideValue(Object val)
    {
        String strVal = "";
        if (val != null)
        {
            if (val instanceof Date)
            {
                strVal = val.toString();
            } else if (val instanceof String) {
                strVal = val.toString().substring(0,1).toUpperCase();
            }
        }
        return strVal;
    }

    /**
     * Ensure that the link describes the search criterion properly. 
     */
    public String appendQueryString()
    {
        StringBuffer extra = new StringBuffer();
        if (simpleFilterString != null)
        {
            URL.appendCGIParam(extra, SIMPLE_FILTER_VALUE, simpleFilterString);
        } 
        else if (filterCriteria != null)
        {
            for (Iterator i = filterCriteria.keySet().iterator(); i.hasNext();)
            {
                String key = (String)i.next();
                String value = (String)filterCriteria.get(key);
                URL.appendCGIParam(extra, cgiKey(FILTER_VALUE, key), value);
            }
            if (extra.length() > 0)
            {
                URL.appendCGIParam(extra, FILTER_CHECK, "TRUE");
            }
        }        
        return extra.toString();
    }    
}
@


1.1.2.1
log
@Ported pagination scheme to new helm. Simple filter still won't work.
@
text
@a0 211
/* ================================================================
 * Copyright (c) 2000 CollabNet.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. 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.
 * 
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by CollabNet (http://www.collab.net/)."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of CollabNet.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 COLLABNET OR ITS 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.
 *
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */

package org.tigris.helm.tool.paginate;

import java.util.*;
import java.lang.reflect.*;
import java.net.URLEncoder;

import org.apache.turbine.util.db.Criteria;
import org.apache.turbine.util.Log;

import org.joist.util.URL;

/**
 * A class describing a PaginatableList which gets the objects it
 * paginates from a Peer and a Criteria.
 *
 * @@author <a href="mailto:leonardr@@collab.net">Leonard Richardson</a>
 * @@version $Id: PaginatableDatabaseBackedList.java,v 1.9 2001/01/26 18:29:11 edk Exp $
 */
public class PeerBackedList extends PaginatableList
{   
    private static final boolean debug = true;

    private Class peer;
    private Criteria baseCriteria;
    private Criteria searchCriteria;
    private boolean simpleFilter;

    /**
     * The filter check CGI key.
     */
    public static final String FILTER_CHECK = "FILTER CHECK";

    /**
     * The filter value CGI key.
     */
    public static final String FILTER_VALUE = "FILTER VALUE";

    /**
     * The simple filter value CGI key.
     */
    public static final String SIMPLE_FILTER_VALUE = "SIMPLE FILTER VALUE";


    /**
     * A basic constructor.
     */
    public PeerBackedList
        (Class peer, Integer itemsPerPage, Criteria base, Criteria search)
        throws Exception
    {
        this.peer = peer;
        this.baseCriteria = base;
        this.searchCriteria = search;
        this.itemsPerPage = itemsPerPage.intValue();
        simpleFilter = false;

        //Make the base and search criteria into one big criteria.
        Criteria bigCriteria = base;
        bigCriteria.putAll(search);
        Criteria [] crit = {bigCriteria};

        //Get the doSelect method for the given peer.
        Class [] critClass = {Criteria.class};
        Method doSelect = peer.getMethod("doSelect", critClass);

        //Run the doSelect method.
        clear();         
        addAll((List)doSelect.invoke(null, crit));
    }

    /**
     * A basic constructor using a simple filter instead of a search
     * criterion.  
     */
    public PeerBackedList
        (Class peer, Integer itemsPerPage, Criteria base, String simple)
        throws Exception
    {
        simpleFilter = true;
        searchCriteria = new Criteria();
        //TODO: Make this reflect the proper fields. Grab the peer's
        //searchableFields member, throwing an error if they don't
        //have one.
    }

    /**
     * Creates a text key for the specified field (i.e.
     * <code>FILTER MATCHTYPE/userID</code>).  Useful for CGI message passing.
     *
     * @@param attrib The filter attribute.
     * @@param field  The name of the field.
     * @@return       The CGI key built from the filter attribute and field.
     */
    public static final String cgiKey(String attrib, String field)
    {
        return attrib + '/' + field;
    }

    public String toString()
    {
        StringBuffer d = new StringBuffer();
        d.append("PeerBackedList holding ").append(size())
            .append(" entries.\n");
        return d.toString();
    }
    
    /**
     * Makes a guide to the given page for use in the navbar.
     */
    public String makeGuide(Integer page)
    {
        return "";
//         String guideField = alphabetizedByField();
//         if (guideField == null) return "";
//         StringBuffer guide = new StringBuffer("(");
//         String firstVal = guideLetter(get(0));
//         String lastVal = guideLetter(get(size()-1));
//         guide.append(firstVal);
//         if (!firstVal.equals(lastVal))
//         {
//             guide.append("-").append(lastVal);
//         }
//         guide.append(")");
//         return guide.toString();
    }

    /**
     * Gets the first letter of the appropriate field.
     */
    private String guideLetter(Object r)
    {
        //TODO: Use introspection on the peer.
        return "";
    }

    /**
     * Ensure that the link describes the search criterion properly.
     */
    public String appendQueryString()
    {
        StringBuffer extra = new StringBuffer();
        if (searchCriteria != null)
        {
            for (Iterator i = searchCriteria.keySet().iterator(); i.hasNext();)
            {
                String key = (String)i.next();
                String value = (String)searchCriteria.get(key);
                if (simpleFilter)
                {
                    URL.appendCGIParam(extra, SIMPLE_FILTER_VALUE, value);
                    break;
                }
                else 
                {
                    URL.appendCGIParam(extra, cgiKey(FILTER_VALUE, key), value);
                }
            }
            if (extra.length() > 0 && !(simpleFilter))
            {
                URL.appendCGIParam(extra, FILTER_CHECK, "TRUE");
            }
        }        
        return extra.toString();
    }    
}
@


1.1.2.2
log
@PeerBackedList can now do ordering and navbar guides based on methods and fields stuck in the appropriate Peer.
@
text
@d63 1
a63 1
 * @@version $Id: PeerBackedList.java,v 1.1.2.1 2001/02/07 02:24:51 leonardr Exp $
a69 3
    private Method pageGuideSource;
    private Map searchableFields;

d72 1
a72 15
    private String simpleFilterString;

    /**
     * The name of the method in the peer to call then making a guide
     * for a page. The method must take an Object (which can always be
     * casted to the type of business object which the peer handles),
     * must return a String, and must not throw any exceptions.
     */
    public static final String PAGE_GUIDE_METHOD_NAME = "pageGuide";

    /**
     * The name of the field, if any, to be used in the order by clause
     * of the Criteria which makes the list.
     */
    public static final String ORDER_FIELD_NAME = "DEFAULT_ORDER_BY";
d101 1
a101 30

        //Get the method, if any, to be invoked when building an alphabetic
        //guide for an object.
        Class [] objectClass = {Object.class};
        try 
        {
            pageGuideSource = peer.getMethod(PAGE_GUIDE_METHOD_NAME, objectClass);
        } 
        catch (Exception ignore) 
        {
            //The peer has no such method. No guide will be printed after
            //each page.
            pageGuideSource = null;
        }

        //Get the field, if any, to be used in an order by statement.
        Field orderField = null;
        try 
        {
            orderField = peer.getField(ORDER_FIELD_NAME);
        } 
        catch (Exception ignore) 
        {
            //The peer has no such field. The objects will be obtained
            //in any particular order.
        }

        //If the criteria we were passed specifies a peer-specific match
        //of any of the specified fields, store that in simpleMatchString
        //and build the peer-appropriate searchCriteria.
d106 1
a106 7

        //Add an order clause, if appropriate, to the big criterion.
        if (orderField != null)
        {
            String orderFieldName = (String)orderField.get(null);
            bigCriteria.addOrderByColumn(orderFieldName);
        }
a108 1
        Criteria [] crit = {bigCriteria};
d118 15
d158 14
a171 2
        if (pageGuideSource == null) return "";
        List thisPage = getPage(page);
d173 7
a179 25
         Object[] first = {thisPage.get(0)};
         Object[] last = {thisPage.get(thisPage.size()-1)};
         String firstVal = null;
         String lastVal = null;

         try 
         {
             firstVal = (String)pageGuideSource.invoke(null, first);
             lastVal = (String)pageGuideSource.invoke(null, last);
         }
         catch (Exception e) 
         {
             throw new Error("Specified pageGuideSorce method threw exception: " + e.getMessage());
         }

         if (firstVal == null || lastVal == null) return "";

         StringBuffer guide = new StringBuffer("(");
         guide.append(firstVal);
         if (!firstVal.equals(lastVal))
         {
             guide.append("-").append(lastVal);
         }
         guide.append(")");
         return guide.toString();
d183 1
a183 1
     * Ensure that the link describes the search criterion properly. 
d188 1
a188 5
        if (simpleFilterString != null)
        {
            URL.appendCGIParam(extra, SIMPLE_FILTER_VALUE, simpleFilterString);
        } 
        else if (searchCriteria != null)
d194 9
a202 1
                URL.appendCGIParam(extra, cgiKey(FILTER_VALUE, key), value);
d204 1
a204 1
            if (extra.length() > 0)
@


1.1.2.3
log
@Changes to support toolbar guides and filtering on paginated lists. Note that filtering does not yet actually work.
@
text
@d63 1
a63 1
 * @@version $Id: PeerBackedList.java,v 1.1.2.2 2001/02/07 16:18:34 leonardr Exp $
d74 1
a74 1
    private Criteria filterCriteria;
a85 8
     * The name of the field in the peer which contains a mapping of
     * human-readable descriptions of fields to the actual database keys
     * themselves, for every database key you want to be OR-searched
     * when a simple filter request comes in.
     */
    public static final String FILTERABLE_FIELDS_FIELD_NAME = "filterableFields";

    /**
d111 1
a111 1
        (Class peer, Integer itemsPerPage, Criteria base, Criteria filter)
d116 1
a120 1
        pageGuideSource = null;
d130 1
d145 3
a147 36
        //If the filter criteria we were passed specifies a
        //peer-specific match of any of the specified fields, store
        //that in simpleMatchString and build the peer-appropriate
        //searchCriteria.
        if (filter == null) filter = new Criteria();
        
        String simpleFilterValue = null;
        if (filter.containsKey(SIMPLE_FILTER_VALUE))
        {
            simpleFilterValue = filter.getString(SIMPLE_FILTER_VALUE);
        }
        if (simpleFilterValue == null)
        {
            this.filterCriteria = filter;
        } else {
            this.filterCriteria = new Criteria();
            try 
            {
                Map map = (Map)peer.getField(FILTERABLE_FIELDS_FIELD_NAME).get(null);
                Log.info("SIMPLE: " + simpleFilterValue);
                for (Iterator i = map.keySet().iterator(); i.hasNext();)
                {
                    String description = (String)i.next();
                    String value = (String)map.get(description);
                    filterCriteria.add(value, (Object)("%"+simpleFilterValue+"%"), Criteria.LIKE);
                    Log.info("Added DOSELECT criteria: " + filterCriteria);
                    break; //TODO: Do an or.                    
                }
            } 
            catch (Exception ignore) 
            {
                //The peer has no mappable fields. This simple filter
                //request will be ignored, and an empty criteria
                //submitted.
            }            
        }
d151 1
a151 1
        bigCriteria.putAll(filterCriteria);
d166 1
a166 2
        clear();       
        Log.info("DOSELECT: " + bigCriteria);
d236 1
a236 1
        else if (filterCriteria != null)
d238 1
a238 1
            for (Iterator i = filterCriteria.keySet().iterator(); i.hasNext();)
d241 1
a241 1
                String value = (String)filterCriteria.get(key);
@


1.1.2.4
log
@Normalized copyright dates.
@
text
@d2 1
a2 1
 * Copyright (c) 2000-2001 CollabNet.  All rights reserved.
d63 1
a63 1
 * @@version $Id: PeerBackedList.java,v 1.1.2.3 2001/02/07 19:49:07 leonardr Exp $
@


1.1.2.5
log
@pageGuideSource method is no longer neccessary in peers. The page guide is obtained automatically from the field used in ordering.
@
text
@a54 1
import org.apache.turbine.om.BaseObject;    
d63 1
a63 1
 * @@version $Id: PeerBackedList.java,v 1.1.2.4 2001/02/15 20:50:45 leonardr Exp $
d70 1
a70 1
    private String orderField;
d78 8
d126 16
a141 3
        //Get the field, if any, to be used in an order by statement and
        //to build the page guides.
        this.orderField = null;
d144 1
a144 2
            Field field = peer.getField(ORDER_FIELD_NAME);
            this.orderField = (String)field.get(null);
d149 1
a149 1
            //in any particular order. Nor will there be any page guides.
d196 2
a197 1
            bigCriteria.addOrderByColumn(orderField);
d231 1
a231 1
   
d237 1
a237 1
        if (orderField == null) return "";
d240 2
a241 2
         BaseObject first = (BaseObject)thisPage.get(0);
         BaseObject last = (BaseObject)thisPage.get(thisPage.size()-1);
a243 5
         
         //TODO: change to getByPeerName; this doesn't work unless you
         //hack it.
         firstVal = makeGuideValue(first.getByName(orderField));
         lastVal = makeGuideValue(last.getByName(orderField));
d245 11
a255 2
         if (firstVal == null || lastVal == null || 
             firstVal=="" || lastVal == "") return "";
a264 18
    }

    /**
     * Turns the value of a field into part of a navbar guide.
     */
    private static String makeGuideValue(Object val)
    {
        String strVal = "";
        if (val != null)
        {
            if (val instanceof Date)
            {
                strVal = val.toString();
            } else if (val instanceof String) {
                strVal = val.toString().substring(0,1).toUpperCase();
            }
        }
        return strVal;
@


1.1.2.6
log
@Fixed filtering.
@
text
@a55 1
import org.apache.turbine.om.peer.BasePeer;    
d64 1
a64 1
 * @@version $Id: PeerBackedList.java,v 1.1.2.5 2001/02/17 00:04:32 leonardr Exp $
a143 2

        this.filterCriteria = base;
d146 1
a146 2
            //A more specific filter was provided, or none at all.
            this.filterCriteria.putAll(filter);
d148 1
a148 3
            //A simple filter value was provided. Build a Criteria
            //which will match the simple filter value in any of the fields
            //which the provided Peer considers filter-worthy.
d151 2
a152 3
                Criteria.Criterion c = null;
                Map map = (Map)peer.getField(FILTERABLE_FIELDS_FIELD_NAME)
                    .get(null);                
d157 3
a159 15
                    //Turn the value into a table name and a value.
                    int splitPoint = value.indexOf(".");
                    String tableName = value.substring(0, splitPoint);
                    value = value.substring(splitPoint+1);
                    Criteria.Criterion p = this.filterCriteria.getNewCriterion
                        (tableName, value, "%"+simpleFilterValue+"%", 
                         Criteria.LIKE);
                    if (c == null)
                    {
                        c = p;
                    } 
                    else
                    {
                        c.or(p);
                    }
a160 1
                this.filterCriteria.add(c);
d170 4
d177 1
a177 1
            this.filterCriteria.addOrderByColumn(orderField);
d181 1
a181 1
        Criteria [] crit = {this.filterCriteria};
d187 1
@


