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 ch...@apache.org on 2001/05/15 16:42:46 UTC

cvs commit: jakarta-james/src/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

charlesb    01/05/15 07:42:45

  Added:       src/java/org/apache/james/nntpserver ArticleWriter.java
                        AuthState.java DateSinceFileFilter.java
                        LISTGroup.java NNTPException.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
  Removed:     src/org/apache/james/nntpserver ArticleWriter.java
                        AuthState.java DateSinceFileFilter.java
                        LISTGroup.java NNTPException.java NNTPHandler.java
                        NNTPServer.java NNTPServer.xinfo
               src/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
  Log:
  Moving from src/org to src/java/org
  
  Revision  Changes    Path
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/ArticleWriter.java
  
  Index: ArticleWriter.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver;
  
  import java.io.*;
  import org.apache.james.nntpserver.repository.NNTPArticle;
  
  // this is used by ARTICLE, HEAD, BODY, STAT command.
  // these commands are identical except in the writing the Article header 
  // and body to the response stream
  // ARTICLE - writes header and body
  // HEAD - writes headers
  // BODY - writes body
  // STAT - does not write anything
  interface ArticleWriter {
      void write(NNTPArticle article);
      class Factory {
          static ArticleWriter ARTICLE(final PrintWriter prt) {
              return new ArticleWriter() {
                      public void write(NNTPArticle article) {
                          article.writeArticle(prt);
                          prt.println(".");
                      }
                  };
          }
          static ArticleWriter HEAD(final PrintWriter prt) {
              return new ArticleWriter() {
                      public void write(NNTPArticle article) {
                          article.writeHead(prt);
                          prt.println(".");
                      }
                  };
          }
          static ArticleWriter BODY(final PrintWriter prt) {
              return new ArticleWriter() {
                      public void write(NNTPArticle article) {
                          article.writeBody(prt);
                          prt.println(".");
                      }
                  };
          }
          static ArticleWriter STAT(final PrintWriter prt) {
              return new ArticleWriter() {
                      public void write(NNTPArticle article) { }
                  };
          }
          static ArticleWriter OVER(final PrintWriter prt) {
              return new ArticleWriter() {
                      public void write(NNTPArticle article) { 
                          article.writeOverview(prt);
                      }
                  };
          }
      } // class Factory
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/AuthState.java
  
  Index: AuthState.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver;
  
  import java.io.OutputStream;
  import java.io.OutputStreamWriter;
  import java.io.PrintWriter;
  import java.io.Writer;
  import org.apache.james.services.UsersRepository;
  
  /**
   * @version 1.0.0, 31/03/2001
   * @author  Harmeet
   *
   * implements the authentication state. Should this be moved to a more common place ??
   * Should there be an authenication service, that manufactures and hands to the different
   * protocol servers AuthState objects.
   */
  public class AuthState {
      private final boolean requiredAuth;
      private final UsersRepository repo;
      private String user;
      private String password;
      public AuthState(boolean requiredAuth,UsersRepository repo) {
          this.requiredAuth = requiredAuth;
          this.repo = repo;
      }
  
      public boolean isAuthenticated() {
          if ( requiredAuth )
              return repo.test(user,password);
          else
              return true;
      }
      public void setUser(String user) {
          this.user = user;
          this.password = null;
      }
      public void setPassword(String password) {
          this.password = password;
      }
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/DateSinceFileFilter.java
  
  Index: DateSinceFileFilter.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver;
  
  import java.io.File;
  import java.io.FilenameFilter;
  
  /**
   * filters files according to their date of last modification
   *
   * @author  Harmeet Bedi <ha...@kodemuse.com>
   */
  public class DateSinceFileFilter implements FilenameFilter
  {
      private final long m_date;
  
      public DateSinceFileFilter( long date ) 
      {
          m_date = date;
      }
  
      public boolean accept( final File file, final String name ) 
      {
          return (new File(file,name).lastModified() >= m_date);
      }
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/LISTGroup.java
  
  Index: LISTGroup.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver;
  
  import java.io.*;
  import org.apache.james.nntpserver.repository.NNTPGroup;
  
  // group information would be displayed differently depending on the 
  // LIST command paramter
  interface LISTGroup {
      void show(NNTPGroup group);
      class Factory {
          static LISTGroup ACTIVE(final PrintWriter prt) {
              return new LISTGroup() {
                      public void show(NNTPGroup group) {
                          prt.println(group.getName()+" "+group.getFirstArticleNumber()+" "+
                                      group.getLastArticleNumber()+" "+
                                      (group.isPostAllowed()?"y":"n"));
                      }
                  };
          }
          static LISTGroup NEWSGROUPS(final PrintWriter prt) {
              return new LISTGroup() {
                      public void show(NNTPGroup group) {
                          prt.println(group.getName()+" "+group.getDescription());
                      }
                  };
          }
      } // class Factory
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/NNTPException.java
  
  Index: NNTPException.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver;
  
  public class NNTPException extends RuntimeException {
      private final Throwable t;
      public NNTPException(String msg) {
          super(msg);
          this.t = null;
      }
      public NNTPException(String msg,Throwable t) {
          super(msg+((t!=null)?": "+t.toString():""));
          this.t = t;
      }
      public NNTPException(Throwable t) {
          super(t.toString());
          this.t = t;
      }
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/NNTPHandler.java
  
  Index: NNTPHandler.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver;
  
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.io.PrintWriter;
  import java.net.Socket;
  import java.text.DateFormat;
  import java.text.ParseException;
  import java.text.SimpleDateFormat;
  import java.util.ArrayList;
  import java.util.Calendar;
  import java.util.Date;
  import java.util.Enumeration;
  import java.util.Iterator;
  import java.util.List;
  import java.util.StringTokenizer;
  import java.util.Vector;
  import org.apache.avalon.framework.activity.Initializable;
  import org.apache.avalon.framework.component.ComponentException;
  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.context.Context;
  import org.apache.avalon.framework.context.ContextException;
  import org.apache.avalon.framework.context.Contextualizable;
  import org.apache.avalon.framework.logger.AbstractLoggable;
  import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
  import org.apache.avalon.cornerstone.services.scheduler.PeriodicTimeTrigger;
  import org.apache.avalon.cornerstone.services.scheduler.Target;
  import org.apache.avalon.cornerstone.services.scheduler.TimeScheduler;
  import org.apache.avalon.excalibur.collections.ListUtils;
  import org.apache.james.Constants;
  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.nntpserver.repository.NNTPUtil;
  import org.apache.james.services.UsersRepository;
  import org.apache.james.services.UsersStore;
  
  /**
   * The NNTP protocol is defined by RFC 977.
   * This implementation is based on IETF draft 13, posted on 2nd April '2001.
   * URL: http://www.ietf.org/internet-drafts/draft-ietf-nntpext-base-13.txt
   *
   * Common NNTP extensions are in RFC 2980.
   *
   * @author Fedor Karpelevitch
   * @author Harmeet <hb...@apache.org>
   */
  public class NNTPHandler extends BaseConnectionHandler
      implements ConnectionHandler, Composable, Configurable, Target {
  
      // timeout controllers
      private TimeScheduler scheduler;
  
      // communciation.
      private Socket socket;
      private BufferedReader reader;
      private PrintWriter writer;
  
      // authentication.
      private AuthState authState;
      private boolean authRequired = false;
      private UsersRepository users;
  
      // data abstractions.
      private NNTPGroup group;
      private NNTPRepository repo;
  
      public void configure( Configuration configuration ) throws ConfigurationException {
          super.configure(configuration);
          authRequired=configuration.getChild("authRequired").getValueAsBoolean(false);
          authState = new AuthState(authRequired,users);
      }
  
      public void compose( final ComponentManager componentManager )
          throws ComponentException
      {
          //System.out.println(getClass().getName()+": compose - "+authRequired);
          UsersStore usersStore = (UsersStore)componentManager.
              lookup( "org.apache.james.services.UsersStore" );
          users = usersStore.getRepository("LocalUsers");
          scheduler = (TimeScheduler)componentManager.
              lookup( "org.apache.avalon.cornerstone.services.scheduler.TimeScheduler" );
          repo = (NNTPRepository)componentManager
              .lookup("org.apache.james.nntpserver.repository.NNTPRepository");
      }
  
      public void handleConnection( Socket connection ) throws IOException {
          try {
              this.socket = connection;
              reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
              writer = new PrintWriter(socket.getOutputStream()) {
                      public void println() {
                          print("\r\n");
                          flush();
                      }
                  };
              getLogger().info( "Connection from " + socket.getInetAddress());
          } catch (Exception e) {
              getLogger().error( "Cannot open connection from: " + e.getMessage(), e );
          }
  
          try {
              final PeriodicTimeTrigger trigger = new PeriodicTimeTrigger( timeout, -1 );
              scheduler.addTrigger( this.toString(), trigger, this );
  
              // section 7.1
              writer.println("200 "+helloName+
                             (repo.isReadOnly()?" Posting Not Premitted":" Posting Permitted"));
  
              while (parseCommand(reader.readLine()))
                  scheduler.resetTrigger(this.toString());
  
              reader.close();
              writer.close();
              socket.close();
              scheduler.removeTrigger(this.toString());
              getLogger().info("Connection closed");
          } catch (Exception e) {
              doQUIT();
              //writer.println("502 Error closing connection.");
              //writer.flush();
              getLogger().error( "Exception during connection:" + e.getMessage(), e );
              try { socket.close();   } catch (IOException ioe) {  }
          }
      }
  
      public void targetTriggered( final String triggerName ) {
          getLogger().error("Connection timeout on socket");
          try {
              writer.println("Connection timeout. Closing connection");
              socket.close();
          } catch (IOException e) { }
      }
  
      // checks if a command is allowed. The authentication state is validated.
      private boolean isAllowed(String command) {
          boolean allowed = authState.isAuthenticated();
          // some commads are allowed, even if the user is not authenticated
          allowed = allowed || command.equalsIgnoreCase("AUTHINFO");
          allowed = allowed || command.equalsIgnoreCase("AUTHINFO");
          allowed = allowed || command.equalsIgnoreCase("MODE");
          allowed = allowed || command.equalsIgnoreCase("QUIT");
          if ( allowed == false )
              writer.println("502 User is not authenticated");
          return allowed;
      }
      private boolean parseCommand(String commandRaw) {
          if (commandRaw == null)
              return false;
          getLogger().info("Command received: " + commandRaw);
          //System.out.println("NNTPHandler> "+commandRaw);
  
          StringTokenizer tokens = new StringTokenizer(commandRaw);
          if (!tokens.hasMoreTokens())
              return false;
          final String command = tokens.nextToken();
  
          //System.out.println("allowed="+isAllowed(command)+","+authState.isAuthenticated());
          if ( isAllowed(command) == false )
              return true;
          if ( command.equalsIgnoreCase("MODE") && tokens.hasMoreTokens() &&
               tokens.nextToken().equalsIgnoreCase("READER") )
              doMODEREADER();
          else if ( command.equalsIgnoreCase("LIST") && tokens.hasMoreTokens() &&
                    tokens.nextToken().equalsIgnoreCase("EXTENSIONS") )
              doLISTEXTENSIONS();
          else if ( command.equalsIgnoreCase("LIST") && tokens.hasMoreTokens() &&
                    tokens.nextToken().equalsIgnoreCase("OVERVIEW.FMT") )
              doLISTOVERVIEWFMT();
          else if ( command.equalsIgnoreCase("GROUP") )
              doGROUP(tokens.hasMoreTokens()?tokens.nextToken():null);
          else if ( command.equalsIgnoreCase("LAST") )
              doLAST();
          else if ( command.equalsIgnoreCase("ARTICLE") )
              doARTICLE(tokens.hasMoreTokens()?tokens.nextToken():null);
          else if ( command.equalsIgnoreCase("HEAD") )
              doHEAD(tokens.hasMoreTokens()?tokens.nextToken():null);
          else if ( command.equalsIgnoreCase("BODY") )
              doBODY(tokens.hasMoreTokens()?tokens.nextToken():null);
          else if ( command.equalsIgnoreCase("STAT") )
              doSTAT(tokens.hasMoreTokens()?tokens.nextToken():null);
          else if ( command.equalsIgnoreCase("POST") )
              doPOST();
          else if ( command.equalsIgnoreCase("IHAVE") )
              doIHAVE(tokens.hasMoreTokens()?tokens.nextToken():null);
          else if ( command.equalsIgnoreCase("LIST") )
              doLIST(tokens);
          else if ( command.equalsIgnoreCase("QUIT") )
              doQUIT();
          else if ( command.equalsIgnoreCase("DATE") )
              doDATE();
          else if ( command.equalsIgnoreCase("HELP") )
              doHELP();
          else if ( command.equalsIgnoreCase("NEWNEWS") )
              doNEWSGROUPS(tokens);
          else if ( command.equalsIgnoreCase("NEWNEWS") )
              doNEWNEWS(tokens);
          else if ( command.equalsIgnoreCase("LISTGROUP") )
              doLISTGROUP(tokens.hasMoreTokens()?tokens.nextToken():null);
          else if ( command.equalsIgnoreCase("OVER") )
              doOVER(tokens.hasMoreTokens()?tokens.nextToken():null);
          else if ( command.equalsIgnoreCase("XOVER") )
              doXOVER(tokens.hasMoreTokens()?tokens.nextToken():null);
          else if ( command.equalsIgnoreCase("PAT") )
              doPAT();
          else if ( command.equalsIgnoreCase("HDR") )
              doHDR(tokens);
          else if ( command.equalsIgnoreCase("XHDR") )
              doXHDR(tokens);
          else if ( command.equalsIgnoreCase("AUTHINFO") )
              doAUTHINFO(tokens);
          else
              writer.println("501 Syntax error");
          return (command.equalsIgnoreCase("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();
          if ( command.equalsIgnoreCase("USER") ) {
              authState.setUser(tok.nextToken());
              writer.println("381 More authentication information required");
          } else if ( command.equalsIgnoreCase("PASS") ) {
              authState.setPassword(tok.nextToken());
              if ( authState.isAuthenticated() )
                  writer.println("281 Authentication accepted");
              else
                  writer.println("482 Authentication rejected");
          }
      }
  
      private void doNEWNEWS(StringTokenizer tok) {
          writer.println("230 list of new articles by message-id follows");
          Iterator iter = repo.getArticlesSince(getDateFrom(tok));
          while ( iter.hasNext() )
              writer.println("<"+((NNTPArticle)iter.next()).getUniqueID()+">");
          writer.println(".");
      }
      private void doNEWSGROUPS(StringTokenizer tok) {
          writer.println("230 list of new newsgroups follows");
          Iterator iter = repo.getGroupsSince(getDateFrom(tok));
          while ( iter.hasNext() )
              writer.println(((NNTPGroup)iter.next()).getName());
          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 would 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();
          DateFormat df = ( date.length() == 8 ) ? DF_DATEFROM_LONG : DF_DATEFROM_SHORT;
          try {
              Date dt = df.parse(date+" "+time);
              if ( utc )
                  dt = new Date(dt.getTime()+UTC_OFFSET);
              return dt;
          } catch ( ParseException pe ) {
              throw new NNTPException("date extraction failed: "+date+","+time+","+utc);
          }
      }
  
      private void doHELP() {
          writer.println("100 Help text follows");
          writer.println(".");
      }
  
      // used to calculate DATE from - see 11.3
      public static final DateFormat DF_DATEFROM_LONG = new SimpleDateFormat("yyyyMMdd HHmmss");
      public static final DateFormat DF_DATEFROM_SHORT = new SimpleDateFormat("yyMMdd HHmmss");
  
      // Date format for the DATE keyword - see 11.1.1
      public static final DateFormat DF_DATE = new SimpleDateFormat("yyyyMMddHHmmss");
      public static final long UTC_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
      private void doDATE() {
          //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);
          String dtStr = DF_DATE.format(new Date(dt.getTime() - UTC_OFFSET));
          writer.println("111 "+dtStr);
      }
      private void doQUIT() {
          writer.println("205 closing connection");
      }
      private void doLIST(StringTokenizer tok) {
          // see section 9.4.1
          String wildmat = "*";
          LISTGroup output = LISTGroup.Factory.ACTIVE(writer);
          if ( tok.hasMoreTokens() ) {
              String param = tok.nextToken();
              // list of variations not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
              String[] notSupported = { "ACTIVE.TIMES", "DISTRIBUTIONS", "DISTRIB.PATS" };
              for ( int i = 0 ; i < notSupported.length ; i++ ) {
                  if ( param.equalsIgnoreCase("ACTIVE.TIMES") ) {
                      writer.println("503 program error, function not performed");
                      return;
                  }
              }
              if ( param.equalsIgnoreCase("NEWSGROUPS") )
                  output = LISTGroup.Factory.NEWSGROUPS(writer);
              else
                  assert(param,param.equalsIgnoreCase("ACTIVE"));
              if ( tok.hasMoreTokens() )
                  wildmat = tok.nextToken();
          }
          Iterator iter = repo.getMatchedGroups(wildmat);
          writer.println("215 list of newsgroups follows");
          while ( iter.hasNext() )
              output.show((NNTPGroup)iter.next());
          writer.println(".");
      }
      private void assert(String id,boolean b) {
          if ( b == false )
              throw new RuntimeException(id);
      }
      private void doIHAVE(String id) {
          // see section 9.3.2.1
          assert(id,id.startsWith("<") && id.endsWith(">"));
          NNTPArticle article = repo.getArticleFromID(id);
          if ( article != null )
              writer.println("435 article not wanted - do not send it");
          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() {
          // 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));
      }
      private void doSTAT(String param) {
          doARTICLE(param,ArticleWriter.Factory.STAT(writer));
      }
      private void doBODY(String param) {
          doARTICLE(param,ArticleWriter.Factory.BODY(writer));
      }
      private void doHEAD(String param) {
          doARTICLE(param,ArticleWriter.Factory.HEAD(writer));
      }
      private void doARTICLE(String param) {
          doARTICLE(param,ArticleWriter.Factory.ARTICLE(writer));
      }
      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.substring(1,param.length()-2));
              if ( article == null )
                  writer.println("430 no such article");
              else
                  writer.println("220 0 "+param+" article retrieved and follows");
          }
          else {
              if ( group == null )
                  writer.println("412 no newsgroup selected");
              else {
                  if ( param == null ) {
                      if ( group.getCurrentArticleNumber() < 0 )
                          writer.println("420 no current article selected");
                      else
                          article = group.getCurrentArticle();
                  }
                  else
                      article = group.getArticle(Integer.parseInt(param));
                  if ( article == null )
                      writer.println("423 no such article number in this group");
                  else
                      writer.println("220 "+article.getArticleNumber()+" "+
                                     article.getUniqueID()+" article retrieved and follows");
              }
          }
          if ( article != null )
              articleWriter.write(article);
      }
      private void doNEXT() {
          // section 9.1.1.3.1
          if ( group == null )
              writer.println("412 no newsgroup selected");
          else if ( group.getCurrentArticleNumber() < 0 )
              writer.println("420 no current article has been selected");
          else if ( group.getCurrentArticleNumber() >= group.getLastArticleNumber() )
              writer.println("421 no next article in this group");
          else {
              NNTPArticle article = group.getCurrentArticle();
              group.setCurrentArticleNumber(group.getCurrentArticleNumber()+1);
              writer.println("223 "+article.getArticleNumber()+" "+article.getUniqueID());
          }
      }
      private void doLAST() {
          // section 9.1.1.2.1
          if ( group == null )
              writer.println("412 no newsgroup selected");
          else if ( group.getCurrentArticleNumber() < 0 )
              writer.println("420 no current article has been selected");
          else if ( group.getCurrentArticleNumber() <= group.getFirstArticleNumber() )
              writer.println("422 no previous article in this group");
          else {
              NNTPArticle article = group.getCurrentArticle();
              group.setCurrentArticleNumber(group.getCurrentArticleNumber()-1);
              writer.println("223 "+article.getArticleNumber()+" "+article.getUniqueID());
          }
      }
      private void doGROUP(String groupName) {
          group = repo.getGroup(groupName);
          // section 9.1.1.1
          if ( group == null )
              writer.println("411 no such newsgroup");
          else
              writer.println("211 "+group.getNumberOfArticles()+" "+
                             group.getFirstArticleNumber()+" "+
                             group.getLastArticleNumber()+" "+
                             group.getName()+" group selected");
  
      }
      private void doLISTEXTENSIONS() {
          // 8.1.1
          writer.println("202 Extensions supported:");
          writer.println("LISTGROUP");
          writer.println("AUTHINFO");
          writer.println("OVER");
          writer.println("XOVER");
          writer.println("HDR");
          writer.println("XHDR");
          writer.println(".");
      }
  
      private void doMODEREADER() {
          // 7.2
          writer.println(repo.isReadOnly()
                         ? "201 Posting Not Permitted" : "200 Posting Permitted");
      }
  
      private void doLISTGROUP(String groupName) {
          // 9.5.1.1.1
          NNTPGroup group = null;
          if (groupName==null) {
              if ( group == null )
                  writer.println("412 not currently in newsgroup");
          }
          else {
              group = repo.getGroup(groupName);
              if ( group == null )
                  writer.println("411 no such newsgroup");
          }
          if ( group != null ) {
              writer.println("211 list of article numbers follow");
  
              for (Iterator iter = group.getArticles();iter.hasNext();) {
                  NNTPArticle article = (NNTPArticle)iter.next();
                  writer.println(article.getArticleNumber());
              }
              writer.println(".");
              this.group = group;
              group.setCurrentArticleNumber(group.getFirstArticleNumber());
          }
      }
  
      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() {
          // 9.5.3.1.1 in draft-12
          writer.println("500 Command not recognized");
      }
      private void doXHDR(StringTokenizer tok) {
          doHDR(tok);
      }
      private void doHDR(StringTokenizer tok) {
          // 9.5.3
          writer.println("500 Command not recognized");
          String hdr = tok.nextToken();
          String range = tok.hasMoreTokens() ? tok.nextToken() : null;
          NNTPArticle[] article = getRange(range);
          if ( article == null )
              writer.println("412 no newsgroup selected");
          else if ( article.length == 0 )
              writer.println("430 no such article");
          else {
              writer.println("221 Header follows");
              for ( int i = 0 ; i < article.length ; i++ ) {
                  String val = article[i].getHeader(hdr);
                  if ( val == null )
                      val = "";
                  writer.println(article[i].getArticleNumber()+" "+val);
              }
              writer.println(".");
          }
      }
      // 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("<") ) {
              NNTPArticle article = repo.getArticleFromID(range);
              return ( article == null )
                  ? new NNTPArticle[0] : new NNTPArticle[] { article };
          }
  
          if ( group == null )
              return null;
          if ( range == null )
              range = ""+group.getCurrentArticleNumber();
  
          int start = -1;
          int end = -1;
          int idx = range.indexOf('-');
          if ( idx == -1 ) {
              start = end = Integer.parseInt(range);
          } else {
              start = Integer.parseInt(range.substring(0,idx));
              if ( idx+1 == range.length() )
                  end = group.getLastArticleNumber();
              else
                  end = Integer.parseInt(range.substring(idx+1));
          }
          List list = new ArrayList();
          for ( int i = start ; i <= end ; i++ ) {
              NNTPArticle article = group.getArticle(i);
              if ( article != null )
                  list.add(article);
          }
          return (NNTPArticle[])list.toArray(new NNTPArticle[0]);
      }
  
      private void doXOVER(String range) {
          doOVER(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(".");
          }
      }
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/NNTPServer.java
  
  Index: NNTPServer.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver;
  
  import java.net.InetAddress;
  import java.net.UnknownHostException;
  import org.apache.avalon.framework.activity.Initializable;
  import org.apache.avalon.framework.component.Component;
  import org.apache.avalon.framework.component.ComponentException;
  import org.apache.avalon.framework.component.ComponentManager;
  import org.apache.avalon.framework.component.DefaultComponentManager;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  import org.apache.avalon.cornerstone.services.connection.AbstractService;
  import org.apache.avalon.cornerstone.services.connection.ConnectionHandlerFactory;
  import org.apache.avalon.cornerstone.services.connection.DefaultHandlerFactory;
  
  /**
   * @author Harmeet <hb...@apache.org>
   */
  public class NNTPServer extends AbstractService {
  
      protected ConnectionHandlerFactory createFactory() {
          return new DefaultHandlerFactory( NNTPHandler.class );
      }
  
      public void configure( final Configuration configuration )
          throws ConfigurationException
      {
          //System.out.println(getClass().getName()+": configure");
          m_port = configuration.getChild( "port" ).getValueAsInteger( 119 );
  
          try {
              String bindAddress = configuration.getChild( "bind" ).getValue( null );
              if( null != bindAddress )
                  m_bindTo = InetAddress.getByName( bindAddress );
          } catch( final UnknownHostException unhe ) {
              throw new ConfigurationException( "Malformed bind parameter", unhe );
          }
  
          final String useTLS = configuration.getChild("useTLS").getValue( "" );
          if( useTLS.equals( "TRUE" ) )
              m_serverSocketType = "ssl";
  
          super.configure( configuration.getChild( "nntphandler" ) );
          getLogger().info("configured NNTPServer to run at : "+m_port);
      }
  
      public void initialize() throws Exception {
          //System.out.println(getClass().getName()+": init");
          super.initialize();
          System.out.println("Started NNTP Server "+m_connectionName);
      }
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/NNTPServer.xinfo
  
  Index: NNTPServer.xinfo
  ===================================================================
  <?xml version="1.0"?>
  
  <blockinfo>
    <services>
      <service name="org.apache.avalon.framework.component.Component" version="1.0"/>
    </services>
  
    <dependencies>
      <dependency>
        <role>org.apache.james.services.MailStore</role>
        <service name="org.apache.james.services.MailStore" version="1.0"/>
      </dependency>
      <dependency>
        <role>org.apache.james.services.UsersStore</role>
        <service name="org.apache.james.services.UsersStore" version="1.0"/>
      </dependency>
      <dependency>
        <role>org.apache.avalon.cornerstone.services.connection.ConnectionManager</role>
        <service name="org.apache.avalon.cornerstone.services.connection.ConnectionManager" 
                 version="1.0"/>
      </dependency>
      <dependency>
        <role>org.apache.avalon.cornerstone.services.sockets.SocketManager</role>
        <service name="org.apache.avalon.cornerstone.services.sockets.SocketManager" version="1.0"/>
      </dependency>
      <dependency>
        <role>org.apache.avalon.cornerstone.services.scheduler.TimeScheduler</role>
        <service name="org.apache.avalon.cornerstone.services.scheduler.TimeScheduler" version="1.0"/>
      </dependency> 
      <dependency>
        <role>org.apache.james.nntpserver.repository.NNTPRepository</role>
        <service name="org.apache.james.nntpserver.repository.NNTPRepository" version="1.0"/>
      </dependency> 
    </dependencies>  
  </blockinfo>
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/ArticleIDRepository.java
  
  Index: ArticleIDRepository.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.util.*;
  import java.io.*;
  import org.apache.avalon.excalibur.io.ExtensionFileFilter;
  import org.apache.avalon.excalibur.io.InvertedFileFilter;
  import org.apache.avalon.excalibur.io.AndFileFilter;
  import org.apache.james.nntpserver.NNTPException;
  import org.apache.james.nntpserver.DateSinceFileFilter;
  import sun.misc.BASE64Encoder;
  
  /** 
   * ArticleIDRepository: contains one file for each article.
   * the file name is Base64 encoded article ID
   * The first line of the file is '# <create date of file>
   * the rest of line have <newsgroup name>=<article number>
   *
   * this would allow fast lookup of a message by message id.
   * allow a process to iterate and sycnhronize messages with other NNTP Servers.
   * this may be inefficient, so could be used for sanity checks and an alternate
   * more efficient process could be used for synchronization.
   */
  public class ArticleIDRepository {
      private final File root;
      private final String articleIDDomainSuffix;
      private int counter = 0;
      ArticleIDRepository(File root,String articleIDDomainSuffix) {
          this.root = root;
          this.articleIDDomainSuffix = articleIDDomainSuffix;
      }
      public File getPath() {
          return root;
      }
      String generateArticleID() {
          int idx = Math.abs(counter++);
          String unique = Thread.currentThread().hashCode()+"."+
              System.currentTimeMillis()+"."+idx;
          return "<"+unique+"@"+articleIDDomainSuffix+">";
      }
      /** @param prop contains the newsgroup name and article number */
      void addArticle(String articleID,Properties prop) throws IOException {
          if ( articleID == null )
              articleID = generateArticleID();
          FileOutputStream fout = new FileOutputStream(getFileFromID(articleID));
          prop.store(fout,new Date().toString());
          fout.close();
      }
      File getFileFromID(String articleID) {
          return new File(root,new BASE64Encoder().encode(articleID.getBytes()));
      }
      boolean isExists(String articleID) {
          return ( articleID == null ) ? false : getFileFromID(articleID).exists();
      }
      NNTPArticle getArticle(NNTPRepository repo,String id) throws IOException {
          File f = getFileFromID(id);
          if ( f.exists() == false )
              return null;
          FileInputStream fin = new FileInputStream(f);
          Properties prop = new Properties();
          prop.load(fin);
          fin.close();
          Enumeration enum = prop.keys();
          NNTPArticle article = null;
          while ( article == null && enum.hasMoreElements() ) {
              String groupName = (String)enum.nextElement();
              int number = Integer.parseInt(prop.getProperty(groupName));
              NNTPGroup group = repo.getGroup(groupName);
              if ( group != null )
                  article = group.getArticle(number);
          }
          return article;
      }
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPArticle.java
  
  Index: NNTPArticle.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.io.*;
  import javax.mail.internet.InternetHeaders;
  import org.apache.james.nntpserver.NNTPException;
  
  public interface NNTPArticle {
      NNTPGroup getGroup();
      int getArticleNumber();
      String getUniqueID();
      void writeArticle(PrintWriter wrt);
      void writeHead(PrintWriter wrt);
      void writeBody(PrintWriter wrt);
      void writeOverview(PrintWriter wrt);
      String getHeader(String header);
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPArticleImpl.java
  
  Index: NNTPArticleImpl.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.io.*;
  import javax.mail.internet.InternetHeaders;
  import org.apache.james.nntpserver.NNTPException;
  
  class NNTPArticleImpl implements NNTPArticle {
      private final File f;
      NNTPArticleImpl(File f) {
          this.f = f;
      }
      public NNTPGroup getGroup() {
          return new NNTPGroupImpl(f.getParentFile());
      }
      public int getArticleNumber() {
          return Integer.parseInt(f.getName());
      }
      public String getUniqueID() {
          try {
              FileInputStream fin = new FileInputStream(f);
              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); }
      }
      public void writeArticle(PrintWriter prt) {
          try {
              BufferedReader reader = new BufferedReader(new FileReader(f));
              String line = null;
              while ( ( line = reader.readLine() ) != null )
                  prt.println(line);
              reader.close();
          } catch(IOException ex) { throw new NNTPException(ex); }
      }
      public void writeHead(PrintWriter prt) {
          try {
              BufferedReader reader = new BufferedReader(new FileReader(f));
              String line = null;
              while ( ( line = reader.readLine() ) != null ) {
                  if ( line.trim().length() == 0 )
                      break;
                  prt.println(line);
              }
              reader.close();
          } catch(IOException ex) { throw new NNTPException(ex); }
      }
      public void writeBody(PrintWriter prt) {
          try {
              BufferedReader reader = new BufferedReader(new FileReader(f));
              String line = null;
              boolean startWriting = false;
              while ( ( line = reader.readLine() ) != null ) {
                  if ( startWriting )
                      prt.println(line);
                  else
                      startWriting = ( line.trim().length() == 0 );
              }
              reader.close();
          } catch(IOException ex) { throw new NNTPException(ex); }
      }
  
      public void writeOverview(PrintWriter prt) {
          try {
              FileInputStream fin = new FileInputStream(f);
              InternetHeaders hdr = new InternetHeaders(fin);
              fin.close();
              int articleNumber = getArticleNumber();
              String subject = hdr.getHeader("Subject",null);
              String author = hdr.getHeader("From",null);
              String date = hdr.getHeader("Date",null);
              String msgId = hdr.getHeader("Message-Id",null);
              String references = hdr.getHeader("References",null);
              long byteCount = f.length();
              long lineCount = -1;
              prt.print(articleNumber+"\t");
              prt.print((subject==null?"":subject)+"\t");
              prt.print((author==null?"":author)+"\t");
              prt.print((date==null?"":date)+"\t");
              prt.print((msgId==null?"":msgId)+"\t");
              prt.print((references==null?"":references)+"\t");
              prt.print(byteCount+"\t");
              prt.println(lineCount+"");
          } catch(Exception ex) { throw new NNTPException(ex); }
      }
      public String getHeader(String header) {
          try {
              FileInputStream fin = new FileInputStream(f);
              InternetHeaders hdr = new InternetHeaders(fin);
              fin.close();
              return hdr.getHeader(header,null);
          } catch(Exception ex) { throw new NNTPException(ex); }
      }
  }
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPGroup.java
  
  Index: NNTPGroup.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.util.*;
  import java.io.*;
  import org.apache.avalon.excalibur.io.ExtensionFileFilter;
  import org.apache.avalon.excalibur.io.InvertedFileFilter;
  import org.apache.avalon.excalibur.io.AndFileFilter;
  import org.apache.james.nntpserver.NNTPException;
  import org.apache.james.nntpserver.DateSinceFileFilter;
  
  public interface NNTPGroup {
      String getName();
      String getDescription();
      boolean isPostAllowed();
  
      // the current article pointer. <0 indicates invalid/unknown value
      int getCurrentArticleNumber();
      void setCurrentArticleNumber(int articleNumber);
  
      int getNumberOfArticles();
      int getFirstArticleNumber();
      int getLastArticleNumber();
  
      NNTPArticle getCurrentArticle();
      NNTPArticle getArticle(int number);
      //NNTPArticle getArticleFromID(String id);
      Iterator getArticlesSince(Date dt);
      Iterator getArticles();
      Object getPath();
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPGroupImpl.java
  
  Index: NNTPGroupImpl.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.util.*;
  import java.io.*;
  import org.apache.avalon.excalibur.io.ExtensionFileFilter;
  import org.apache.avalon.excalibur.io.InvertedFileFilter;
  import org.apache.avalon.excalibur.io.AndFileFilter;
  import org.apache.james.nntpserver.NNTPException;
  import org.apache.james.nntpserver.DateSinceFileFilter;
  
  // group is reprensted by a directory.
  // articles are stored in files with the name of file == article number
  class NNTPGroupImpl implements NNTPGroup {
      private final File root;
      private int currentArticle = -1;
      private int lastArticle;
      private int firstArticle;
      // an instance may collect range info once. This involves disk I/O
      private boolean articleRangeInfoCollected = false;
      NNTPGroupImpl(File root) {
          this.root = root;
      }
      public String getName() {
          return root.getName();
      }
      public String getDescription() {
          return getName();
      }
      public boolean isPostAllowed() {
          return true;
      }
      private void collectArticleRangeInfo() {
          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 )
                  first = num;
              if ( num > last )
                  last = num;
          }
          firstArticle = Math.max(first,0);
          lastArticle = Math.max(last,0);
          articleRangeInfoCollected = true;
      }
      public int getNumberOfArticles() {
          return getLastArticleNumber() - getFirstArticleNumber();
      }
      public int getFirstArticleNumber() {
          collectArticleRangeInfo();
          return firstArticle;
      }
      public int getLastArticleNumber() {
          collectArticleRangeInfo();
          return lastArticle;
      }
      public int getCurrentArticleNumber() {
          collectArticleRangeInfo();
          // this is not as per RFC, but this is not significant.
          if ( currentArticle == -1 && firstArticle > 0 )
              currentArticle = firstArticle;
          return currentArticle;
      }
      public void setCurrentArticleNumber(int articleNumber) {
          this.currentArticle = articleNumber;
      }
  
      public NNTPArticle getCurrentArticle() {
          return getArticle(getCurrentArticleNumber());
      }
      public NNTPArticle getArticle(int number) {
          File f = new File(root,number+"");
          return f.exists() ? new NNTPArticleImpl(f) : null;
      }
  //     public NNTPArticle getArticleFromID(String id) {
  //         if ( id == null )
  //             return null;
  //         int idx = id.indexOf('@');
  //         if ( idx != -1 )
  //             id = id.substring(0,idx);
  //         File f = new File(root,id+".id");
  //         if ( f.exists() == false )
  //             return null;
  //         try {
  //             FileInputStream fin = new FileInputStream(f);
  //             int count = fin.available();
  //             byte[] ba = new byte[count];
  //             fin.read(ba);
  //             fin.close();
  //             String str = new String(ba);
  //             int num = Integer.parseInt(str);
  //             return getArticle(num);
  //         } catch(IOException ioe) {
  //             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.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPLineReader.java
  
  Index: NNTPLineReader.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.io.*;
  
  // this interface is used to read the data from client and stream it
  // into server repository
  public interface NNTPLineReader {
      // reads a line of data.
      // @return null indicates end of data
      String readLine();
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPLineReaderImpl.java
  
  Index: NNTPLineReaderImpl.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.io.*;
  import org.apache.james.nntpserver.NNTPException;
  
  public class NNTPLineReaderImpl implements NNTPLineReader {
      private final BufferedReader reader;
      public NNTPLineReaderImpl(BufferedReader reader) {
          this.reader = reader;
      }
      public String readLine() {
          try {
              String line = reader.readLine();
              // check for end of article.
              if ( line.equals(".") )
                  line = null;
              else if ( line.startsWith(".") )
                   line = line.substring(1,line.length());
              return line;
          } catch(IOException ioe) {
              throw new NNTPException("could not create article",ioe);
          }
      }
  }
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPRepository.java
  
  Index: NNTPRepository.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.io.*;
  import java.util.*;
  import org.apache.avalon.framework.activity.Initializable;
  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.AbstractLoggable;
  import org.apache.avalon.excalibur.io.AndFileFilter;
  import org.apache.avalon.excalibur.io.DirectoryFileFilter;
  import org.apache.oro.io.GlobFilenameFilter;
  
  public interface NNTPRepository {
      NNTPGroup getGroup(String groupName);
      NNTPArticle getArticleFromID(String id);
      void createArticle(NNTPLineReader reader);
      Iterator getMatchedGroups(String wildmat);
      Iterator getGroupsSince(Date dt);
      Iterator getArticlesSince(Date dt);
      boolean isReadOnly();
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPRepositoryImpl.java
  
  Index: NNTPRepositoryImpl.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.io.*;
  import java.util.*;
  import org.apache.avalon.framework.activity.Initializable;
  import org.apache.avalon.framework.component.Component;
  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.AbstractLoggable;
  import org.apache.avalon.excalibur.io.AndFileFilter;
  import org.apache.avalon.excalibur.io.DirectoryFileFilter;
  import org.apache.james.nntpserver.DateSinceFileFilter;
  import org.apache.james.nntpserver.NNTPException;
  import org.apache.oro.io.GlobFilenameFilter;
  import org.apache.avalon.phoenix.Block;
  
  public class NNTPRepositoryImpl extends AbstractLoggable 
      implements NNTPRepository, Configurable, Initializable, Block
  {
      private boolean readOnly;
      // the groups are located under this path.
      private File rootPath;
      // articles are temprorily written here and then sent to the spooler.
      private File tempPath;
      private NNTPSpooler spool;
      private ArticleIDRepository articleIDRepo;
      private String[] addGroups = null;
      public void configure( Configuration configuration ) throws ConfigurationException {
          //System.out.println(getClass().getName()+": configure");
          //NNTPUtil.show(configuration,System.out);
          readOnly = configuration.getChild("readOnly").getValueAsBoolean(false);
          rootPath = NNTPUtil.getDirectory(configuration,"rootPath");
          tempPath = NNTPUtil.getDirectory(configuration,"tempPath");
          File articleIDPath = NNTPUtil.getDirectory(configuration,"articleIDPath");
          String articleIDDomainSuffix = configuration.getChild("articleIDDomainSuffix")
              .getValue("foo.bar.sho.boo");
          articleIDRepo = new ArticleIDRepository(articleIDPath,articleIDDomainSuffix);
          spool = (NNTPSpooler)NNTPUtil.createInstance
              (configuration.getChild("spool"),getLogger(),
               "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());
          configuration = configuration.getChild("newsgroups");
          List addGroupsList = new ArrayList();
          if ( configuration != null ) {
              Configuration[] children = configuration.getChildren("newsgroup");
              if ( children != null )
                  for ( int i = 0 ; i < children.length ; i++ )
                      addGroupsList.add(children[i].getValue());
          }
          addGroups = (String[])addGroupsList.toArray(new String[0]);
          getLogger().debug("repository configuration done");
      }
      public void initialize() throws Exception {
          //System.out.println(getClass().getName()+": init");
          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 )
                  groupF.mkdirs();
          }
          if ( tempPath.exists() == false )
              tempPath.mkdirs();
          File articleIDPath = articleIDRepo.getPath();
          if ( articleIDPath.exists() == false )
              articleIDPath.mkdirs();
          if ( spool instanceof Initializable )
              ((Initializable)spool).initialize();
          getLogger().debug("repository initialization done");
      }
      public boolean isReadOnly() {
          return readOnly;
      }
      public NNTPGroup getGroup(String groupName) {
          File f = new File(rootPath,groupName);
          return ( f.exists() && f.isDirectory() ) ? new NNTPGroupImpl(f) : null;
      }
      public NNTPArticle getArticleFromID(String id) {
          try {
              return articleIDRepo.getArticle(this,id);
          } catch(Exception ex) {
              ex.printStackTrace();
              return null;
          }
  //         int idx = id.indexOf('@');
  //         String name = id.substring(0,idx);
  //         String groupname = id.substring(idx+1);
  //         NNTPGroup group = getGroup(groupname);
  //         return ( group == null ) ? null : group.getArticleFromID(name);
      }
      public void createArticle(NNTPLineReader reader) {
          File f = new File(tempPath,System.currentTimeMillis()+"."+Math.random());
          try {
              FileOutputStream fout = new FileOutputStream(f);
              PrintStream prt = new PrintStream(fout,true);
              String line;
              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);
          }
      }
      public Iterator getMatchedGroups(String wildmat) {
          File[] f = rootPath.listFiles(new AndFileFilter
              (new DirectoryFileFilter(),new GlobFilenameFilter(wildmat)));
          return getGroups(f);
      }
      private Iterator getGroups(File[] f) {
          List list = new ArrayList();
          for ( int i = 0 ; i < f.length ; i++ )
              list.add(new NNTPGroupImpl(f[i]));
          return list.iterator();
      }
      public Iterator getGroupsSince(Date dt) {
          File[] f = rootPath.listFiles(new AndFileFilter
              (new DirectoryFileFilter(),new DateSinceFileFilter(dt.getTime())));
          return getGroups(f);
      }
  
      // 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
      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
                              return false;
                      }
                      if ( iter.hasNext() )
                          return true;
                      else {
                          iter = null;
                          return hasNext();
                      }
                  }
                  public Object next() {
                      return iter.next();
                  }
                  public void remove() {
                      throw new UnsupportedOperationException("remove not supported");
                  }
              };
      }
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPSpooler.java
  
  Index: NNTPSpooler.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.io.*;
  import java.util.*;
  import javax.mail.internet.MimeMessage;
  import org.apache.avalon.framework.activity.Initializable;
  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.AbstractLoggable;
  import org.apache.avalon.framework.logger.Loggable;
  import org.apache.avalon.excalibur.io.IOUtil;
  import org.apache.james.util.Lock;
  
  // processes entries and sends to appropriate groups.
  // eats up inappropriate entries.
  class NNTPSpooler extends AbstractLoggable implements Configurable, Initializable {
      private Worker[] worker;
      private File spoolPath;
      public void configure( Configuration configuration ) throws ConfigurationException {
          //System.out.println(getClass().getName()+": configure");
          //NNTPUtil.show(configuration,System.out);
          spoolPath = NNTPUtil.getDirectory(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];
          for ( int i = 0 ; i < worker.length ; i++ ) {
              worker[i] = new Worker(threadIdleTime,spoolPath);
              if ( worker[i] instanceof Loggable )
                  ((Loggable)worker[i]).setLogger(getLogger());
          }
      }
      void setRepository(NNTPRepository repo) {
          for ( int i = 0 ; i < worker.length ; i++ )
              worker[i].setRepository(repo);
      }
      void setArticleIDRepository(ArticleIDRepository articleIDRepo) {
          for ( int i = 0 ; i < worker.length ; i++ )
              worker[i].setArticleIDRepository(articleIDRepo);
      }
      File getSpoolPath() {
          if ( spoolPath.exists() == false )
              spoolPath.mkdirs();
          return spoolPath;
      }
      public void initialize() throws Exception {
          //System.out.println(getClass().getName()+": init");
          for ( int i = 0 ; i < worker.length ; i++ )
              new Thread(worker[i],"NNTPSpool-"+i).start();
      }
      static class Worker extends AbstractLoggable implements Runnable {
          private static final Lock lock = new Lock();
          private final File spoolPath;
          private final int threadIdleTime;
          private ArticleIDRepository articleIDRepo;
          private NNTPRepository repo;
          Worker(int threadIdleTime,File spoolPath) {
              this.threadIdleTime = threadIdleTime;
              this.spoolPath = spoolPath;
          }
          void setArticleIDRepository(ArticleIDRepository articleIDRepo) {
              this.articleIDRepo = articleIDRepo;
          }
          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
          public void run() {
              getLogger().debug("in spool thread");
              while ( Thread.currentThread().isInterrupted() == false ) {
                  String[] list = spoolPath.list();
                  for ( int i = 0 ; i < list.length ; i++ ) {
                      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());
                          try {
                              process(f);
                          } catch(Exception ex) {
                              getLogger().debug("exception occured in processing file: "+
                                                f.getAbsolutePath(),ex);
                          } finally {
                              lock.unlock(list[i]);
                          }
                      }
                  }
                  // this is good for other non idle threads
                  try {  Thread.currentThread().sleep(threadIdleTime);
                  } catch(InterruptedException ex) {  }
              }
          }
          private void process(File f) throws Exception {
              getLogger().debug("process: "+f.getAbsolutePath()+","+f.getCanonicalPath());
              final MimeMessage msg;
              String articleID;
              {   // get the message for copying to destination groups.
                  FileInputStream fin = new FileInputStream(f);
                  msg = new MimeMessage(null,fin);
                  fin.close();
  
                  // ensure no duplicates exist.
                  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);
                      f.delete();
                      return;
                  }
                  if ( articleID == null ) {
                      articleID = articleIDRepo.generateArticleID();
                      msg.setHeader("Message-Id", articleID);
                      FileOutputStream fout = new FileOutputStream(f);
                      msg.writeTo(fout);
                      fout.close();
                  }
              }
  
              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]);
                  NNTPGroup group = repo.getGroup(headers[i]);
                  if ( group == null ) {
                      getLogger().debug("group not found: "+headers[i]);
                      continue;
                  }
                  int artNum = group.getLastArticleNumber();
                  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())
                          break;
                  }
                  getLogger().debug("copying message to: "+articleFile.getAbsolutePath());
                  prop.setProperty(group.getName(),articleFile.getName());
                  FileInputStream fin = new FileInputStream(f);
                  FileOutputStream fout = new FileOutputStream(articleFile);
                  IOUtil.copy(fin,fout);
                  fin.close();
                  fout.close();
              }
              articleIDRepo.addArticle(articleID,prop);
              boolean delSuccess = f.delete();
              if ( delSuccess == false )
                  getLogger().error("could not delete file: "+f.getAbsolutePath());
          }
      } // class Worker
  }
  
  
  
  1.1                  jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPUtil.java
  
  Index: NNTPUtil.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  package org.apache.james.nntpserver.repository;
  
  import java.io.*;
  import org.apache.avalon.framework.activity.Initializable;
  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.Loggable;
  import org.apache.james.nntpserver.NNTPException;
  import org.apache.log.Logger;
  
  // processes entries and sends to appropriate groups.
  // eats up inappropriate entries.
  public class NNTPUtil {
      static File getDirectory(Configuration configuration,String child)
          throws ConfigurationException
      {
          String str = configuration.getChild(child).getValue();
          if ( str.toLowerCase().startsWith("file://") )
              str = str.substring("file://".length());
          File f = new File(str);
          if ( f.exists() && f.isFile() )
              throw new NNTPException("Expecting '"+f.getAbsolutePath()+"' directory");
          if ( f.exists() == false )
              f.mkdirs();
          return f;
      }
      public static Object createInstance(Configuration configuration,Logger logger,
                                          String clsName) throws ConfigurationException
      {
          try { clsName = configuration.getAttribute("class");
          } catch(ConfigurationException ce) { }
          try {
              Object obj = Class.forName(clsName).newInstance();
              if ( obj instanceof Loggable )
                  ((Loggable)obj).setLogger( logger );
              if ( obj instanceof Configurable )
                  ((Configurable)obj).configure(configuration.getChild("configuration"));
              return obj;
          } 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++ )
              prt.println(i+". "+children[i].getName());
      }
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: james-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: james-dev-help@jakarta.apache.org