head     1.1;
branch   1.1.1;
access   ;
symbols  NEWHELM:1.1.1.1 TIGRIS_NOV_12_2000:1.1.1;
locks    ; strict;
comment  @# @;


1.1
date     2000.11.12.21.15.55;  author dlr;  state Exp;
branches 1.1.1.1;
next     ;

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


desc
@@



1.1
log
@Initial revision
@
text
@/* ================================================================
 * 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 COLLAB.NET 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.project;

import java.sql.*;
import java.util.*;

import com.workingdogs.village.*;
import org.webmacro.util.*;
import org.webmacro.servlet.*;

import org.joist.database.*;
import org.tigris.helm.*;
import org.tigris.helm.database.*;
import org.tigris.helm.domain.BusinessObject;
import org.tigris.helm.domain.Category;
import org.tigris.helm.domain.User;
import org.tigris.helm.security.*;
import org.tigris.helm.tool.mail.MailingList;
import org.tigris.helm.tool.*;

public class Project extends BusinessObject
{
    protected UserManager uMgr;
    protected UserUserGroupManager usrGrpMgr;
    protected ProjectManager pMgr;
    protected ProjectProjectGroupManager prjGrpMgr;
    protected UserRoleProjectManager urpMgr;
    protected DomainManager domMgr;

    private static Log log = new Log("proj", "Project");

    ////////////////////////////////////////////////////////////////////
    // Constructors.
    ////////////////////////////////////////////////////////////////////

    public Project (Connection conn, DataManager dMgr, Record rec)
        throws DoesNotExist
    {
        super(conn, dMgr, rec);
    }

    /**
     * Constructor for a project which already exists -- accessed by
     * projectID.
     */
    public Project(Connection conn, DataManager dMgr, String projectID)
        throws SQLException, DataSetException, DoesNotExist
    {
        super(conn, dMgr);
        setRecord(pMgr.getProjectForID(conn, projectID));
    }

    /**
     * Constructor for a project which already exists -- accessed by
     * domainID and project name.
     */
     public Project(Connection c, DataManager dMgr, 
                   String domainID, String name)
         throws SQLException, DataSetException, DoesNotExist
    {
        super(c, dMgr);
        setRecord(pMgr.getProjectForName(c, domainID, name));
    }

    /**
     * Constructor for a project which already exists -- accessed by
     * domainID and alias URL.
     */
    public Project(Connection c, DataManager dMgr, String domainID, 
                   String aliasURL, boolean ignore)
        throws SQLException, DataSetException, JoistDatabaseException,
               DoesNotExist
    {
        super(c, dMgr);
        setRecord(pMgr.getProjectForAliasURL(c, domainID, aliasURL));
    }

    /**
     * Constructor for a new project.
     */
    public Project(Connection c, DataManager dMgr, AccessControlList acl,
                   ProjectProfile profile,
                   String domainID, String name, String license,
                   String shortDescription, String longDescription,
                   String status, String aliasURL, String memberPolicy, 
                   String proposedBy, String projectOwner)
        throws SQLException, DataSetException, JoistDatabaseException,
               AlreadyExists, UpdateException, ACLException, DoesNotExist
    {
        super(c, dMgr);

        if (!acl.hasPermissionForDomain("Project - Suggest", domainID) &&
            !acl.hasPermissionForDomain("Project - Add", domainID))
        {
            throw new ACLException(acl, "Project - Suggest or Project - Add", 
                                   "Domain " + domainID, null);
        }
        
        // Add the project to the database.
        String approvedBy = usrGrpMgr.getNobodyUserID(c);
        setRecord(pMgr.addProject(conn, profile, domainID, name, 
                                  license, shortDescription, longDescription,
                                  "", aliasURL, memberPolicy,
                                  proposedBy, approvedBy));

        //Add the project to the appropriate groups.
        ArrayList groups = new ArrayList();
        groups.add(pMgr.getHostProjectID());
        groups.add(pMgr.getDomainProjectID(domainID));
        for (ListIterator i = groups.listIterator(); i.hasNext();)
        {
            String projectGroup = (String)i.next();
            prjGrpMgr.addProjectToGroup(conn, 
                                        getID(),
                                        projectGroup);
        }

        //Approve the project, if user has the right permission.
        approve(acl);

        // Add the project to CVS, Issuezilla, etc.
        String domainName = domMgr.getDomainName(domainID);
        TigrisUpdate.addProject(domainName, name, getID(), null, 
                                shortDescription);
        
        // Add the mailing lists and modules for the project.
        // Please note that addDefaultMailingLists also takes care of
        // adding the project itself to anzu.  Perhaps the name should
        // change to addProjectMailSetup or somesuch.
        profile.addDefaultMailingLists(conn, dMgr, new Integer(getID()));
        profile.addDefaultModules(conn, dMgr,
                                  domainName, name, new Integer(getID()), 
                                  null);  
        
        //Give someone Project Owner role on this project.
        //
        //If the ACL has Project Approve permission, make
        //the owner whomever they designate.
        //
        //Otherwise, make the ACL's user the owner.        
        String newOwnerID = null;
        if (projectOwner == null ||
            !acl.hasPermissionForDomain("Project - Approve", domainID))
        {
            newOwnerID = acl.getUserID();
        } else {
            newOwnerID = new User(conn, dMgr, domainID, projectOwner).getID();
        }
        
        // HELP: special user for system-approved roles?
        String rootUserID = uMgr.getUserForLoginID(conn, "root")
            .getValue(UserManager.USER_ID).asString();
        urpMgr.grantNamedRole(conn, newOwnerID, "Project Owner", 
                              getID(), rootUserID);
    }

    protected void createManagers ()
    {
        this.dMgr = dMgr;
        this.uMgr = (UserManager)dMgr.getManager
            ("org.joist.database.UserManager");        
        this.pMgr = (ProjectManager)dMgr.getManager
            ("org.tigris.helm.database.ProjectManager");
        this.prjGrpMgr = (ProjectProjectGroupManager)dMgr.getManager
            ("org.tigris.helm.database.ProjectProjectGroupManager");
        this.usrGrpMgr = (UserUserGroupManager)dMgr.getManager
            ("org.tigris.helm.database.UserUserGroupManager");
        this.urpMgr = (UserRoleProjectManager)dMgr
            .getManager("org.tigris.helm.database.UserRoleProjectManager");
        this.domMgr = (DomainManager)dMgr
            .getManager("org.tigris.helm.database.DomainManager");
    }

    ////////////////////////////////////////////////////////////////////
    // Writing functions.
    ////////////////////////////////////////////////////////////////////

    /**
     * Adds the project to a category.
     */
    public void addToCategory(Category cat)
        throws DataSetException, SQLException, JoistDatabaseException,
               UpdateException
    {
        prjGrpMgr.addProjectToGroup(conn, getID(), cat.getID());
    }

    /**
     * Removes the project from a category.
     */
    public void removeFromCategory(Category cat)
        throws DataSetException, SQLException, JoistDatabaseException,
               UpdateException
    {
        prjGrpMgr.removeProjectFromGroup(conn, getID(), cat.getID());
    }

    /**
     * Changes the project category--removes from all old categories
     * and adds to the new one.
     */
    public void changeCategory(Category newCat)
        throws DataSetException, SQLException, JoistDatabaseException,
               UpdateException, DoesNotExist
    {
        for (Iterator i = getCategories().iterator(); i.hasNext();)
        {
            removeFromCategory((Category)i.next());
        }
        //Add the project to the new category.
        addToCategory(newCat);
    }

    /**
     * Deletes the project.
     */
    public void delete(AccessControlList acl)
        throws SQLException, DataSetException, JoistDatabaseException,
               UpdateException, ACLException
    {
        acl.assertPermissionForProject("Project - Delete", getID());
        
        DomainManager domMgr = (DomainManager)
            dMgr.getManager("org.tigris.helm.database.DomainManager");
        ModuleManager modMgr = (ModuleManager)
            dMgr.getManager("org.tigris.helm.database.ModuleManager");
        ProjectProjectGroupManager prjGrpMgr = (ProjectProjectGroupManager)
            dMgr.getManager
            ("org.tigris.helm.database.ProjectProjectGroupManager");

        //Delete this project's modules.
        modMgr.deleteModulesForProject(conn, getID());
        
        //Remove it from its groups.
        prjGrpMgr.removeBaseFromComposites(conn, getID());

        //Remove all roles in the project.

        //TODO: Remove all other references to it. (Downloads etc.)
        
        //TODO: Delete this project's mailing lists.           

        //Delete the project itself.
        pMgr.deleteProject(conn, getID());

        //Delete its CVS repository etc.
        String domainName = domMgr.getDomainName(getDomainID());
        TigrisUpdate.deleteProject(domainName, getName(), getID(), null);
    }

    /**
     * Approves the project.
     */
    public void approve(AccessControlList acl)
        throws SQLException, DataSetException, JoistDatabaseException,
               UpdateException, ACLException
    {
        String action = "Project - Approve";
        if (!acl.hasPermissionForProject(action, getID()))
        {
            return;
        }
        acl.assertPermissionForProject(action, getID());
        pMgr.approveProject(conn, getID(), acl.getUserID());
        //Add the project to the list of public projects, if it's not
        //proprietary.
        if (!Licenses.isProprietary(getLicense()))
        {
            ProjectProjectGroupManager prjGrpMgr = (ProjectProjectGroupManager)
                dMgr.getManager
                ("org.tigris.helm.database.ProjectProjectGroupManager");        
            prjGrpMgr.addProjectToGroup(conn, getID(), 
                                        pMgr.getPublicDomainProjectID 
                                        (getDomainID()));
        }
    }

    /**
     * Updates the project's information.
     */
    public void updateInfo(AccessControlList acl,
                           String license,
                           String shortDescription,
                           String longDescription,
                           String status, 
                           String aliasURL,
                           String memberPolicy
                           )
        throws SQLException, DataSetException, JoistDatabaseException,
               UpdateException, ACLException
    {
        acl.assertPermissionForProject("Project - Edit", getID());
        pMgr.updateProjectInfo(conn, getID(), license, shortDescription,
                               longDescription, status, aliasURL, memberPolicy);
    }
    
    ////////////////////////////////////////////////////////////////////
    // Read-only functions.
    ////////////////////////////////////////////////////////////////////   

    public List getCategories()
        throws DataSetException, SQLException, DoesNotExist
    {
        DataSet categorySet = 
            pMgr.getCategoriesForProject(conn, getID());
        ArrayList categories = new ArrayList();
        for (int i = 0; i < categorySet.size(); i++)
        {                    
            Record r = categorySet.getRecord(i);
            Category c = new Category(conn, dMgr, r);
            categories.add(c);
        }        
        return categories;
    }

    public List getCategoryProjects(AccessControlList acl)
        throws DataSetException, SQLException, DoesNotExist
    {
        DataSet projectSet = 
            pMgr.getProjectsForCategory(conn, getID());
        return AccessControlledDatabaseFilter.getViewableProjects
            (conn, dMgr, acl, projectSet);
    }

    public List getUsers()
        throws DataSetException, SQLException, JoistDatabaseException,
               DoesNotExist
    {
        DataSet userSet = urpMgr.getUsersForProject(conn, 
                                                    getID(), 
                                                    Boolean.TRUE);
        ArrayList userList = new ArrayList();
        for (int i = 0; i < userSet.size(); i++)
        {                    
            Record r = userSet.getRecord(i);
            User u = new User(conn, dMgr, r);
            userList.add(u);
        }
        
        return userList;
    }

    public List getRequestedUsers()
        throws DataSetException, SQLException, JoistDatabaseException,
               DoesNotExist
    {
        DataSet userSet = urpMgr.getUsersForProject(conn, 
                                                    getID(), Boolean.FALSE);
        ArrayList userList = new ArrayList();
        for (int i = 0; i < userSet.size(); i++)
        {                    
            Record r = userSet.getRecord(i);
            User u = new User(conn, dMgr, r);
            userList.add(u);
        }
        
        return userList;
    }

    public String getNumberOfRequestedUsers()
        throws DataSetException, SQLException, JoistDatabaseException, 
               DoesNotExist
    {
        DataSet userSet = urpMgr.getUsersForProject(conn, 
                                                    getID(), Boolean.FALSE);
        ArrayList userList = new ArrayList();
        for (int i = 0; i < userSet.size(); i++)
        {
            Record r = userSet.getRecord(i);
            User u = new User(conn, dMgr, r);
            userList.add(u);
        }
        
        return Integer.toString(userList.size());
    }

    public List getMailingLists()
        throws DataSetException, SQLException, DoesNotExist
    {
	MailingListManager mlMgr = (MailingListManager)
	    dMgr.getManager("org.tigris.helm.database.MailingListManager");
        DataSet mailingListSet = mlMgr.listProjectMailingLists(conn, 
                                                               getID());

        ArrayList mailingLists = new ArrayList();
        for (int i = 0; i < mailingListSet.size(); i++)
        {                    
            Record r = mailingListSet.getRecord(i);
            MailingList m = new MailingList(conn, dMgr, r);
            mailingLists.add(m);
        }
        
        return mailingLists;
    }

    public boolean exists(){
        try {
            String test = getID();
        } catch (Exception e) { // assume it's bad
            return false;
        }
        return true;
    }

    public boolean isCategory()
        throws DataSetException
    {
        String type = getType();
        //TODO: Don't hard-code this.
        return type.equals("category");
    }

    public boolean isApproved()
        throws SQLException, DataSetException, JoistDatabaseException
    {
        return !getApprovedBy().equals(usrGrpMgr.getNobodyUserID(conn));
    }

    ////////////////////////////////////////////////////////////////////
    // Field accessor functions.
    ////////////////////////////////////////////////////////////////////

    public String getID()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_ID).asString();
    }

    public String getDomainID()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_DOMAIN_ID).asString();
    }

    public String getDomainName()
        throws DataSetException, SQLException
    {
        return pMgr.getProjectDomain(conn, getDomainID());
    }

    public String getName()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_NAME).asString();
    }

    public String getLicense()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_LICENSE).asString();
    }

    public String getShortDescription()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_SHORT_DESCRIPTION)
            .asString();
    }

    public String getLongDescription()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_LONG_DESCRIPTION)
            .asString();
    }

    public String getProposedBy()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_PROPOSED_BY).asString();
    }

    public String getProposedByLoginID()
        throws DataSetException, JoistDatabaseException, SQLException,
               DoesNotExist
    {
        String userID = getProposedBy();
        Record r = uMgr.getUser(conn, userID);
        User u = new User(conn, dMgr, r);
        return u.getLoginID();
    }

    public String getProposedEmail()
        throws DataSetException, JoistDatabaseException, SQLException,
               DoesNotExist
    {
        String userID = getProposedBy();
        Record r = uMgr.getUser(conn, userID);
        User u = new User(conn, dMgr, r);
        return u.getEmail();
    }

    public String getProposedByName()
        throws DataSetException, JoistDatabaseException, SQLException,
               DoesNotExist
    {
        String userID = getProposedBy();
        Record r = uMgr.getUser(conn, userID);
        User u = new User(conn, dMgr, r);
        return u.getName();
    }

    public String getApprovedBy()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_APPROVED_BY).asString();
    }

    public String getStatus()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_STATUS).asString();
    }

    public String getType()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_TYPE).asString();
    }

    public String getAliasURL()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_ALIAS_URL).asString();
    }

    public String getMemberPolicy()
        throws DataSetException
    {
        return rec.getValue(ProjectManager.PROJECT_MEMBER_POLICY).asString();
    }
    
    
    
    /** Return the ProjectHome url.
     *
     * @@see #getProjectHome( String )
     */
    public String getProjectHome( )
        throws NullPointerException, 
                IllegalArgumentException,
                DataSetException
    {
        return getProjectHome( ( String )null );
    }
    
    
    /** Return the ProjectHome url.
     *
     * Will return either '/servlets/ProjectHome?projectID=THIS_PROJECT_ID
     * or 'http://THIS_PROJECTNAME.THIS_PROJECT_DOMAIN/servlets/ProjectHome.
     * The switch turns on the <code>env.sh</code> setting IS_VHOSTING.  (The
     * ${SANDOBX}/scripts/configure.pl writes the IS_VHOSTING value to 
     * <code>${SANDBOX}/newhelm/conf/40helm.zone.properties</code>)
     * <p>
     * You would use this method to write out links to this project's
     * ProjectHome page.  For example, say you are browsing 
     * www.wireopen.com and you come upon the ProjectList page that
     * holds a list of all projects.  Calling this method on each of the
     * projects found to write the links for ProjectList will 
     * return the appropriate ProjectHome link. 
     *
     * @@param inProtocol Pass 'http' or 'https'.  If null, we assume 'http'.
     *
     * @@return Either 'PROJECTHOME_SERVLETPATH?projectID=THIS_PROJECT_ID' or
     * 'http://THIS_PROJECTNAME.THIS_PROJECT_DOMAIN/SERVLET_PATH'
     *
     * @@exception NullPointerException If i can't get JoistProperties instance.
     * @@exception IllegalArgumentException Thrown if you pass anything but 
     * 'http' or 'http' in the inProtocol param.
     * @@exception DataSetException Thrown by getName( ).
     */
    public String getProjectHome( String inProtocol )
        throws NullPointerException, 
                IllegalArgumentException,
                DataSetException
    {
        StringBuffer url2Return = new StringBuffer( ); // Build url in here.

        final String DOMAIN_KEY = "DOMAIN";  // Key to pull site domain.
        final String IS_VHOSTING_KEY = "IS_VHOSTING"; // Key for VHOSTING.
        final String SERVLET_PATH = "/servlets/ProjectHome"; 
        final String QUERY_STRING = "?projectID=" + getID( );
        
        // JoistProperties is a singleton of system properties.
        //
        org.joist.util.JoistProperties properties
            = org.joist.util.JoistProperties.getInstance( );
        if( properties == null )
            throw new NullPointerException( "JoistProperties is null" );
                    
        // Test that vhosting is turned on.
        //
        String isVhosting = properties.getProperty( IS_VHOSTING_KEY );
        if( isVhosting != null
            && ( new Boolean( isVhosting ).booleanValue( ) ) )
        {   
            String protocol = getProtocol( inProtocol );
            String port = getPort( properties, protocol );
            String domain = properties.getProperty( DOMAIN_KEY );
            if( domain == null || domain.length( ) <= 0 )
                throw new NullPointerException( DOMAIN_KEY + " is null" );
           
            url2Return.append( protocol );
            url2Return.append( "://" );         
            url2Return.append( getName( ) ); // Add this project's name as host.
            url2Return.append( '.' ); // Dot between project name and domain
            url2Return.append( domain );  
            if( port != null)
            {
                url2Return.append( ":" );
                url2Return.append( port );
            }
            url2Return.append( SERVLET_PATH );
        }
        else // IS_VHOSING is false.  VHOSTING is not turned on.
        {
            url2Return.append( SERVLET_PATH );
            url2Return.append( QUERY_STRING );
        }
        
        return url2Return.toString( );
    }
    
    
    /** Get port to use.
     * 
     * @@param inProperties Instance of JoistProperties.  Used to pull http
     * ports from.
     * @@param inProtocol Either 'http' or 'https'.
     *
     * @@return Port to use as string.  Null if port is 
     *
     * @@exception NullPointerException If i can't get a JoistProperties instance.
     */
     private String getPort( org.joist.util.JoistProperties inProperties,
                                String inProtocol )
        throws NullPointerException
     {
        String port = null; // Port to return
        
        final String HTTP_PROTOCOL = "http";
        final Integer DEFAULT_HTTP_PORT = new Integer( 80 );
        final Integer DEFAULT_HTTPS_PORT = new Integer( 443 );
        final String HTTP_PORT_KEY = "http_port";   // Key to pull http_port
        final String HTTPS_PORT_KEY = "https_port"; // Key to pull https_port.
        
        boolean isHttp = inProtocol.equalsIgnoreCase( HTTP_PROTOCOL );
        Integer defaultPort = ( isHttp )? DEFAULT_HTTP_PORT: DEFAULT_HTTPS_PORT;
        String portKey = ( isHttp )? HTTP_PORT_KEY: HTTPS_PORT_KEY;
        port = inProperties.getProperty( portKey );
        if( port == null )
            throw new NullPointerException( portKey + " is null" );
        if( port.equals( defaultPort.toString( ) ) )
            port = null; // Return null if port is default for the protocol.
            
        return port;
     }
    
    
    /** Figure the protocol to use -- http or https.
     * 
     * @@param inProtocol Pass 'http' or 'https'.  If null, we assume 'http'.
     *
     * @@return Return 'http' or 'https'
     *
     * @@exception IllegalArgumentException Thrown if you pass anything but 
     * 'http' or 'http' in the inProtocol param.
     */
    private String getProtocol( String inProtocol )
        throws NullPointerException, 
                IllegalArgumentException
    {
        final String HTTP_PROTOCOL = "http";
        final String HTTPS_PROTOCOL = "https";
        final String DEFAULT_PROTOCOL = HTTP_PROTOCOL; // Default protocol    
         
        String protocol = DEFAULT_PROTOCOL; // Protocol to return.

        if( inProtocol != null )
            protocol = inProtocol;
        if( !protocol.equalsIgnoreCase( HTTP_PROTOCOL )
            && !protocol.equalsIgnoreCase( HTTPS_PROTOCOL ) )
        {
            throw new IllegalArgumentException( "Unsupported protocol: "
                + inProtocol );
        }
            
        return protocol;
    }
}
@


1.1.1.1
log
@Merging newhelm tree into helm, as per IZ issue #557.  Previous major version of helm is tagged TIGRIS_NOV_11_2000.
@
text
@@
