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>