You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by pg...@apache.org on 2002/10/02 11:53:38 UTC

cvs commit: jakarta-james/src/java/org/apache/james/nntpserver/repository package.html ArticleIDRepository.java NNTPArticle.java NNTPArticleImpl.java NNTPGroup.java NNTPGroupImpl.java NNTPLineReader.java NNTPLineReaderImpl.java NNTPRepository.java NNTPRepositoryImpl.java NNTPSpooler.java NNTPUtil.java

pgoldstein    2002/10/02 02:53:38

  Modified:    src/conf james-assembly.xml
               src/java/org/apache/james/nntpserver AuthServiceImpl.java
                        DateSinceFileFilter.java NNTPHandler.java
                        NNTPServer.java NNTPServer.xinfo
               src/java/org/apache/james/nntpserver/repository
                        ArticleIDRepository.java NNTPArticle.java
                        NNTPArticleImpl.java NNTPGroup.java
                        NNTPGroupImpl.java NNTPLineReader.java
                        NNTPLineReaderImpl.java NNTPRepository.java
                        NNTPRepositoryImpl.java NNTPSpooler.java
                        NNTPUtil.java
  Added:       src/java/org/apache/james/nntpserver package.html
               src/java/org/apache/james/nntpserver/repository package.html
  Log:
  Many changes to the NNTP server.
  
  Corrected a number of outstanding bugs, including issues with command parsing,
  NNTP authentication/authorization, and some group handling.
  
  Added substantial documentation.
  
  A number of issues may remain - a number of TODOs have been added to draw attention
  to some potential problems.
  
  Note that deployment of this distribution will require a change to the assembly.xml
  as per the change to james-assembly.xml
  
  Revision  Changes    Path
  1.11      +1 -1      jakarta-james/src/conf/james-assembly.xml
  
  Index: james-assembly.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/conf/james-assembly.xml,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- james-assembly.xml	30 Sep 2002 17:21:14 -0000	1.10
  +++ james-assembly.xml	2 Oct 2002 09:53:37 -0000	1.11
  @@ -88,7 +88,7 @@
   
     <!-- NNTP Server -->
     <block name="nntpserver" class="org.apache.james.nntpserver.NNTPServer" >
  -    <provide name="nntpauth" role="org.apache.james.nntpserver.AuthService"/>
  +    <provide name="users-store" role="org.apache.james.services.UsersStore"/>
       <provide name="sockets"
                role="org.apache.avalon.cornerstone.services.sockets.SocketManager"/>
       <provide name="connections"
  
  
  
  1.6       +3 -13     jakarta-james/src/java/org/apache/james/nntpserver/AuthServiceImpl.java
  
  Index: AuthServiceImpl.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/AuthServiceImpl.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- AuthServiceImpl.java	18 Aug 2002 07:30:17 -0000	1.5
  +++ AuthServiceImpl.java	2 Oct 2002 09:53:38 -0000	1.6
  @@ -44,8 +44,7 @@
       public boolean isAuthorized(String command) {
           command = command.toUpperCase(Locale.US);
           boolean allowed = isAuthenticated();
  -        // some commads are authorized, even if the user is not authenticated
  -        allowed = allowed || command.equals("AUTHINFO");
  +        // some commands are authorized, even if the user is not authenticated
           allowed = allowed || command.equals("AUTHINFO");
           allowed = allowed || command.equals("MODE");
           allowed = allowed || command.equals("QUIT");
  @@ -53,13 +52,7 @@
       }
   
       /**
  -     * Pass the <code>ComponentManager</code> to the <code>composer</code>.
  -     * The instance uses the specified <code>ComponentManager</code> to 
  -     * acquire the components it needs for execution.
  -     *
  -     * @param componentManager The <code>ComponentManager</code> which this
  -     *                <code>Composable</code> uses.
  -     * @throws ComponentException if an error occurs
  +     * @see org.apache.avalon.framework.component.Composable#compose(ComponentManager)
        */
       public void compose( final ComponentManager componentManager )
           throws ComponentException
  @@ -69,10 +62,7 @@
       }
   
       /**
  -     * Pass the <code>Configuration</code> to the instance.
  -     *
  -     * @param configuration the class configurations.
  -     * @throws ConfigurationException if an error occurs
  +     * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
        */
       public void configure( Configuration configuration ) throws ConfigurationException {
           authRequired =
  
  
  
  1.3       +4 -6      jakarta-james/src/java/org/apache/james/nntpserver/DateSinceFileFilter.java
  
  Index: DateSinceFileFilter.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/DateSinceFileFilter.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- DateSinceFileFilter.java	19 Aug 2002 18:57:07 -0000	1.2
  +++ DateSinceFileFilter.java	2 Oct 2002 09:53:38 -0000	1.3
  @@ -15,8 +15,8 @@
    *
    * @author  Harmeet Bedi <ha...@kodemuse.com>
    */
  -public class DateSinceFileFilter implements FilenameFilter
  -{
  +public class DateSinceFileFilter implements FilenameFilter {
  +
       /**
        * The date that serves as the lower bound of the region of 
        * interest
  @@ -30,8 +30,7 @@
        * @param date the date that serves as the lower bound of the region of 
        * interest
        */
  -    public DateSinceFileFilter( long date ) 
  -    {
  +    public DateSinceFileFilter( long date ) {
           m_date = date;
       }
   
  @@ -44,8 +43,7 @@
        *
        * @return true if the file meets the criteria, false otherwise
        */
  -    public boolean accept( final File dir, final String name ) 
  -    {
  +    public boolean accept( final File dir, final String name ) {
           return (new File(dir,name).lastModified() >= m_date);
       }
   }
  
  
  
  1.17      +592 -261  jakarta-james/src/java/org/apache/james/nntpserver/NNTPHandler.java
  
  Index: NNTPHandler.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/NNTPHandler.java,v
  retrieving revision 1.16
  retrieving revision 1.17
  diff -u -r1.16 -r1.17
  --- NNTPHandler.java	26 Aug 2002 19:46:18 -0000	1.16
  +++ NNTPHandler.java	2 Oct 2002 09:53:38 -0000	1.17
  @@ -15,12 +15,16 @@
   import org.apache.avalon.framework.component.ComponentManager;
   import org.apache.avalon.framework.component.Composable;
   import org.apache.avalon.framework.configuration.Configurable;
  +import org.apache.avalon.framework.configuration.Configuration;
  +import org.apache.avalon.framework.configuration.ConfigurationException;
   import org.apache.avalon.framework.logger.Logger;
   import org.apache.james.BaseConnectionHandler;
   import org.apache.james.nntpserver.repository.NNTPArticle;
   import org.apache.james.nntpserver.repository.NNTPGroup;
   import org.apache.james.nntpserver.repository.NNTPLineReaderImpl;
   import org.apache.james.nntpserver.repository.NNTPRepository;
  +import org.apache.james.services.UsersRepository;
  +import org.apache.james.services.UsersStore;
   import org.apache.james.util.RFC977DateFormat;
   import org.apache.james.util.RFC2980DateFormat;
   import org.apache.james.util.SimplifiedDateFormat;
  @@ -43,62 +47,119 @@
    *
    * @author Fedor Karpelevitch
    * @author Harmeet <hb...@apache.org>
  + * @author Peter M. Goldstein <fa...@alum.mit.edu>
    */
   public class NNTPHandler extends BaseConnectionHandler
       implements ConnectionHandler, Composable, Configurable, Target {
   
  -    // timeout controllers
  +    /**
  +     * used to calculate DATE from - see 11.3
  +     */
  +    private static final SimplifiedDateFormat DF_RFC977 = new RFC977DateFormat();
  +
  +    /**
  +     * Date format for the DATE keyword - see 11.1.1
  +     */
  +    private static final SimplifiedDateFormat DF_RFC2980 = new RFC2980DateFormat();
  +
  +    /**
  +     * The UTC offset for this time zone.
  +     */
  +    public static final long UTC_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
  +
  +    /**
  +     * Timeout controller
  +     */
       private TimeScheduler scheduler;
   
  -    // communciation.
  +    /**
  +     * The TCP/IP socket over which the POP3 interaction
  +     * is occurring
  +     */
       private Socket socket;
  +
  +    /**
  +     * The reader associated with incoming characters.
  +     */
       private BufferedReader reader;
  -    private PrintWriter writer;
   
  -    // authentication & authorization.
  -    private AuthService auth;
  +    /**
  +     * The writer to which outgoing messages are written.
  +     */
  +    private PrintWriter writer;
   
  -    // data abstractions.
  +    /**
  +     * The current newsgroup.
  +     */
       private NNTPGroup group;
  +
  +    /**
  +     * The repository that stores the news articles for this NNTP server.
  +     */
       private NNTPRepository repo;
   
  -    private static final boolean DEBUG_PROTOCOL = 
  -        Boolean.getBoolean("apache.nntpserver.debug");
  +    /**
  +     * The repository that stores the local users.  Used for authentication.
  +     */
  +    private UsersRepository userRepository = null;
   
       /**
  -     * Pass the <code>ComponentManager</code> to the <code>composer</code>.
  -     * The instance uses the specified <code>ComponentManager</code> to 
  -     * acquire the components it needs for execution.
  -     *
  -     * @param componentManager The <code>ComponentManager</code> which this
  -     *                <code>Composable</code> uses.
  -     * @throws ComponentException if an error occurs
  +     * Whether authentication is required to access this NNTP server
  +     */
  +    private boolean authRequired = false;
  +
  +    /**
  +     * The user id associated with the NNTP dialogue
  +     */
  +    private String user;
  +
  +    /**
  +     * The password associated with the NNTP dialogue
  +     */
  +    private String password;
  +
  +
  +    /**
  +     * @see org.apache.avalon.framework.component.Composable#compose(ComponentManager)
        */
       public void compose( final ComponentManager componentManager )
           throws ComponentException
       {
  -        //System.out.println(getClass().getName()+ ": compose - " + authRequired);
  -        auth = (AuthService)componentManager.
  -            lookup( "org.apache.james.nntpserver.AuthService" );
  +        getLogger().debug("NNTPHandler compose...begin");
  +        UsersStore usersStore = (UsersStore)componentManager.lookup(UsersStore.ROLE);
  +        userRepository = usersStore.getRepository("LocalUsers");
  +
           scheduler = (TimeScheduler)componentManager.
               lookup( "org.apache.avalon.cornerstone.services.scheduler.TimeScheduler" );
  +
           repo = (NNTPRepository)componentManager
               .lookup("org.apache.james.nntpserver.repository.NNTPRepository");
  +        getLogger().debug("NNTPHandler compose...end");
       }
   
  +    /**
  +     * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
  +     */
  +    public void configure(Configuration configuration)
  +            throws ConfigurationException {
  +        super.configure(configuration);
  +        getLogger().debug("NNTPHandler configure...begin");
  +        authRequired =
  +            configuration.getChild("authRequired").getValueAsBoolean(false);
  +        getLogger().debug("NNTPHandler configure...end");
  +    }
  +
  +    /**
  +     * @see org.apache.avalon.cornerstone.services.connection.ConnectionHandler#handleConnection(Socket)
  +     */
       public void handleConnection( Socket connection ) throws IOException {
  -        final Logger logger = getLogger();
           try {
               this.socket = connection;
  -            reader = new BufferedReader(new InputStreamReader(socket.getInputStream())) {
  -                    public String readLine() throws IOException {
  -                        String s = super.readLine();
  -                        if ( DEBUG_PROTOCOL ) 
  -                            logger.debug("C: " + s);
  -                        return s;
  -                    }
  -                };
  +            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
               writer = new PrintWriter(socket.getOutputStream()) {
  +                // TODO: This implementation is sensitive to details of the PrintWriter
  +                //       implementation.  Specifically, that println(String) calls println().
  +                //       This should be corrected so that this sensitivity is removed.
                       public void println() {
                           // lines must end with CRLF, irrespective of the OS
                           print("\r\n");
  @@ -106,18 +167,20 @@
                       }
                       public void println(String s) {
                           super.println(s);
  -                        if ( DEBUG_PROTOCOL )
  -                            logger.debug("S: " + s);
  +                        if (getLogger().isDebugEnabled()) {
  +                            getLogger().debug("Sent: " + s);
  +                        }
                       }
                   };
  -            logger.info( "Connection from " + socket.getInetAddress());
           } catch (Exception e) {
  -            logger.error( "Cannot open connection from: " + e.getMessage(), e );
  +            getLogger().error( "Cannot open connection from: " + e.getMessage(), e );
           }
   
  +        String triggerId = this.toString();
  +
           try {
               final PeriodicTimeTrigger trigger = new PeriodicTimeTrigger( timeout, -1 );
  -            scheduler.addTrigger( this.toString(), trigger, this );
  +            scheduler.addTrigger( triggerId, trigger, this );
   
               // section 7.1
               if ( repo.isReadOnly() ) {
  @@ -127,8 +190,7 @@
                           .append(helloName)
                           .append(" NNTP Service Ready, posting prohibited");
                   writer.println(respBuffer.toString());
  -            }
  -            else {
  +            } else {
                   StringBuffer respBuffer =
                       new StringBuffer(128)
                               .append("200 ")
  @@ -138,39 +200,45 @@
               }
   
               while (parseCommand(reader.readLine())) {
  -                scheduler.resetTrigger(this.toString());
  +                scheduler.resetTrigger(triggerId);
               }
   
  -            scheduler.removeTrigger(this.toString());
  -            logger.info("Connection closed");
  +            getLogger().info("Connection closed");
           } catch (Exception e) {
  -            doQUIT();
  -            //writer.println("502 Error closing connection.");
  -            //writer.flush();
  -            logger.error( "Exception during connection:" + e.getMessage(), e );
  +            doQUIT(null);
  +            getLogger().error( "Exception during connection:" + e.getMessage(), e );
           } finally {
               try {
                   if (reader != null) {
                       reader.close();
                   }
               } catch (IOException ioe) {
  -                logger.warn("NNTPHandler: Unexpected exception occurred while closing reader: " + ioe);
  +                getLogger().warn("NNTPHandler: Unexpected exception occurred while closing reader: " + ioe);
               }
   
               if (writer != null) {
                   writer.close();
               }
   
  +            scheduler.removeTrigger(triggerId);
  +
               try {
                   if (socket != null) {
                       socket.close();
                   }
               } catch (IOException ioe) {
  -                logger.warn("NNTPHandler: Unexpected exception occurred while closing socket: " + ioe);
  +                getLogger().warn("NNTPHandler: Unexpected exception occurred while closing socket: " + ioe);
               }
           }
       }
   
  +    /**
  +     * Callback method called when the the PeriodicTimeTrigger in 
  +     * handleConnection is triggered.  In this case the trigger is
  +     * being used as a timeout, so the method simply closes the connection.
  +     *
  +     * @param triggerName the name of the trigger
  +     */
       public void targetTriggered( final String triggerName ) {
           getLogger().error("Connection timeout on socket");
           try {
  @@ -195,101 +263,144 @@
           if (commandRaw == null) {
               return false;
           }
  -        if (getLogger().isInfoEnabled()) {
  -            getLogger().info("Command received: " + commandRaw);
  +        if (getLogger().isDebugEnabled()) {
  +            getLogger().debug("Command received: " + commandRaw);
           }
   
  -        StringTokenizer tokens = new StringTokenizer(commandRaw);
  -        if (!tokens.hasMoreTokens())
  -            return false;
  -        final String command = tokens.nextToken().toUpperCase(Locale.US);
  +        String command = commandRaw.trim();
  +        String argument = null;
  +        int spaceIndex = command.indexOf(" ");
  +        if (spaceIndex >= 0) {
  +            argument = command.substring(spaceIndex + 1);
  +            command = command.substring(0, spaceIndex);
  +        }
  +        command = command.toUpperCase(Locale.US);
   
  -        if (!auth.isAuthorized(command) ) {
  +        if (!isAuthorized(command) ) {
               writer.println("502 User is not authenticated");
               getLogger().debug("Command not allowed.");
               return true;
           }
  -        if ( command.equals("MODE") && tokens.hasMoreTokens() &&
  -             tokens.nextToken().toUpperCase(Locale.US).equals("READER") )
  -            doMODEREADER();
  -        else if ( command.equals("LIST") && tokens.hasMoreTokens() &&
  -                  tokens.nextToken().toUpperCase(Locale.US).equals("EXTENSIONS") )
  -            doLISTEXTENSIONS();
  -        else if ( command.equals("LIST") && tokens.hasMoreTokens() &&
  -                  tokens.nextToken().toUpperCase(Locale.US).equals("OVERVIEW.FMT") )
  -            doLISTOVERVIEWFMT();
  -        else if ( command.equals("GROUP") )
  -            doGROUP(tokens.hasMoreTokens()?tokens.nextToken():null);
  -        else if ( command.equals("NEXT") )
  -            doNEXT();
  -        else if ( command.equals("LAST") )
  -            doLAST();
  -        else if ( command.equals("ARTICLE") )
  -            doARTICLE(tokens.hasMoreTokens()?tokens.nextToken():null);
  -        else if ( command.equals("HEAD") )
  -            doHEAD(tokens.hasMoreTokens()?tokens.nextToken():null);
  -        else if ( command.equals("BODY") )
  -            doBODY(tokens.hasMoreTokens()?tokens.nextToken():null);
  -        else if ( command.equals("STAT") )
  -            doSTAT(tokens.hasMoreTokens()?tokens.nextToken():null);
  -        else if ( command.equals("POST") )
  -            doPOST();
  -        else if ( command.equals("IHAVE") )
  -            doIHAVE(tokens.hasMoreTokens()?tokens.nextToken():null);
  -        else if ( command.equals("LIST") )
  -            doLIST(tokens);
  -        else if ( command.equals("QUIT") )
  -            doQUIT();
  -        else if ( command.equals("DATE") )
  -            doDATE();
  -        else if ( command.equals("HELP") )
  -            doHELP();
  -        else if ( command.equals("NEWGROUPS") )
  -            doNEWGROUPS(tokens);
  -        else if ( command.equals("NEWNEWS") )
  -            doNEWNEWS(tokens);
  -        else if ( command.equals("LISTGROUP") )
  -            doLISTGROUP(tokens.hasMoreTokens()?tokens.nextToken():null);
  -        else if ( command.equals("OVER") )
  -            doOVER(tokens.hasMoreTokens()?tokens.nextToken():null);
  -        else if ( command.equals("XOVER") )
  -            doXOVER(tokens.hasMoreTokens()?tokens.nextToken():null);
  -        else if ( command.equals("PAT") )
  -            doPAT();
  -        else if ( command.equals("HDR") )
  -            doHDR(tokens);
  -        else if ( command.equals("XHDR") )
  -            doXHDR(tokens);
  -        else if ( command.equals("AUTHINFO") )
  -            doAUTHINFO(tokens);
  -        else
  -            writer.println("501 Syntax error");
  +        if ((command.equals("MODE")) && (argument != null) &&
  +            argument.toUpperCase(Locale.US).equals("READER")) {
  +            doMODEREADER(argument);
  +        } else if ( command.equals("LIST")) {
  +            doLIST(argument);
  +        } else if ( command.equals("GROUP") ) {
  +            doGROUP(argument);
  +        } else if ( command.equals("NEXT") ) {
  +            doNEXT(argument);
  +        } else if ( command.equals("LAST") ) {
  +            doLAST(argument);
  +        } else if ( command.equals("ARTICLE") ) {
  +            doARTICLE(argument);
  +        } else if ( command.equals("HEAD") ) {
  +            doHEAD(argument);
  +        } else if ( command.equals("BODY") ) {
  +            doBODY(argument);
  +        } else if ( command.equals("STAT") ) {
  +            doSTAT(argument);
  +        } else if ( command.equals("POST") ) {
  +            doPOST(argument);
  +        } else if ( command.equals("IHAVE") ) {
  +            doIHAVE(argument);
  +        } else if ( command.equals("QUIT") ) {
  +            doQUIT(argument);
  +        } else if ( command.equals("DATE") ) {
  +            doDATE(argument);
  +        } else if ( command.equals("HELP") ) {
  +            doHELP(argument);
  +        } else if ( command.equals("NEWGROUPS") ) {
  +            doNEWGROUPS(argument);
  +        } else if ( command.equals("NEWNEWS") ) {
  +            doNEWNEWS(argument);
  +        } else if ( command.equals("LISTGROUP") ) {
  +            doLISTGROUP(argument);
  +        } else if ( command.equals("OVER") ) {
  +            doOVER(argument);
  +        } else if ( command.equals("XOVER") ) {
  +            doXOVER(argument);
  +        } else if ( command.equals("PAT") ) {
  +            doPAT(argument);
  +        } else if ( command.equals("HDR") ) {
  +            doHDR(argument);
  +        } else if ( command.equals("XHDR") ) {
  +            doXHDR(argument);
  +        } else if ( command.equals("AUTHINFO") ) {
  +            doAUTHINFO(argument);
  +        } else {
  +            doUnknownCommand(command, argument);
  +        }
           return (command.equals("QUIT") == false);
       }
   
  -    // implements only the originnal AUTHINFO
  -    // for simple and generic AUTHINFO, 501 is sent back. This is as
  -    // per article 3.1.3 of RFC 2980
  -    private void doAUTHINFO(StringTokenizer tok) {
  -        String command = tok.nextToken().toUpperCase(Locale.US);
  +    /**
  +     * Handles an unrecognized command, logging that.
  +     *
  +     * @param command the command received from the client
  +     * @param argument the argument passed in with the command
  +     */
  +    private void doUnknownCommand(String command, String argument) {
  +        if (getLogger().isDebugEnabled()) {
  +            StringBuffer logBuffer =
  +                new StringBuffer(128)
  +                    .append("Received unknown command ")
  +                    .append(command)
  +                    .append(" with argument ")
  +                    .append(argument);
  +            getLogger().debug(logBuffer.toString());
  +        }
  +        writer.println("501 Syntax error");
  +    }
  +
  +    /**
  +     * Implements only the originnal AUTHINFO.
  +     * for simple and generic AUTHINFO, 501 is sent back. This is as
  +     * per article 3.1.3 of RFC 2980
  +     *
  +     * @param argument the argument passed in with the AUTHINFO command
  +     */
  +    private void doAUTHINFO(String argument) {
  +        String command = null;
  +        String value = null;
  +        if (argument != null) {
  +            int spaceIndex = argument.indexOf(" ");
  +            if (spaceIndex >= 0) {
  +                command = argument.substring(0, spaceIndex);
  +                value = argument.substring(spaceIndex + 1);
  +            }
  +        }
  +        if (command == null) {
  +            writer.println("501 Syntax error");
  +            return;
  +        }
           if ( command.equals("USER") ) {
  -            auth.setUser(tok.nextToken());
  +            user = value;
               writer.println("381 More authentication information required");
           } else if ( command.equals("PASS") ) {
  -            auth.setPassword(tok.nextToken());
  -            if ( auth.isAuthenticated() ) {
  +            password = value;
  +            if ( isAuthenticated() ) {
                   writer.println("281 Authentication accepted");
  -            }
  -            else {
  +            } else {
                   writer.println("482 Authentication rejected");
  +                // Clear bad authentication
  +                user = null;
  +                password = null;
               }
           }
       }
   
  -    private void doNEWNEWS(StringTokenizer tok) {
  +    /**
  +     * Lists the articles posted since the date passed in as
  +     * an argument.
  +     *
  +     * @param argument the argument passed in with the NEWNEWS command.
  +     *                 Should be a date.
  +     */
  +    private void doNEWNEWS(String argument) {
           // see section 11.4
           writer.println("230 list of new articles by message-id follows");
  -        Iterator iter = repo.getArticlesSince(getDateFrom(tok));
  +        Iterator iter = repo.getArticlesSince(getDateFrom(argument));
           while ( iter.hasNext() ) {
               StringBuffer iterBuffer =
                   new StringBuffer(64)
  @@ -300,7 +411,15 @@
           }
           writer.println(".");
       }
  -    private void doNEWGROUPS(StringTokenizer tok) {
  +
  +    /**
  +     * Lists the groups added since the date passed in as
  +     * an argument.
  +     *
  +     * @param argument the argument passed in with the NEWNEWS command.
  +     *                 Should be a date.
  +     */
  +    private void doNEWGROUPS(String argument) {
           // see section 11.3
           // there seeem to be few differences.
           // draft-ietf-nntpext-base-15.txt mentions 231 in section 11.3.1, 
  @@ -310,7 +429,7 @@
           // '<group name> <last article> <first article> <posting allowed>'
           // NOTE: following INN over either document.
           writer.println("231 list of new newsgroups follows");
  -        Iterator iter = repo.getGroupsSince(getDateFrom(tok));
  +        Iterator iter = repo.getGroupsSince(getDateFrom(argument));
           while ( iter.hasNext() ) {
               NNTPGroup group = (NNTPGroup)iter.next();
               StringBuffer iterBuffer =
  @@ -326,52 +445,23 @@
           }
           writer.println(".");
       }
  -    // returns the date from @param input.
  -    // The input tokens are assumed to be in format date time [GMT|UTC] .
  -    // 'date' is in format [XX]YYMMDD. 'time' is in format 'HHMMSS'
  -    // NOTE: This routine could do with some format checks.
  -    private Date getDateFrom(StringTokenizer tok) {
  -        String date = tok.nextToken();
  -        String time = tok.nextToken();
  -        boolean  utc = ( tok.hasMoreTokens() );
  -        Date d = new Date();
  -        try {
  -            StringBuffer dateStringBuffer =
  -                new StringBuffer(64)
  -                    .append(date)
  -                    .append(" ")
  -                    .append(time);
  -            Date dt = DF_RFC977.parse(dateStringBuffer.toString());
  -            if ( utc )
  -                dt = new Date(dt.getTime()+UTC_OFFSET);
  -            return dt;
  -        } catch ( ParseException pe ) {
  -            StringBuffer exceptionBuffer =
  -                new StringBuffer(128)
  -                    .append("date extraction failed: ")
  -                    .append(date)
  -                    .append(",")
  -                    .append(time)
  -                    .append(",")
  -                    .append(utc);
  -            throw new NNTPException(exceptionBuffer.toString());
  -        }
  -    }
   
  -    private void doHELP() {
  +    /**
  +     * Lists the help text for the service.
  +     *
  +     * @param argument the argument passed in with the HELP command.
  +     */
  +    private void doHELP(String argument) {
           writer.println("100 Help text follows");
           writer.println(".");
       }
   
  -    // used to calculate DATE from - see 11.3
  -    public static final SimplifiedDateFormat DF_RFC977 = new RFC977DateFormat();
  -
  -    // Date format for the DATE keyword - see 11.1.1
  -    public static final SimplifiedDateFormat DF_RFC2980 = new RFC2980DateFormat();
  -
  -    public static final long UTC_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
  -
  -    private void doDATE() {
  +    /**
  +     * Returns the current date according to the news server.
  +     *
  +     * @param argument the argument passed in with the DATE command
  +     */
  +    private void doDATE(String argument) {
           //Calendar c = Calendar.getInstance();
           //long UTC_OFFSET = c.get(c.ZONE_OFFSET) + c.get(c.DST_OFFSET);
           Date dt = new Date(System.currentTimeMillis()-UTC_OFFSET);
  @@ -379,34 +469,61 @@
           writer.println("111 "+dtStr);
       }
   
  -    private void doQUIT() {
  +    /**
  +     * Quits the transaction.
  +     *
  +     * @param argument the argument passed in with the QUIT command
  +     */
  +    private void doQUIT(String argument) {
           writer.println("205 closing connection");
       }
   
  -    private void doLIST(StringTokenizer tok) {
  +    /**
  +     * Handles the LIST command and its assorted extensions.
  +     *
  +     * @param argument the argument passed in with the LIST command.
  +     */
  +    private void doLIST(String argument) {
  +
           // see section 9.4.1
           String wildmat = "*";
  -        LISTGroup output = LISTGroup.Factory.ACTIVE(writer);
  -        if ( tok.hasMoreTokens() ) {
  -            String param = tok.nextToken().toUpperCase(Locale.US);
  -            // list of variations not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
  -            String[] notSupported = { "ACTIVE.TIMES", "DISTRIBUTIONS", "DISTRIB.PATS" };
  -            // TODO: I don't understand what this loop is trying to accomplish -- PMG
  -            for ( int i = 0 ; i < notSupported.length ; i++ ) {
  -                if ( param.equals("ACTIVE.TIMES") ) {
  -                    writer.println("503 program error, function not performed");
  -                    return;
  -                }
  -            }
  -            if ( param.equals("NEWSGROUPS") ) {
  -                output = LISTGroup.Factory.NEWSGROUPS(writer);
  -            }
  -            else {
  -                check(param,param.equals("ACTIVE"));
  -            }
  -            if ( tok.hasMoreTokens() ) {
  -                wildmat = tok.nextToken();
  +        LISTGroup output = null;
  +
  +        String extension = argument;
  +        if (argument != null) {
  +            int spaceIndex = argument.indexOf(" ");
  +            if (spaceIndex >= 0) {
  +                wildmat = argument.substring(spaceIndex + 1);
  +                extension = argument.substring(0, spaceIndex);
               }
  +            extension = extension.toUpperCase(Locale.US);
  +        }
  +
  +        if ((extension == null) || (extension.equals("ACTIVE"))) {
  +            output = LISTGroup.Factory.ACTIVE(writer);
  +        } else if (extension.equals("NEWSGROUPS") ) {
  +            output = LISTGroup.Factory.NEWSGROUPS(writer);
  +        } else if (extension.equals("EXTENSIONS") ) {
  +            doLISTEXTENSIONS();
  +            return;
  +        } else if (extension.equals("OVERVIEW.FMT") ) {
  +            doLISTOVERVIEWFMT();
  +            return;
  +        } else if (extension.equals("ACTIVE.TIMES") ) {
  +            // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
  +            writer.println("503 program error, function not performed");
  +            return;
  +        } else if (extension.equals("DISTRIBUTIONS") ) {
  +            // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
  +            writer.println("503 program error, function not performed");
  +            return;
  +        } else if (extension.equals("DISTRIB.PATS") ) {
  +            // not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
  +            writer.println("503 program error, function not performed");
  +            return;
  +        } else {
  +            writer.println("501 Syntax error");
  +            return;
           }
           Iterator iter = repo.getMatchedGroups(wildmat);
           writer.println("215 list of newsgroups follows");
  @@ -415,59 +532,108 @@
           }
           writer.println(".");
       }
  -    private void check(String id,boolean b) {
  -        if ( b == false ) {
  -            throw new RuntimeException(id);
  -        }
  -    }
  +
  +    /**
  +     * Informs the server that the client has an article with the specified
  +     * message-ID.
  +     *
  +     * @param id the message id
  +     */
       private void doIHAVE(String id) {
           // see section 9.3.2.1
  -        check(id,id.startsWith("<") && id.endsWith(">"));
  +        if (!(id.startsWith("<") && id.endsWith(">"))) {
  +            writer.println("501 command syntax error");
  +            return;
  +        }
           NNTPArticle article = repo.getArticleFromID(id);
  -        if ( article != null )
  +        if ( article != null ) {
               writer.println("435 article not wanted - do not send it");
  -        else {
  +        } else {
               writer.println("335 send article to be transferred. End with <CR-LF>.<CR-LF>");
               createArticle();
               writer.println("235 article received ok");
           }
       }
   
  -    private void doPOST() {
  +    /**
  +     * Posts an article to the news server.
  +     *
  +     * @param argument the argument passed in with the AUTHINFO command
  +     */
  +    private void doPOST(String argument) {
           // see section 9.3.1.1
           writer.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
           createArticle();
           writer.println("240 article received ok");
       }
   
  -    private void createArticle() {
  -        repo.createArticle(new NNTPLineReaderImpl(reader));
  -    }
  -
  +    /**
  +     * Sets the current article pointer.
  +     *
  +     * @param the argument passed in to the HEAD command,
  +     *        which should be an article number or message id.
  +     *        If no parameter is provided, the current selected
  +     *        article is used.
  +     */
       private void doSTAT(String param) {
           doARTICLE(param,ArticleWriter.Factory.STAT(writer));
       }
   
  +    /**
  +     * Displays the body of a particular article.
  +     *
  +     * @param the argument passed in to the HEAD command,
  +     *        which should be an article number or message id.
  +     *        If no parameter is provided, the current selected
  +     *        article is used.
  +     */
       private void doBODY(String param) {
           doARTICLE(param,ArticleWriter.Factory.BODY(writer));
       }
   
  +    /**
  +     * Displays the header of a particular article.
  +     *
  +     * @param the argument passed in to the HEAD command,
  +     *        which should be an article number or message id.
  +     *        If no parameter is provided, the current selected
  +     *        article is used.
  +     */
       private void doHEAD(String param) {
           doARTICLE(param,ArticleWriter.Factory.HEAD(writer));
       }
   
  +    /**
  +     * Displays the header and body of a particular article.
  +     *
  +     * @param the argument passed in to the HEAD command,
  +     *        which should be an article number or message id.
  +     *        If no parameter is provided, the current selected
  +     *        article is used.
  +     */
       private void doARTICLE(String param) {
           doARTICLE(param,ArticleWriter.Factory.ARTICLE(writer));
       }
   
  +    /**
  +     * The implementation of the method body for the ARTICLE, STAT,
  +     * HEAD, and BODY commands.
  +     *
  +     * @param the argument passed in to the command,
  +     *        which should be an article number or message id.
  +     *        If no parameter is provided, the current selected
  +     *        article is used.
  +     * @param articleWriter the writer that determines the output
  +     *                      written to the client.
  +     */
       private void doARTICLE(String param,ArticleWriter articleWriter) {
           // section 9.2.1
           NNTPArticle article = null;
           if ( (param != null) && param.startsWith("<") && param.endsWith(">") ) {
               article = repo.getArticleFromID(param);
  -            if ( article == null )
  +            if ( article == null ) {
                   writer.println("430 no such article");
  -            else {
  +            } else {
                   StringBuffer respBuffer =
                       new StringBuffer(64)
                               .append("220 0 ")
  @@ -475,22 +641,23 @@
                               .append(" article retrieved and follows");
                   writer.println(respBuffer.toString());
               }
  -        }
  -        else {
  -            if ( group == null )
  +        } else {
  +            if ( group == null ) {
                   writer.println("412 no newsgroup selected");
  -            else {
  +            } else {
                   if ( param == null ) {
  -                    if ( group.getCurrentArticleNumber() < 0 )
  +                    if ( group.getCurrentArticleNumber() < 0 ) {
                           writer.println("420 no current article selected");
  -                    else
  +                    } else {
                           article = group.getCurrentArticle();
  +                    }
                   }
  -                else
  +                else {
                       article = group.getArticle(Integer.parseInt(param));
  -                if ( article == null )
  +                }
  +                if ( article == null ) {
                       writer.println("423 no such article number in this group");
  -                else {
  +                } else {
                       StringBuffer respBuffer =
                           new StringBuffer(128)
                                   .append("220 ")
  @@ -502,19 +669,25 @@
                   }
               }
           }
  -        if ( article != null )
  +        if ( article != null ) {
               articleWriter.write(article);
  +        }   
       }
   
  -    private void doNEXT() {
  +    /**
  +     * Advances the current article pointer to the next article in the group.
  +     *
  +     * @param argument the argument passed in with the NEXT command
  +     */
  +    private void doNEXT(String argument) {
           // section 9.1.1.3.1
  -        if ( group == null )
  +        if ( group == null ) {
               writer.println("412 no newsgroup selected");
  -        else if ( group.getCurrentArticleNumber() < 0 )
  +        } else if ( group.getCurrentArticleNumber() < 0 ) {
               writer.println("420 no current article has been selected");
  -        else if ( group.getCurrentArticleNumber() >= group.getLastArticleNumber() )
  +        } else if ( group.getCurrentArticleNumber() >= group.getLastArticleNumber() ) {
               writer.println("421 no next article in this group");
  -        else {
  +        } else {
               group.setCurrentArticleNumber(group.getCurrentArticleNumber()+1);
               NNTPArticle article = group.getCurrentArticle();
               StringBuffer respBuffer =
  @@ -527,15 +700,21 @@
           }
       }
   
  -    private void doLAST() {
  +    /**
  +     * Advances the currently selected article pointer to the last article
  +     * in the selected group.
  +     *
  +     * @param argument the argument passed in with the LAST command
  +     */
  +    private void doLAST(String argument) {
           // section 9.1.1.2.1
  -        if ( group == null )
  +        if ( group == null ) {
               writer.println("412 no newsgroup selected");
  -        else if ( group.getCurrentArticleNumber() < 0 )
  +        } else if ( group.getCurrentArticleNumber() < 0 ) {
               writer.println("420 no current article has been selected");
  -        else if ( group.getCurrentArticleNumber() <= group.getFirstArticleNumber() )
  +        } else if ( group.getCurrentArticleNumber() <= group.getFirstArticleNumber() ) {
               writer.println("422 no previous article in this group");
  -        else {
  +        } else {
               group.setCurrentArticleNumber(group.getCurrentArticleNumber()-1);
               NNTPArticle article = group.getCurrentArticle();
               StringBuffer respBuffer =
  @@ -548,12 +727,18 @@
           }
       }
   
  +    /**
  +     * Selects a group to be the current newsgroup.
  +     *
  +     * @param group the name of the group being selected.
  +     */
       private void doGROUP(String groupName) {
  -        group = repo.getGroup(groupName);
  +        NNTPGroup newGroup = repo.getGroup(groupName);
           // section 9.1.1.1
  -        if ( group == null )
  +        if ( newGroup == null ) {
               writer.println("411 no such newsgroup");
  -        else {
  +        } else {
  +            group = newGroup;
               // if the number of articles in group == 0
               // then the server may return this information in 3 ways, 
               // The clients must honor all those 3 ways.
  @@ -577,6 +762,9 @@
           }
       }
   
  +    /**
  +     * Lists the extensions supported by this news server.
  +     */
       private void doLISTEXTENSIONS() {
           // 8.1.1
           writer.println("202 Extensions supported:");
  @@ -589,28 +777,43 @@
           writer.println(".");
       }
   
  -    private void doMODEREADER() {
  +    /**
  +     * Informs the server that the client is a newsreader.
  +     *
  +     * @param argument the argument passed in with the MODE READER command
  +     */
  +    private void doMODEREADER(String argument) {
           // 7.2
           writer.println(repo.isReadOnly()
                          ? "201 Posting Not Permitted" : "200 Posting Permitted");
       }
   
  +    /**
  +     * Gets a listing of article numbers in specified group name
  +     * or in the already selected group if the groupName is null.
  +     *
  +     * @param groupName the name of the group to list
  +     */
       private void doLISTGROUP(String groupName) {
           // 9.5.1.1.1
  -        NNTPGroup group = null;
           if (groupName==null) {
  -            if ( group == null )
  +            if ( group == null) {
                   writer.println("412 not currently in newsgroup");
  +                return;
  +            }
           }
           else {
               group = repo.getGroup(groupName);
  -            if ( group == null )
  +            if ( group == null ) {
                   writer.println("411 no such newsgroup");
  +                return;
  +            }
           }
           if ( group != null ) {
               writer.println("211 list of article numbers follow");
   
  -            for (Iterator iter = group.getArticles();iter.hasNext();) {
  +            Iterator iter = group.getArticles();
  +            while (iter.hasNext()) {
                   NNTPArticle article = (NNTPArticle)iter.next();
                   writer.println(article.getArticleNumber());
               }
  @@ -620,33 +823,62 @@
           }
       }
   
  +    /**
  +     * Handles the LIST OVERVIEW.FMT command.  Not supported.
  +     */
       private void doLISTOVERVIEWFMT() {
           // 9.5.3.1.1
           // 503 means information is not available as per 9.5.2.1
           writer.println("503 program error, function not performed");
       }
  -    private void doPAT() {
  +
  +    /**
  +     * Handles the PAT command.  Not supported.
  +     *
  +     * @param argument the argument passed in with the PAT command
  +     */
  +    private void doPAT(String argument) {
           // 9.5.3.1.1 in draft-12
           writer.println("500 Command not recognized");
       }
  -    private void doXHDR(StringTokenizer tok) {
  -        doHDR(tok);
  +
  +    /**
  +     * Get the values of the headers for the selected newsgroup, 
  +     * with an optional range modifier.
  +     *
  +     * @param argument the argument passed in with the XHDR command.
  +     */
  +    private void doXHDR(String argument) {
  +        doHDR(argument);
       }
  -    private void doHDR(StringTokenizer tok) {
  +
  +    /**
  +     * Get the values of the headers for the selected newsgroup, 
  +     * with an optional range modifier.
  +     *
  +     * @param argument the argument passed in with the HDR command.
  +     */
  +    private void doHDR(String argument) {
           // 9.5.3
  -        String hdr = tok.nextToken();
  -        String range = tok.hasMoreTokens() ? tok.nextToken() : null;
  +        String hdr = argument;
  +        String range = null;
  +        int spaceIndex = argument.indexOf(" ");
  +        if (spaceIndex >= 0 ) {
  +            range = hdr.substring(spaceIndex + 1);
  +            hdr = hdr.substring(0, spaceIndex);
  +        }
           NNTPArticle[] article = getRange(range);
  -        if ( article == null )
  +        if ( article == null ) {
               writer.println("412 no newsgroup selected");
  -        else if ( article.length == 0 )
  +        } else if ( article.length == 0 ) {
               writer.println("430 no such article");
  -        else {
  +        } else {
               writer.println("221 Header follows");
               for ( int i = 0 ; i < article.length ; i++ ) {
                   String val = article[i].getHeader(hdr);
  -                if ( val == null )
  +                if ( val == null ) {
                       val = "";
  +                }
                   StringBuffer hdrBuffer =
                       new StringBuffer(128)
                               .append(article[i].getArticleNumber())
  @@ -657,9 +889,92 @@
               writer.println(".");
           }
       }
  -    // returns the list of articles that match the range.
  -    // @return null indicates insufficient information to
  -    // fetch the list of articles
  +
  +    /**
  +     * Returns information from the overview database regarding the
  +     * current article, or a range of articles.
  +     *
  +     * @param range the optional article range.
  +     */
  +    private void doXOVER(String range) {
  +        doOVER(range);
  +    }
  +
  +    /**
  +     * Returns information from the overview database regarding the
  +     * current article, or a range of articles.
  +     *
  +     * @param range the optional article range.
  +     */
  +    private void doOVER(String range) {
  +        // 9.5.2.2.1
  +        if ( group == null ) {
  +            writer.println("412 No newsgroup selected");
  +            return;
  +        }
  +        NNTPArticle[] article = getRange(range);
  +        ArticleWriter articleWriter = ArticleWriter.Factory.OVER(writer);
  +        if ( article.length == 0 ) {
  +            writer.println("420 No article(s) selected");
  +        } else {
  +            writer.println("224 Overview information follows");
  +            for ( int i = 0 ; i < article.length ; i++ ) {
  +                articleWriter.write(article[i]);
  +            }
  +            writer.println(".");
  +        }
  +    }
  +
  +    /**
  +     * Handles the transaction for getting the article data.
  +     */
  +    private void createArticle() {
  +        repo.createArticle(new NNTPLineReaderImpl(reader));
  +    }
  +
  +    /**
  +     * Returns the date from @param input.
  +     * The input tokens are assumed to be in format date time [GMT|UTC] .
  +     * 'date' is in format [XX]YYMMDD. 'time' is in format 'HHMMSS'
  +     * NOTE: This routine could do with some format checks.
  +     *
  +     * @param argument the date string
  +     */
  +    private Date getDateFrom(String argument) {
  +        StringTokenizer tok = new StringTokenizer(argument, " ");
  +        String date = tok.nextToken();
  +        String time = tok.nextToken();
  +        boolean utc = ( tok.hasMoreTokens() );
  +        Date d = new Date();
  +        try {
  +            StringBuffer dateStringBuffer =
  +                new StringBuffer(64)
  +                    .append(date)
  +                    .append(" ")
  +                    .append(time);
  +            Date dt = DF_RFC977.parse(dateStringBuffer.toString());
  +            if ( utc ) {
  +                dt = new Date(dt.getTime()+UTC_OFFSET);
  +            }
  +            return dt;
  +        } catch ( ParseException pe ) {
  +            StringBuffer exceptionBuffer =
  +                new StringBuffer(128)
  +                    .append("date extraction failed: ")
  +                    .append(date)
  +                    .append(",")
  +                    .append(time)
  +                    .append(",")
  +                    .append(utc);
  +            throw new NNTPException(exceptionBuffer.toString());
  +        }
  +    }
  +
  +    /**
  +     * Returns the list of articles that match the range.
  +     * @return null indicates insufficient information to
  +     * fetch the list of articles
  +     */
       private NNTPArticle[] getRange(String range) {
           // check for msg id
           if ( range != null && range.startsWith("<") ) {
  @@ -668,10 +983,12 @@
                   ? new NNTPArticle[0] : new NNTPArticle[] { article };
           }
   
  -        if ( group == null )
  +        if ( group == null ) {
               return null;
  -        if ( range == null )
  -            range = ""+group.getCurrentArticleNumber();
  +        }
  +        if ( range == null ) {
  +            range = "" + group.getCurrentArticleNumber();
  +        }
   
           int start = -1;
           int end = -1;
  @@ -680,10 +997,11 @@
               start = end = Integer.parseInt(range);
           } else {
               start = Integer.parseInt(range.substring(0,idx));
  -            if ( idx+1 == range.length() )
  +            if ( idx+1 == range.length() ) {
                   end = group.getLastArticleNumber();
  -            else
  +            } else {
                   end = Integer.parseInt(range.substring(idx+1));
  +            }
           }
           List list = new ArrayList();
           for ( int i = start ; i <= end ; i++ ) {
  @@ -695,27 +1013,40 @@
           return (NNTPArticle[])list.toArray(new NNTPArticle[0]);
       }
   
  -    private void doXOVER(String range) {
  -        doOVER(range);
  +    /**
  +     * Return whether the user associated with the connection (possibly no
  +     * user) is authorized to execute the command.
  +     *
  +     * @param the command being tested
  +     * @return whether the command is authorized
  +     */
  +    private boolean isAuthorized(String command) {
  +        boolean allowed = isAuthenticated();
  +        if (allowed) {
  +            return true;
  +        }
  +        // some commands are authorized, even if the user is not authenticated
  +        allowed = allowed || command.equals("AUTHINFO");
  +        allowed = allowed || command.equals("MODE");
  +        allowed = allowed || command.equals("QUIT");
  +        return allowed;
       }
   
  -    private void doOVER(String range) {
  -        // 9.5.2.2.1
  -        if ( group == null ) {
  -            writer.println("412 No newsgroup selected");
  -            return;
  -        }
  -        NNTPArticle[] article = getRange(range);
  -        ArticleWriter articleWriter = ArticleWriter.Factory.OVER(writer);
  -        if ( article.length == 0 ) {
  -            writer.println("420 No article(s) selected");
  -        }
  -        else {
  -            writer.println("224 Overview information follows");
  -            for ( int i = 0 ; i < article.length ; i++ ) {
  -                articleWriter.write(article[i]);
  +    /**
  +     * Return whether the connection has been authenticated.
  +     *
  +     * @return whether the connection has been authenticated.
  +     */
  +    private boolean isAuthenticated() {
  +        if ( authRequired ) {
  +            if  ((user != null) && (password != null) && (userRepository != null)) {
  +                return userRepository.test(user,password);
  +            } else {
  +                return false;
               }
  -            writer.println(".");
  +        } else {
  +            return true;
           }
       }
  +
   }
  
  
  
  1.11      +21 -12    jakarta-james/src/java/org/apache/james/nntpserver/NNTPServer.java
  
  Index: NNTPServer.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/NNTPServer.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- NNTPServer.java	24 Sep 2002 22:23:04 -0000	1.10
  +++ NNTPServer.java	2 Oct 2002 09:53:38 -0000	1.11
  @@ -30,11 +30,9 @@
       protected ConnectionHandlerFactory createFactory() {
           return new DefaultHandlerFactory(NNTPHandler.class);
       }
  +
       /**
  -     * Pass the <code>Configuration</code> to the instance.
  -     *
  -     * @param configuration the class configurations.
  -     * @throws ConfigurationException if an error occurs
  +     * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
        */
       public void configure(final Configuration configuration) throws ConfigurationException {
           enabled = configuration.getAttributeAsBoolean("enabled", true);
  @@ -47,35 +45,46 @@
               } catch (final UnknownHostException unhe) {
                   throw new ConfigurationException("Malformed bind parameter", unhe);
               }
  -            final boolean useTLS = configuration.getChild("userTLS").getValueAsBoolean(false);
  +            final boolean useTLS = configuration.getChild("useTLS").getValueAsBoolean(false);
               if (useTLS) {
                   m_serverSocketType = "ssl";
               }
               super.configure(configuration.getChild("handler"));
  +            boolean authRequired =
  +                configuration.getChild("handler").getChild("authRequired").getValueAsBoolean(false);
  +            if (getLogger().isDebugEnabled()) {
  +                if (authRequired) {
  +                    getLogger().debug("NNTP Server requires authentication.");
  +                } else {
  +                    getLogger().debug("NNTP Server doesn't require authentication.");
  +                }
  +            }
               if (getLogger().isInfoEnabled()) {
  -                getLogger().info("configured NNTPServer to run at : " + m_port);
  +                getLogger().info("Configured NNTPServer to run at : " + m_port);
               }
           }
       }
  +
       /**
  -     * Initialize the component. Initialization includes
  -     * allocating any resources required throughout the
  -     * components lifecycle.
  -     *
  -     * @throws Exception if an error occurs
  +     * @see org.apache.avalon.framework.activity.Initializable#initialize()
        */
       public void initialize() throws Exception {
           if (enabled) {
               super.initialize();
               System.out.println("NNTP Server Started " + m_connectionName);
  +            getLogger().info("NNTP Server Started " + m_connectionName);
           } else {
               getLogger().info("NNTP Server Disabled");
               System.out.println("NNTP Server Disabled");
           }
       }
  +
  +    /**
  +     * @see org.apache.avalon.framework.activity.Disposable#dispose()
  +     */
       public void dispose() {
           if (enabled) {
               super.dispose();
           }
  -    }    
  +    }
   }
  
  
  
  1.4       +2 -2      jakarta-james/src/java/org/apache/james/nntpserver/NNTPServer.xinfo
  
  Index: NNTPServer.xinfo
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/NNTPServer.xinfo,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- NNTPServer.xinfo	18 Nov 2001 01:00:37 -0000	1.3
  +++ NNTPServer.xinfo	2 Oct 2002 09:53:38 -0000	1.4
  @@ -30,8 +30,8 @@
         <service name="org.apache.james.nntpserver.repository.NNTPRepository" version="1.0"/>
       </dependency> 
       <dependency>
  -      <role>org.apache.james.nntpserver.AuthService</role>
  -      <service name="org.apache.james.nntpserver.AuthService" version="1.0"/>
  +      <role>org.apache.james.services.UsersStore</role>
  +      <service name="org.apache.james.services.UsersStore" version="1.0"/>
       </dependency> 
     </dependencies>
   
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/package.html
  
  Index: package.html
  ===================================================================
  <body>
  <p>Provides classes implementing NNTP functionality.</p>
  </body>
  
  
  
  1.8       +59 -4     jakarta-james/src/java/org/apache/james/nntpserver/repository/ArticleIDRepository.java
  
  Index: ArticleIDRepository.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/ArticleIDRepository.java,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- ArticleIDRepository.java	18 Aug 2002 07:30:17 -0000	1.7
  +++ ArticleIDRepository.java	2 Oct 2002 09:53:38 -0000	1.8
  @@ -31,18 +31,41 @@
    * @author Harmeet Bedi <ha...@kodemuse.com>
    */
   public class ArticleIDRepository {
  +
  +    /**
  +     * The root of the repository in the file system
  +     */
       private final File root;
  +
  +    /**
  +     * The suffix appended to the articleIDs
  +     */
       private final String articleIDDomainSuffix;
  +
  +    /**
  +     * A counter of the number of article IDs
  +     *
  +     * TODO: Potentially serious threading problem here
  +     */
       private int counter = 0;
  +
       ArticleIDRepository(File root,String articleIDDomainSuffix) {
           this.root = root;
           this.articleIDDomainSuffix = articleIDDomainSuffix;
       }
   
  +    /**
  +     * Returns the root of the repository.
  +     *
  +     * @return the root of the repository
  +     */
       public File getPath() {
           return root;
       }
   
  +    /**
  +     * Generate a new article ID for use in the repository.
  +     */
       String generateArticleID() {
           int idx = Math.abs(counter++);
           StringBuffer idBuffer =
  @@ -59,10 +82,15 @@
           return idBuffer.toString();
       }
   
  -    /** @param prop contains the newsgroup name and article number */
  +    /**
  +     * Add the article information to the repository.
  +     *
  +     * @param prop contains the newsgroup name and article number.
  +     */
       void addArticle(String articleID,Properties prop) throws IOException {
  -        if ( articleID == null )
  +        if ( articleID == null ) {
               articleID = generateArticleID();
  +        }
           FileOutputStream fout = null;
           try {
               fout = new FileOutputStream(getFileFromID(articleID));
  @@ -74,6 +102,14 @@
           }
       }
   
  +    /**
  +     * Returns the file in the repository corresponding to the specified
  +     * article ID.
  +     *
  +     * @param articleID the article ID
  +     *
  +     * @return the repository file
  +     */
       File getFileFromID(String articleID) {
           String b64Id;
           try {
  @@ -84,14 +120,32 @@
           return new File(root, b64Id);
       }
   
  +    /**
  +     * Returns whether the article ID is in the repository
  +     *
  +     * @param articleID the article ID
  +     *
  +     * @return whether the article ID is in the repository
  +     */
       boolean isExists(String articleID) {
           return ( articleID == null ) ? false : getFileFromID(articleID).exists();
       }
   
  +    /**
  +     * Get the article from the NNTP respository with the specified id.
  +     *
  +     * @param repo the NNTP repository where the article is stored
  +     * @param id the id of the article to retrieve
  +     *
  +     * @return the article
  +     *
  +     * @throws IOException if the ID information cannot be loaded
  +     */
       NNTPArticle getArticle(NNTPRepository repo,String id) throws IOException {
           File f = getFileFromID(id);
  -        if ( f.exists() == false )
  +        if ( f.exists() == false ) {
               return null;
  +        }
           FileInputStream fin = null;
           Properties prop = new Properties();
           try {
  @@ -108,8 +162,9 @@
               String groupName = (String)enum.nextElement();
               int number = Integer.parseInt(prop.getProperty(groupName));
               NNTPGroup group = repo.getGroup(groupName);
  -            if ( group != null )
  +            if ( group != null ) {
                   article = group.getArticle(number);
  +            }
           }
           return article;
       }
  
  
  
  1.3       +50 -1     jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPArticle.java
  
  Index: NNTPArticle.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPArticle.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- NNTPArticle.java	10 Nov 2001 23:10:59 -0000	1.2
  +++ NNTPArticle.java	2 Oct 2002 09:53:38 -0000	1.3
  @@ -15,12 +15,61 @@
    * @author Harmeet Bedi <ha...@kodemuse.com>
    */
   public interface NNTPArticle {
  +
  +    /**
  +     * Gets the group containing this article.
  +     *
  +     * @return the group
  +     */
       NNTPGroup getGroup();
  +
  +    /**
  +     * Gets the article number for this article.
  +     *
  +     * @return the article number
  +     */
       int getArticleNumber();
  +
  +    /**
  +     * Gets the unique message id for this article.
  +     *
  +     * @return the message id
  +     */
       String getUniqueID();
  +
  +    /**
  +     * Writes the whole article to a writer.
  +     *
  +     * @param wrt the PrintWriter to which the article is written.
  +     */
       void writeArticle(PrintWriter wrt);
  +
  +    /**
  +     * Writes the article headers to a writer.
  +     *
  +     * @param wrt the PrintWriter to which the article is written.
  +     */
       void writeHead(PrintWriter wrt);
  +
  +    /**
  +     * Writes the article body to a writer.
  +     *
  +     * @param wrt the PrintWriter to which the article is written.
  +     */
       void writeBody(PrintWriter wrt);
  +
  +    /**
  +     * Writes the article overview to a writer.
  +     *
  +     * @param wrt the PrintWriter to which the article is written.
  +     */
       void writeOverview(PrintWriter wrt);
  -    String getHeader(String header);
  +
  +    /**
  +     * Gets the header with the specified headerName.  Returns null
  +     * if the header doesn't exist.
  +     *
  +     * @param headerName the name of the header being retrieved.
  +     */
  +    String getHeader(String headerName);
   }
  
  
  
  1.7       +75 -26    jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPArticleImpl.java
  
  Index: NNTPArticleImpl.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPArticleImpl.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- NNTPArticleImpl.java	7 Sep 2002 06:16:14 -0000	1.6
  +++ NNTPArticleImpl.java	2 Oct 2002 09:53:38 -0000	1.7
  @@ -18,29 +18,55 @@
    * @author Harmeet Bedi <ha...@kodemuse.com>
    */
   class NNTPArticleImpl implements NNTPArticle {
  -    private final File f;
  +
  +    /**
  +     * The file that stores the article data
  +     */
  +    private final File articleFile;
  +
  +    /**
  +     * The sole constructor for this class.
  +     *
  +     * @param f the file that stores the article data
  +     */
       NNTPArticleImpl(File f) {
  -        this.f = f;
  +        articleFile = f;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpsever.repository.NNTPArticle#getGroup()
  +     */
       public NNTPGroup getGroup() {
  -        return new NNTPGroupImpl(f.getParentFile());
  +        return new NNTPGroupImpl(articleFile.getParentFile());
       }
  +
  +    /**
  +     * @see org.apache.james.nntpsever.repository.NNTPArticle#getArticleNumber()
  +     */
       public int getArticleNumber() {
  -        return Integer.parseInt(f.getName());
  +        return Integer.parseInt(articleFile.getName());
       }
  +
  +    /**
  +     * @see org.apache.james.nntpsever.repository.NNTPArticle#getUniqueID()
  +     */
       public String getUniqueID() {
           try {
  -            FileInputStream fin = new FileInputStream(f);
  +            FileInputStream fin = new FileInputStream(articleFile);
               InternetHeaders headers = new InternetHeaders(fin);
               String[] idheader = headers.getHeader("Message-Id");
               fin.close();
               return ( idheader.length > 0 ) ? idheader[0] : null;
           } catch(Exception ex) { throw new NNTPException(ex); }
       }
  +
  +    /**
  +     * @see org.apache.james.nntpsever.repository.NNTPArticle#writeArticle(PrintWriter)
  +     */
       public void writeArticle(PrintWriter prt) {
           BufferedReader reader = null;
           try {
  -            reader = new BufferedReader(new FileReader(f));
  +            reader = new BufferedReader(new FileReader(articleFile));
               String line = null;
               while ( ( line = reader.readLine() ) != null ) {
                   prt.println(line);
  @@ -57,9 +83,13 @@
               }
           }
       }
  +
  +    /**
  +     * @see org.apache.james.nntpsever.repository.NNTPArticle#writeHead(PrintWriter)
  +     */
       public void writeHead(PrintWriter prt) {
           try {
  -            BufferedReader reader = new BufferedReader(new FileReader(f));
  +            BufferedReader reader = new BufferedReader(new FileReader(articleFile));
               String line = null;
               while ( ( line = reader.readLine() ) != null ) {
                   if ( line.trim().length() == 0 )
  @@ -69,9 +99,13 @@
               reader.close();
           } catch(IOException ex) { throw new NNTPException(ex); }
       }
  +
  +    /**
  +     * @see org.apache.james.nntpsever.repository.NNTPArticle#writeBody(PrintWriter)
  +     */
       public void writeBody(PrintWriter prt) {
           try {
  -            BufferedReader reader = new BufferedReader(new FileReader(f));
  +            BufferedReader reader = new BufferedReader(new FileReader(articleFile));
               String line = null;
               boolean startWriting = false;
               while ( ( line = reader.readLine() ) != null ) {
  @@ -84,23 +118,12 @@
           } catch(IOException ex) { throw new NNTPException(ex); }
       }
   
  -    // rfc2980: 2.8 XOVER
  -    // requires newline and tab to be converted to space
  -    private String cleanHeader(String field) {
  -        if ( field == null )
  -            field = "";
  -        StringBuffer sb = new StringBuffer(field);
  -        for( int i=0 ; i<sb.length() ; i++ ) {
  -            char c = sb.charAt(i);
  -            if( (c=='\n') || (c=='\t') ) 
  -                sb.setCharAt(i, ' ');
  -        }
  -        return sb.toString();
  -    }
  -    
  +    /**
  +     * @see org.apache.james.nntpsever.repository.NNTPArticle#writeOverview(PrintWriter)
  +     */
       public void writeOverview(PrintWriter prt) {
           try {
  -            FileInputStream fin = new FileInputStream(f);
  +            FileInputStream fin = new FileInputStream(articleFile);
               InternetHeaders hdr = new InternetHeaders(fin);
               fin.close();
               int articleNumber = getArticleNumber();
  @@ -109,7 +132,7 @@
               String date = hdr.getHeader("Date",null);
               String msgId = hdr.getHeader("Message-Id",null);
               String references = hdr.getHeader("References",null);
  -            long byteCount = f.length();
  +            long byteCount = articleFile.length();
               long lineCount = -1;
               StringBuffer line=new StringBuffer(128)
                   .append(articleNumber + "\t")
  @@ -123,12 +146,38 @@
               prt.println(line.toString());
           } catch(Exception ex) { throw new NNTPException(ex); }
       }
  +
  +    /**
  +     * @see org.apache.james.nntpsever.repository.NNTPArticle#getHeader(String)
  +     */
       public String getHeader(String header) {
           try {
  -            FileInputStream fin = new FileInputStream(f);
  +            FileInputStream fin = new FileInputStream(articleFile);
               InternetHeaders hdr = new InternetHeaders(fin);
               fin.close();
               return hdr.getHeader(header,null);
  -        } catch(Exception ex) { throw new NNTPException(ex); }
  +        } catch(Exception ex) {
  +            throw new NNTPException(ex);
  +        }
  +    }
  +
  +    /**
  +     * Strips out newlines and tabs, converting them to spaces.
  +     * rfc2980: 2.8 XOVER requires newline and tab to be converted to spaces
  +     *
  +     * @param the input String
  +     *
  +     * @return the cleaned string
  +     */
  +    private String cleanHeader(String field) {
  +        if ( field == null )
  +            field = "";
  +        StringBuffer sb = new StringBuffer(field);
  +        for( int i=0 ; i<sb.length() ; i++ ) {
  +            char c = sb.charAt(i);
  +            if( (c=='\n') || (c=='\t') ) 
  +                sb.setCharAt(i, ' ');
  +        }
  +        return sb.toString();
       }
   }
  
  
  
  1.3       +80 -3     jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPGroup.java
  
  Index: NNTPGroup.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPGroup.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- NNTPGroup.java	10 Nov 2001 23:10:59 -0000	1.2
  +++ NNTPGroup.java	2 Oct 2002 09:53:38 -0000	1.3
  @@ -16,24 +16,101 @@
    * @author Harmeet Bedi <ha...@kodemuse.com>
    */
   public interface NNTPGroup {
  +
  +    /**
  +     * Gets the name of the newsgroup
  +     *
  +     * @return the newsgroup name
  +     */
       String getName();
  +
  +    /**
  +     * Gets the description of the newsgroup
  +     *
  +     * @return the newsgroup description
  +     */
       String getDescription();
  +
  +    /**
  +     * Returns whether posting is allowed to this newsgroup
  +     *
  +     * @return whether posting is allowed to this newsgroup
  +     */
       boolean isPostAllowed();
   
  -    /** the current article pointer. 
  -     * @return <0 indicates invalid/unknown value
  +    /**
  +     * Returns the current article number.
  +     *
  +     * @return A return value of less than zero 
  +     *         indicates invalid/unknown value
        */
       int getCurrentArticleNumber();
  +
  +    /**
  +     * Sets the current article number.
  +     *
  +     * @param the new current article number
  +     */
       void setCurrentArticleNumber(int articleNumber);
   
  +    /**
  +     * Gets the number of articles in the group.
  +     *
  +     * @return the number of articles in the group.
  +     */
       int getNumberOfArticles();
  +
  +    /**
  +     * Gets the first article number in the group.
  +     *
  +     * @return the first article number in the group.
  +     */
       int getFirstArticleNumber();
  +
  +    /**
  +     * Gets the last article number in the group.
  +     *
  +     * @return the last article number in the group.
  +     */
       int getLastArticleNumber();
   
  +    /**
  +     * Gets the current article.
  +     *
  +     * @return the current article
  +     */
       NNTPArticle getCurrentArticle();
  +
  +    /**
  +     * Gets the article with the specified article number.
  +     *
  +     * @param number the article number
  +     *
  +     * @return the article
  +     */
       NNTPArticle getArticle(int number);
  -    //NNTPArticle getArticleFromID(String id);
  +
  +    /**
  +     * Retrieves an iterator of articles in this newsgroup that were
  +     * posted on or after the specified date.
  +     *
  +     * @param dt the Date that acts as a lower bound for the list of
  +     *           articles
  +     *
  +     * @return the article iterator
  +     */
       Iterator getArticlesSince(Date dt);
  +
  +    /**
  +     * Retrieves an iterator of all articles in this newsgroup
  +     *
  +     * @return the article iterator
  +     */
       Iterator getArticles();
  +
  +    /**
  +     * TODO: I don't understand the purpose of this method.  It seems to 
  +     *       be an implementation hack.  
  +     */
       Object getPath();
   }
  
  
  
  1.6       +114 -25   jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPGroupImpl.java
  
  Index: NNTPGroupImpl.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPGroupImpl.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- NNTPGroupImpl.java	7 Aug 2002 23:38:48 -0000	1.5
  +++ NNTPGroupImpl.java	2 Oct 2002 09:53:38 -0000	1.6
  @@ -25,56 +25,122 @@
    * @author Harmeet Bedi <ha...@kodemuse.com>
    */
   class NNTPGroupImpl implements NNTPGroup {
  +
  +    /**
  +     * The directory to which this group maps.
  +     */
       private final File root;
  +
  +    /**
  +     * The article number of the current article
  +     */
       private int currentArticle = -1;
  +
  +    /**
  +     * The last article number in the group
  +     */
       private int lastArticle;
  +
  +    /**
  +     * The last article number in the group
  +     */
       private int firstArticle;
  +
  +    /**
  +     * The number of articles in the group.
  +     */
       private int numOfArticles;
  -    // an instance may collect range info once. This involves disk I/O
  +
  +    /**
  +     * Whether the first, last, and total number of articles in the
  +     * group have been read from disk.
  +     * An instance may collect range info once. This involves disk I/O
  +     */
       private boolean articleRangeInfoCollected = false;
  +
  +    /**
  +     * The sole constructor for this particular NNTPGroupImpl.
  +     *
  +     * @param root the directory containing the articles
  +     */
       NNTPGroupImpl(File root) {
           this.root = root;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getName()
  +     */
       public String getName() {
           return root.getName();
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getDescription()
  +     */
       public String getDescription() {
           return getName();
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#isPostAllowed()
  +     */
       public boolean isPostAllowed() {
           return true;
       }
  +
  +    /**
  +     * Generates the first, last, and number of articles from the
  +     * information in the group directory.
  +     */
       private void collectArticleRangeInfo() {
  -        if ( articleRangeInfoCollected )
  +        if ( articleRangeInfoCollected ) {
               return;
  +        }
           String[] list = root.list();
  -        //new InvertedFileFilter(new ExtensionFileFilter(".id")));
           int first = -1;
           int last = -1;
           for ( int i = 0 ; i < list.length ; i++ ) {
               int num = Integer.parseInt(list[i]);
  -            if ( first == -1 || num < first )
  +            if ( first == -1 || num < first ) {
                   first = num;
  -            if ( num > last )
  +            }
  +            if ( num > last ) {
                   last = num;
  +            }
           }
           numOfArticles = list.length;
           firstArticle = Math.max(first,0);
           lastArticle = Math.max(last,0);
           articleRangeInfoCollected = true;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getNumberOfArticles()
  +     */
       public int getNumberOfArticles() {
           collectArticleRangeInfo();
           return numOfArticles;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getFirstArticleNumber()
  +     */
       public int getFirstArticleNumber() {
           collectArticleRangeInfo();
           return firstArticle;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getLastArticleNumber()
  +     */
       public int getLastArticleNumber() {
           collectArticleRangeInfo();
           return lastArticle;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getCurrentArticleNumber()
  +     */
       public int getCurrentArticleNumber() {
           collectArticleRangeInfo();
           // this is not as per RFC, but this is not significant.
  @@ -82,17 +148,60 @@
               currentArticle = firstArticle;
           return currentArticle;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#setCurrentArticleNumber(int)
  +     */
       public void setCurrentArticleNumber(int articleNumber) {
           this.currentArticle = articleNumber;
       }
   
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getCurrentArticle()
  +     */
       public NNTPArticle getCurrentArticle() {
           return getArticle(getCurrentArticleNumber());
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getArticle(int)
  +     */
       public NNTPArticle getArticle(int number) {
           File f = new File(root,number + "");
           return f.exists() ? new NNTPArticleImpl(f) : null;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getArticlesSince(Date)
  +     */
  +    public Iterator getArticlesSince(Date dt) {
  +        File[] f = root.listFiles(new AndFileFilter
  +            (new DateSinceFileFilter(dt.getTime()),
  +             new InvertedFileFilter(new ExtensionFileFilter(".id"))));
  +        List list = new ArrayList();
  +        for ( int i = 0 ; i < f.length ; i++ )
  +            list.add(new NNTPArticleImpl(f[i]));
  +        return list.iterator();
  +    }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getArticles()
  +     */
  +    public Iterator getArticles() {
  +        File[] f = root.listFiles();
  +        List list = new ArrayList();
  +        for ( int i = 0 ; i < f.length ; i++ )
  +            list.add(new NNTPArticleImpl(f[i]));
  +        return list.iterator();
  +    }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.NNTPGroup#getPath()
  +     */
  +    public Object getPath() {
  +        return root;
  +    }
  +
   //     public NNTPArticle getArticleFromID(String id) {
   //         if ( id == null )
   //             return null;
  @@ -115,25 +224,5 @@
   //             throw new NNTPException("could not fectch article: "+id,ioe);
   //         }
   //     }
  -    public Iterator getArticlesSince(Date dt) {
  -        File[] f = root.listFiles(new AndFileFilter
  -            (new DateSinceFileFilter(dt.getTime()),
  -             new InvertedFileFilter(new ExtensionFileFilter(".id"))));
  -        List list = new ArrayList();
  -        for ( int i = 0 ; i < f.length ; i++ )
  -            list.add(new NNTPArticleImpl(f[i]));
  -        return list.iterator();
  -    }
   
  -    public Iterator getArticles() {
  -        File[] f = root.listFiles();
  -        //(new InvertedFileFilter(new ExtensionFileFilter(".id")));
  -        List list = new ArrayList();
  -        for ( int i = 0 ; i < f.length ; i++ )
  -            list.add(new NNTPArticleImpl(f[i]));
  -        return list.iterator();
  -    }
  -    public Object getPath() {
  -        return root;
  -    }
   }
  
  
  
  1.4       +3 -1      jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPLineReader.java
  
  Index: NNTPLineReader.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPLineReader.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- NNTPLineReader.java	18 Jan 2002 02:48:36 -0000	1.3
  +++ NNTPLineReader.java	2 Oct 2002 09:53:38 -0000	1.4
  @@ -15,8 +15,10 @@
    * @author Harmeet Bedi <ha...@kodemuse.com>
    */
   public interface NNTPLineReader {
  +
       /** 
  -     * reads a line of data.
  +     * Reads a line of data.
  +     *
        * @return null indicates end of data
        */
       String readLine();
  
  
  
  1.5       +12 -2     jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPLineReaderImpl.java
  
  Index: NNTPLineReaderImpl.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPLineReaderImpl.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- NNTPLineReaderImpl.java	3 Jun 2002 16:07:06 -0000	1.4
  +++ NNTPLineReaderImpl.java	2 Oct 2002 09:53:38 -0000	1.5
  @@ -13,17 +13,27 @@
   import java.io.IOException;
   
   /**
  - * reads and translates client data. After this translation, 
  + * Reads and translates client data. After this translation, 
    * the data can be streamed into server repository.
    * Handles Dot Stuffing.
    *
    * @author Harmeet Bedi <ha...@kodemuse.com>
    */
   public class NNTPLineReaderImpl implements NNTPLineReader {
  +
  +    /**
  +     * The BufferedReader that provides the client data being
  +     * read in.
  +     */
       private final BufferedReader reader;
  +
       public NNTPLineReaderImpl(BufferedReader reader) {
           this.reader = reader;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.repository.NNTPLineReader#readLine
  +     */
       public String readLine() {
           try {
               String line = reader.readLine();
  @@ -34,7 +44,7 @@
                    line = line.substring(1,line.length());
               return line;
           } catch(IOException ioe) {
  -            throw new NNTPException("could not create article",ioe);
  +            throw new NNTPException("Could not create article",ioe);
           }
       }
   }
  
  
  
  1.4       +52 -0     jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPRepository.java
  
  Index: NNTPRepository.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPRepository.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- NNTPRepository.java	18 Jan 2002 02:48:36 -0000	1.3
  +++ NNTPRepository.java	2 Oct 2002 09:53:38 -0000	1.4
  @@ -16,11 +16,63 @@
    * @author Harmeet Bedi <ha...@kodemuse.com>
    */
   public interface NNTPRepository {
  +
  +    /**
  +     * Gets the group with the specified name from within the repository.
  +     *
  +     * @param groupName the name of the group to retrieve
  +     *
  +     * @return the group
  +     */
       NNTPGroup getGroup(String groupName);
  +
  +    /**
  +     * Gets the article with the specified id from within the repository.
  +     *
  +     * @param id the id of the article to retrieve
  +     *
  +     * @return the article
  +     */
       NNTPArticle getArticleFromID(String id);
  +
  +    /**
  +     * Creates an article in the repository from the data in the reader.
  +     *
  +     * @param reader the reader that serves as a source for the article data
  +     */
       void createArticle(NNTPLineReader reader);
  +
  +    /**
  +     * Gets all groups that match the wildmat string
  +     *
  +     * @param wildmat the wildmat parameter
  +     *
  +     * @return an iterator containing the groups retrieved
  +     */
       Iterator getMatchedGroups(String wildmat);
  +
  +    /**
  +     * Gets all groups added since the specified date
  +     *
  +     * @param dt the Date that serves as a lower bound
  +     *
  +     * @return an iterator containing the groups retrieved
  +     */
       Iterator getGroupsSince(Date dt);
  +
  +    /**
  +     * Gets all articles posted since the specified date
  +     *
  +     * @param dt the Date that serves as a lower bound
  +     *
  +     * @return an iterator containing the articles retrieved
  +     */
       Iterator getArticlesSince(Date dt);
  +
  +    /**
  +     * Returns whether this repository is read only.
  +     *
  +     * @return whether this repository is read only
  +     */
       boolean isReadOnly();
   }
  
  
  
  1.9       +94 -31    jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPRepositoryImpl.java
  
  Index: NNTPRepositoryImpl.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPRepositoryImpl.java,v
  retrieving revision 1.8
  retrieving revision 1.9
  diff -u -r1.8 -r1.9
  --- NNTPRepositoryImpl.java	18 Aug 2002 07:30:17 -0000	1.8
  +++ NNTPRepositoryImpl.java	2 Oct 2002 09:53:38 -0000	1.9
  @@ -38,24 +38,43 @@
   public class NNTPRepositoryImpl extends AbstractLogEnabled 
       implements NNTPRepository, Contextualizable, Configurable, Initializable
   {
  +    /**
  +     * The context employed by this repository
  +     */
       private Context context;
  +
  +    /**
  +     * Whether the repository is read only
  +     */
       private boolean readOnly;
  -    // the groups are located under this path.
  +
  +    /**
  +     * The groups are located under this path.
  +     */
       private File rootPath;
  -    // articles are temprorily written here and then sent to the spooler.
  +
  +    /**
  +     * Articles are temporarily written here and then sent to the spooler.
  +     */
       private File tempPath;
  +
  +    /**
  +     * The spooler for this repository.
  +     */
       private NNTPSpooler spool;
  +
  +    /**
  +     * The article ID repository associated with this NNTP repository.
  +     */
       private ArticleIDRepository articleIDRepo;
  -    private String[] addGroups = null;
   
  +    /**
  +     * The list of groups stored in this repository
  +     */
  +    private String[] addGroups = null;
   
       /**
  -     * Pass the Context to the component.
  -     * This method is called after the setLogger()
  -     * method and before any other method.
  -     *
  -     * @param context the context
  -     * @throws ContextException if context is invalid
  +     * @see org.apache.avalon.framework.context.Contextualizable#contextualize(Context)
        */
       public void contextualize(Context context)
               throws ContextException {
  @@ -63,10 +82,7 @@
       }
   
       /**
  -     * Pass the <code>Configuration</code> to the instance.
  -     *
  -     * @param configuration the class configurations.
  -     * @throws ConfigurationException if an error occurs
  +     * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
        */
       public void configure( Configuration configuration ) throws ConfigurationException {
           //System.out.println(getClass().getName() + ": configure");
  @@ -83,9 +99,11 @@
                "org.apache.james.nntpserver.repository.NNTPSpooler");
           spool.setRepository(this);
           spool.setArticleIDRepository(articleIDRepo);
  -        getLogger().debug("repository:readOnly="+readOnly);
  -        getLogger().debug("repository:rootPath="+rootPath.getAbsolutePath());
  -        getLogger().debug("repository:tempPath="+tempPath.getAbsolutePath());
  +        if (getLogger().isDebugEnabled()) {
  +            getLogger().debug("repository:readOnly=" + readOnly);
  +            getLogger().debug("repository:rootPath=" + rootPath.getAbsolutePath());
  +            getLogger().debug("repository:tempPath=" + tempPath.getAbsolutePath());
  +        }
           configuration = configuration.getChild("newsgroups");
           List addGroupsList = new ArrayList();
           if ( configuration != null ) {
  @@ -99,37 +117,50 @@
       }
   
       /**
  -     * Initialize the component. Initialization includes
  -     * allocating any resources required throughout the
  -     * components lifecycle.
  -     *
  -     * @throws Exception if an error occurs
  +     * @see org.apache.avalon.framework.activity.Initializable#initialize()
        */
       public void initialize() throws Exception {
           //System.out.println(getClass().getName() + ": init");
  -        if ( rootPath.exists() == false )
  +        if ( rootPath.exists() == false ) {
               rootPath.mkdirs();
  +        }
           for ( int i = 0 ; i < addGroups.length ; i++ ) {
               File groupF = new File(rootPath,addGroups[i]);
  -            if ( groupF.exists() == false )
  +            if ( groupF.exists() == false ) {
                   groupF.mkdirs();
  +            }
           }
  -        if ( tempPath.exists() == false )
  +        if ( tempPath.exists() == false ) {
               tempPath.mkdirs();
  +        }
           File articleIDPath = articleIDRepo.getPath();
  -        if ( articleIDPath.exists() == false )
  +        if ( articleIDPath.exists() == false ) {
               articleIDPath.mkdirs();
  -        if ( spool instanceof Initializable )
  +        }
  +        if ( spool instanceof Initializable ) {
               ((Initializable)spool).initialize();
  +        }
           getLogger().debug("repository initialization done");
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.repository.NNTPRepository#isReadOnly()
  +     */
       public boolean isReadOnly() {
           return readOnly;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.repository.NNTPRepository#getGroup(String)
  +     */
       public NNTPGroup getGroup(String groupName) {
           File f = new File(rootPath,groupName);
           return ( f.exists() && f.isDirectory() ) ? new NNTPGroupImpl(f) : null;
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.repository.NNTPRepository#getArticleFromID(String)
  +     */
       public NNTPArticle getArticleFromID(String id) {
           try {
               return articleIDRepo.getArticle(this,id);
  @@ -143,6 +174,10 @@
   //         NNTPGroup group = getGroup(groupname);
   //         return ( group == null ) ? null : group.getArticleFromID(name);
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.repository.NNTPRepository#createArticle(NNTPLineReader)
  +     */
       public void createArticle(NNTPLineReader reader) {
           StringBuffer fileBuffer =
               new StringBuffer(32)
  @@ -154,25 +189,44 @@
               FileOutputStream fout = new FileOutputStream(f);
               PrintStream prt = new PrintStream(fout,true);
               String line;
  -            while ( ( line = reader.readLine() ) != null )
  +            while ( ( line = reader.readLine() ) != null ) {
                   prt.println(line);
  +            }
               prt.close();
               f.renameTo(new File(spool.getSpoolPath(),f.getName()));
           } catch(IOException ex) {
               throw new NNTPException("create article failed",ex);
           }
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.repository.NNTPRepository#getMatchedGroups(String)
  +     */
       public Iterator getMatchedGroups(String wildmat) {
           File[] f = rootPath.listFiles(new AndFileFilter
               (new DirectoryFileFilter(),new GlobFilenameFilter(wildmat)));
           return getGroups(f);
       }
  +
  +    /**
  +     * Gets an iterator of all news groups represented by the files
  +     * in the parameter array.
  +     *
  +     * @param f the array of files that correspond to news groups
  +     *
  +     * @return an iterator of news groups
  +     */
       private Iterator getGroups(File[] f) {
           List list = new ArrayList();
  -        for ( int i = 0 ; i < f.length ; i++ )
  +        for ( int i = 0 ; i < f.length ; i++ ) {
               list.add(new NNTPGroupImpl(f[i]));
  +        }
           return list.iterator();
       }
  +
  +    /**
  +     * @see org.apache.james.nntpserver.repository.NNTPRepository#getGroupsSince(Date)
  +     */
       public Iterator getGroupsSince(Date dt) {
           File[] f = rootPath.listFiles(new AndFileFilter
               (new DirectoryFileFilter(),new DateSinceFileFilter(dt.getTime())));
  @@ -182,29 +236,38 @@
       // gets the list of groups.
       // creates iterator that concatenates the article iterators in the list of groups.
       // there is at most one article iterator reference for all the groups
  +
  +    /**
  +     * @see org.apache.james.nntpserver.repository.NNTPRepository#getArticlesSince(Date)
  +     */
       public Iterator getArticlesSince(final Date dt) {
           final Iterator giter = getGroupsSince(dt);
           return new Iterator() {
  +
                   private Iterator iter = null;
  +
                   public boolean hasNext() {
                       if ( iter == null ) {
                           if ( giter.hasNext() ) {
                               NNTPGroup group = (NNTPGroup)giter.next();
                               iter = group.getArticlesSince(dt);
                           }
  -                        else
  +                        else {
                               return false;
  +                        }
                       }
  -                    if ( iter.hasNext() )
  +                    if ( iter.hasNext() ) {
                           return true;
  -                    else {
  +                    } else {
                           iter = null;
                           return hasNext();
                       }
                   }
  +
                   public Object next() {
                       return iter.next();
                   }
  +
                   public void remove() {
                       throw new UnsupportedOperationException("remove not supported");
                   }
  
  
  
  1.8       +112 -33   jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPSpooler.java
  
  Index: NNTPSpooler.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPSpooler.java,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- NNTPSpooler.java	18 Aug 2002 07:30:17 -0000	1.7
  +++ NNTPSpooler.java	2 Oct 2002 09:53:38 -0000	1.8
  @@ -34,43 +34,74 @@
   class NNTPSpooler extends AbstractLogEnabled 
           implements Contextualizable, Configurable, Initializable {
   
  +    /**
  +     * The spooler context
  +     */
       private Context context;
  -    private Worker[] worker;
  +
  +    /**
  +     * The array of spooler runnables, each associated with a Worker thread
  +     */
  +    private SpoolerRunnable[] worker;
  +
  +    /**
  +     * The directory containing entries to be spooled.
  +     */
       private File spoolPath;
   
  +    /**
  +     * @see org.apache.avalon.framework.context.Contextualizable#contextualize(Context)
  +     */
       public void contextualize(final Context context) 
               throws ContextException {
           this.context = context;
       }
   
       /**
  -     * Pass the <code>Configuration</code> to the instance.
  -     *
  -     * @param configuration the class configurations.
  -     * @throws ConfigurationException if an error occurs
  +     * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
        */
       public void configure( Configuration configuration ) throws ConfigurationException {
  -        //System.out.println(getClass().getName()+": configure");
  -        //NNTPUtil.show(configuration,System.out);
           spoolPath = NNTPUtil.getDirectory(context, configuration, "spoolPath");
           int threadCount = configuration.getChild("threadCount").getValueAsInteger(1);
           int threadIdleTime = configuration.getChild("threadIdleTime").getValueAsInteger(1000);
           //String tgName=configuration.getChild("threadGroupName").getValue("NNTPSpooler");
  -        worker = new Worker[threadCount];
  +        worker = new SpoolerRunnable[threadCount];
           for ( int i = 0 ; i < worker.length ; i++ ) {
  -            worker[i] = new Worker(threadIdleTime,spoolPath);
  -            if ( worker[i] instanceof LogEnabled )
  +            worker[i] = new SpoolerRunnable(threadIdleTime,spoolPath);
  +            if ( worker[i] instanceof LogEnabled ) {
                   ((LogEnabled)worker[i]).enableLogging(getLogger());
  +            }
           }
       }
  +
  +    /**
  +     * Sets the repository used by this spooler.
  +     *
  +     * @param repo the repository to be used
  +     */
       void setRepository(NNTPRepository repo) {
  -        for ( int i = 0 ; i < worker.length ; i++ )
  +        for ( int i = 0 ; i < worker.length ; i++ ) {
               worker[i].setRepository(repo);
  +        }
       }
  +
  +    /**
  +     * Sets the article id repository used by this spooler.
  +     *
  +     * @param articleIDRepo the article id repository to be used
  +     */
       void setArticleIDRepository(ArticleIDRepository articleIDRepo) {
  -        for ( int i = 0 ; i < worker.length ; i++ )
  +        for ( int i = 0 ; i < worker.length ; i++ ) {
               worker[i].setArticleIDRepository(articleIDRepo);
  +        }
       }
  +
  +    /**
  +     * Returns (and creates, if the directory doesn't already exist) the
  +     * spool directory
  +     *
  +     * @return the spool directory
  +     */
       File getSpoolPath() {
           if ( spoolPath.exists() == false ) {
               spoolPath.mkdirs();
  @@ -79,35 +110,71 @@
       }
   
       /**
  -     * Initialize the component. Initialization includes
  -     * allocating any resources required throughout the
  -     * components lifecycle.
  -     *
  -     * @throws Exception if an error occurs
  +     * @see org.apache.avalon.framework.activity.Initializable#initialize()
        */
       public void initialize() throws Exception {
           //System.out.println(getClass().getName()+": init");
  -        for ( int i = 0 ; i < worker.length ; i++ )
  +        // TODO: Replace this with a standard Avalon thread pool
  +        for ( int i = 0 ; i < worker.length ; i++ ) {
               new Thread(worker[i],"NNTPSpool-"+i).start();
  +        }
       }
  -    static class Worker extends AbstractLogEnabled implements Runnable {
  +
  +    /**
  +     * A static inner class that provides the body for the spool
  +     * threads.
  +     */
  +    static class SpoolerRunnable extends AbstractLogEnabled implements Runnable {
  +
           private static final Lock lock = new Lock();
  +
  +        /**
  +         * The directory containing entries to be spooled.
  +         */
           private final File spoolPath;
  +
  +        /**
  +         * The time the spooler thread sleeps between processing
  +         */
           private final int threadIdleTime;
  +
  +        /**
  +         * The article ID repository used by this spooler thread
  +         */
           private ArticleIDRepository articleIDRepo;
  +
  +        /**
  +         * The NNTP repository used by this spooler thread
  +         */
           private NNTPRepository repo;
  -        Worker(int threadIdleTime,File spoolPath) {
  +
  +        SpoolerRunnable(int threadIdleTime,File spoolPath) {
               this.threadIdleTime = threadIdleTime;
               this.spoolPath = spoolPath;
           }
  +
  +        /**
  +         * Sets the article id repository used by this spooler thread.
  +         *
  +         * @param articleIDRepo the article id repository to be used
  +         */
           void setArticleIDRepository(ArticleIDRepository articleIDRepo) {
               this.articleIDRepo = articleIDRepo;
           }
  +
  +        /**
  +         * Sets the repository used by this spooler thread.
  +         *
  +         * @param repo the repository to be used
  +         */
           void setRepository(NNTPRepository repo) {
               this.repo = repo;
           }
  -        // the threads race to grab a lock. if a thread wins it processes the article,
  -        // if it loses it tries to lock and process the next article
  +
  +        /**
  +         * The threads race to grab a lock. if a thread wins it processes the article,
  +         * if it loses it tries to lock and process the next article.
  +         */
           public void run() {
               getLogger().debug("in spool thread");
               while ( Thread.currentThread().isInterrupted() == false ) {
  @@ -116,11 +183,11 @@
                       getLogger().debug("Files to process: "+list.length);
                       if ( lock.lock(list[i]) ) {
                           File f = new File(spoolPath,list[i]).getAbsoluteFile();
  -                        getLogger().debug("processing file: "+f.getAbsolutePath());
  +                        getLogger().debug("Processing file: "+f.getAbsolutePath());
                           try {
                               process(f);
                           } catch(Exception ex) {
  -                            getLogger().debug("exception occured in processing file: "+
  +                            getLogger().debug("Exception occured while processing file: "+
                                                 f.getAbsolutePath(),ex);
                           } finally {
                               lock.unlock(list[i]);
  @@ -128,10 +195,17 @@
                       }
                   }
                   // this is good for other non idle threads
  -                try {  Thread.currentThread().sleep(threadIdleTime);
  +                try {
  +                    Thread.currentThread().sleep(threadIdleTime);
                   } catch(InterruptedException ex) {  }
               }
           }
  +
  +        /**
  +         * Process a file stored in the spool.
  +         *
  +         * @param f the spool file being processed
  +         */
           private void process(File f) throws Exception {
               StringBuffer logBuffer =
                   new StringBuffer(160)
  @@ -142,6 +216,7 @@
               getLogger().debug(logBuffer.toString());
               final MimeMessage msg;
               String articleID;
  +            // TODO: Why is this a block?
               {   // get the message for copying to destination groups.
                   FileInputStream fin = new FileInputStream(f);
                   msg = new MimeMessage(null,fin);
  @@ -151,7 +226,7 @@
                   String[] idheader = msg.getHeader("Message-Id");
                   articleID = (idheader!=null && idheader.length>0?idheader[0]:null);
                   if ( articleIDRepo.isExists(articleID) ) {
  -                    getLogger().debug("message already exists: "+articleID);
  +                    getLogger().debug("Message already exists: "+articleID);
                       f.delete();
                       return;
                   }
  @@ -167,23 +242,26 @@
               String[] headers = msg.getHeader("Newsgroups");
               Properties prop = new Properties();
               for ( int i = 0 ; i < headers.length ; i++ ) {
  -                getLogger().debug("copying message to group: "+headers[i]);
  +                getLogger().debug("Copying message to group: "+headers[i]);
                   NNTPGroup group = repo.getGroup(headers[i]);
                   if ( group == null ) {
  -                    getLogger().debug("group not found: "+headers[i]);
  +                    getLogger().debug("Group not found: "+headers[i]);
                       continue;
                   }
                   int artNum = group.getLastArticleNumber();
  +
  +                // TODO: Encapsulate this in the NNTP group.
                   File root = (File)group.getPath();
                   File articleFile = null;
                   // this ensures that different threads do not create articles with
                   // same number
                   while( true ) {
                       articleFile = new File(root,(artNum+1)+"");
  -                    if (articleFile.createNewFile())
  +                    if (articleFile.createNewFile()) {
                           break;
  +                    }
                   }
  -                getLogger().debug("copying message to: "+articleFile.getAbsolutePath());
  +                getLogger().debug("Copying message to: "+articleFile.getAbsolutePath());
                   prop.setProperty(group.getName(),articleFile.getName());
                   FileInputStream fin = new FileInputStream(f);
                   FileOutputStream fout = new FileOutputStream(articleFile);
  @@ -193,8 +271,9 @@
               }
               articleIDRepo.addArticle(articleID,prop);
               boolean delSuccess = f.delete();
  -            if ( delSuccess == false )
  -                getLogger().error("could not delete file: "+f.getAbsolutePath());
  +            if ( delSuccess == false ) {
  +                getLogger().error("Could not delete file: "+f.getAbsolutePath());
  +            }
           }
  -    } // class Worker
  +    } // class SpoolerRunnable
   }
  
  
  
  1.10      +36 -21    jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPUtil.java
  
  Index: NNTPUtil.java
  ===================================================================
  RCS file: /home/cvs/jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPUtil.java,v
  retrieving revision 1.9
  retrieving revision 1.10
  diff -u -r1.9 -r1.10
  --- NNTPUtil.java	25 Sep 2002 23:29:39 -0000	1.9
  +++ NNTPUtil.java	2 Oct 2002 09:53:38 -0000	1.10
  @@ -31,13 +31,29 @@
    */
   public class NNTPUtil {
   
  -    private final static int prefixLength = "file://".length();
  +    /**
  +     * The file prefix String
  +     */
  +    private final static String filePrefix = "file://";
   
  +    /**
  +     * The length of the file prefix String
  +     */
  +    private final static int filePrefixLength = filePrefix.length();
  +
  +    /**
  +     * Get the explicit File represented by the URL String stored
  +     * in the child parameter.
  +     *
  +     * @param context the context for the NNTP repository
  +     * @param configuration the configuration for the NNTP repository
  +     * @param child the configuration parameter name that stores the URL
  +     */
       static File getDirectory(Context context, Configuration configuration, String child)
           throws ConfigurationException
       {
           String fileName = configuration.getChild(child).getValue();
  -        if (!fileName.toLowerCase(Locale.US).startsWith("file://") ) {
  +        if (!fileName.toLowerCase(Locale.US).startsWith(filePrefix) ) {
               StringBuffer exceptionBuffer =
                   new StringBuffer(128)
                           .append("Malformed ")
  @@ -45,7 +61,7 @@
                           .append(" - Must be of the format \"file://<filename>\".");
               throw new ConfigurationException(exceptionBuffer.toString());
           }
  -        fileName = fileName.substring(prefixLength);
  +        fileName = fileName.substring(filePrefixLength);
           if (!(fileName.startsWith("/"))) {
               String baseDirectory = "";
               try {
  @@ -79,14 +95,28 @@
           }
           return f;
       }
  -    public static Object createInstance(Context context, 
  +
  +    /**
  +     * Creates an instance of the spool class.
  +     *
  +     * @param context the context for the NNTP spooler
  +     * @param configuration the configuration for the NNTP spooler
  +     * @param logger the logger for the NNTP spooler
  +     * @param clsName the class name for the NNTP spooler
  +     *
  +     * TODO: This factory method doesn't properly implement the Avalon lifecycle.
  +     */
  +    static Object createInstance(Context context, 
                                           Configuration configuration,
                                           Logger logger,
                                           String clsName) 
               throws ConfigurationException
       {
  -        try { clsName = configuration.getAttribute("class");
  -        } catch(ConfigurationException ce) { }
  +        try {
  +            clsName = configuration.getAttribute("class");
  +        } catch(ConfigurationException ce) {
  +            // TODO: Why is this being caught and ignored?
  +        }
           try {
               Object obj = NNTPUtil.class.getClassLoader().loadClass(clsName).newInstance();
               if ( obj instanceof LogEnabled )
  @@ -99,21 +129,6 @@
           } catch(Exception ex) {
               ex.printStackTrace();
               throw new ConfigurationException("spooler initialization failed",ex);
  -        }
  -    }
  -
  -    public static void show(Configuration conf,PrintStream prt) {
  -        prt.println("conf.getClass=" + conf.getClass().getName());
  -        prt.println("name=" + conf.getName());
  -        Configuration[] children = conf.getChildren();
  -        for ( int i = 0 ; i < children.length ; i++ ) {
  -            
  -            StringBuffer showBuffer =
  -                new StringBuffer(64)
  -                        .append(i)
  -                        .append(". ")
  -                        .append(children[i].getName());
  -            prt.println(showBuffer.toString());
           }
       }
   }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/package.html
  
  Index: package.html
  ===================================================================
  <body>
  <p>Provides the interfaces that define NNTP articles, groups, and repositories as well as the implementations of those interfaces.</p>
  </body>
  
  
  

--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>