• No se han encontrado resultados

There are two JSP pages used to display the NNTP client information. The code for the group list, nntp_groups.jsp (shown rendered in the browser in Figure 4-4), can be found in Listing 4-3.

Listing 4-3 NNTP Group List JSP

<%@ page contentType="text/html; charset=iso-8859-1" language="java" import="com.cascadetg.ch04.*" %> <%

String newsgroup_pattern = "comp.lang.java.*";

NNTPConnection myNNTPConnection = new NNTPConnection();

if(request.getParameter("refresh") != null)

myNNTPConnection.refreshNewsgroupList(newsgroup_pattern);

org.apache.commons.net.nntp.NewsgroupInfo[] newsgroups = myNNTPConnection.getNewsgroupList(newsgroup_pattern); %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml">

<head>

<title>Java Newsgroups</title>

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <link href="../ch03/default.css" rel="stylesheet" type="text/css" /> </head>

<body>

56 Net Chapter 4

Figure 4-5 Viewing a NNTP post.

Listing 4-3 (continued)

<p><strong><%= newsgroup_pattern %></strong></p><hr />

<table width="100%" border="0" cellspacing="3" cellpadding="3"> <tr> <td><strong>Newsgroup</strong></td> <td><strong>Articles</strong></td> <td>&nbsp;</td> </tr> <%

for(int i = 0; i < newsgroups.length; i++) { %> <tr> <td><%= newsgroups[i].getNewsgroup() %></td> <td><%= newsgroups[i].getArticleCount() %></td> <td><a href="nntp_message.jsp?newsgroup=<%= newsgroups[i].getNewsgroup() %>&article=<%= newsgroups[i].getLastArticle()

%>">Most Recent Article</a></td> </tr>

<% } %>

</table><hr />

<form name="form1" id="form1" method="post" action=""> <input name="refresh" type="submit"

id="refresh" value="Refresh Newsgroup List" /> </form>

</body> </html>

The code in Listing 4-3 relies on the underlying org.apache.commons.net. nntp.NewsgroupInfo class for the information about the groups. Figure 4-6 shows the methods of this class.

NNTP Implementation 57

Clicking on the link for the Most Recent Article displays the nntp_ message.jsppage. The listing for nntp_message.jspis shown in Listing 4-4. Listing 4-4 NNTP Message Display JSP

<%@ page contentType="text/html; charset=iso-8859-1" language="java" import="com.cascadetg.ch04.*" %> <%

NNTPConnection myNNTPConnection = new NNTPConnection(); String newsgroup = request.getParameter("newsgroup"); String article = request.getParameter("article");

String header = myNNTPConnection.getArticleHeader(newsgroup, article); String body = myNNTPConnection.getArticleBody(newsgroup, article); String previousArticle = myNNTPConnection.previousArticle(newsgroup, article);

String nextArticle = myNNTPConnection.nextArticle(newsgroup, article); %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml">

<head>

<title><%= newsgroup %> <%= article %></title>

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <link href="../ch03/default.css" rel="stylesheet" type="text/css" /> <style type="text/css">

<!-- .header {

background-color: #EEEEEE; border: thin solid #CCCCCC; }

.body {

font-family: "Courier New", Courier, mono; font-size: small; } --> </style> </head> <body>

<table width="100%" border="0" cellspacing="3" cellpadding="3"> <tr>

<td><strong><%= newsgroup %> #<%= article %></strong></td> </tr>

</table> <hr />

<table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr>

<td width="33%"><%

if(previousArticle != null) { %>

<a href="nntp_message.jsp?newsgroup=<%=newsgroup%>&article=<%= previousArticle %>">&lt;- Previous</a>

<% } %>&nbsp;</td> <td align="center"><a href="nntp_groups.jsp"><strong>Return To Group List</strong></a></td> <td width="33%" align="right"><% if(nextArticle != null) { %> <a href="nntp_message.jsp?newsgroup=<%=newsgroup%>&article=<%= nextArticle %>">Next -&gt;</a>

58 Net Chapter 4

NNTP Implementation 59

Figure 4-7 NNTP Connection example classes. Listing 4-4 (continued)

<% } %>&nbsp;</td> </tr>

</table> <hr />

<table width="100%" border="0" cellpadding="3" cellspacing="3" class="header"> <tr> <td class="header"><%= header %></td> </tr> </table> <br /> <p class="body"> <%= body %><br /> </p> </body> </html>

Now that you’ve seen the JSP presentation code, let’s look at the underly- ing Java code. A diagram is shown in Figure 4-7.

Looking at the NNTPConnection code shown in Listing 4-5, you’ll notice that several java.util.Hashtable objects are used to cache the content retrieved. As NNTP is typically read-and-post, but messages are rarely deleted, this is a reasonable approach. Other NNTP clients cache data on disk or in a database.

You’ll also notice the liberal use of the synchronizedkeyword to avoid con- tention over the use of the NNTPClient object—it’s important for certain com- mands to be issued in sequence. For example, the NNTPConnection.nextArticle()

method issues a client.selectNewsgroup() call and then immediately issues client.selectNextArticle()and client.selectArticle(). If these calls were to be intermixed with other connections, the wrong data might be returned.

Listing 4-5 NNTP Connection Example package com.cascadetg.ch04;

import org.apache.commons.net.nntp.*;

// http://nagoya.apache.org/bugzilla/show_bug.cgi?id=26282

public class NNTPConnection {

/**

* This class attempts to improve performance over the * FTPConnection class by using a single static client to * retrieve data. It caches some of the data in-memory. You'll * notice that this cache code adds significant complexity, * however.

*/

static org.apache.commons.net.nntp.NNTPClient client = new org.apache.commons.net.nntp.NNTPClient();

/** These serve as our in-memory cache of retrived data */ static NewsgroupInfo[] newsgroups = null;

static java.util.Hashtable article_bodies = new java.util.Hashtable();

static java.util.Hashtable article_headers = new java.util.Hashtable();

static java.util.Hashtable article_pointers = new java.util.Hashtable();

/** Gets the latest newsgroup list, ignoring the cache */ public void refreshNewsgroupList(String newsgroup_pattern) { checkConnection(); try { synchronized (client) { newsgroups = client.listNewsgroups(newsgroup_pattern); }

} catch (Exception e) { e.printStackTrace(); } }

/** Gets the latest articleID, given a newsgroup name */ public int getRecentArticleID(String newsgroup)

{

for (int i = 0; i < newsgroups.length; i++) { if (newsgroups[i].getNewsgroup().compareTo(newsgroup) == 0) return newsgroups[i].getLastArticle(); } return -1; } 60 Net Chapter 4 (continues)

Listing 4-5 (continued)

/** Gets the list of newsgroups, from the cache if possible */ public NewsgroupInfo[] getNewsgroupList(String newsgroup_list) {

if (newsgroups == null)

refreshNewsgroupList(newsgroup_list);

return newsgroups; }

/** Gets an article pointer given a newsgroup and articleID. */ ArticlePointer getArticlePointer( String newsgroup, String articleID) { if (article_pointers.containsKey(newsgroup + articleID)) return (ArticlePointer)article_pointers.get( newsgroup + articleID); checkConnection();

ArticlePointer newPointer = new ArticlePointer();

String result = ""; try { synchronized (client) { client.selectNewsgroup(newsgroup); client.selectArticle(articleID); getAsString( client.retrieveArticleBody( articleID, newPointer), false); }

article_pointers.put(newsgroup + articleID, newPointer); return newPointer;

} catch (Exception e) {

result = "Unable to retrive."; e.printStackTrace();

}

return null; }

/**

* Gets the next article given a current article. Note that * this method relies on the server to provide the previous and * next article information.

*/

public String nextArticle(String newsgroup, String articleID) {

if (articleID == null) return null;

ArticlePointer myPointer = new ArticlePointer();

checkConnection(); try

NNTP Implementation 61

Listing 4-5 (continued) { synchronized (client) { client.selectNewsgroup(newsgroup); client.selectArticle(Integer.parseInt(articleID)); if (!client.selectNextArticle(myPointer)) return null; client.selectArticle( myPointer.articleNumber, myPointer); } } catch (Exception e) { e.printStackTrace(); } return myPointer.articleNumber + ""; }

/** See nextArticle for more information. */

public String previousArticle( String newsgroup,

String articleID) {

if (articleID == null) return null;

ArticlePointer myPointer = new ArticlePointer();

checkConnection(); try { synchronized (client) { client.selectNewsgroup(newsgroup); client.selectArticle(Integer.parseInt(articleID)); if (!client.selectPreviousArticle(myPointer)) return null; client.selectArticle( myPointer.articleNumber, myPointer); } } catch (Exception e) { e.printStackTrace(); } return myPointer.articleNumber + ""; }

/** Gets the body of an article, preferably from the cache */ public String getArticleBody(

String newsgroup, String articleID) { if (article_bodies.containsKey(newsgroup + articleID)) return (String)article_bodies.get( newsgroup + articleID); checkConnection(); String result = ""; try 62 Net Chapter 4 (continues)

Listing 4-5 (continued) { synchronized (client) { client.selectNewsgroup(newsgroup); client.selectArticle(articleID); result = getAsString( client.retrieveArticleBody(articleID), true); }

article_bodies.put(newsgroup + articleID, result);

} catch (Exception e) {

result = "Unable to retrive."; e.printStackTrace();

}

return result;

}

/** Gets the header of an article, preferably from the cache */ public String getArticleHeader(

String newsgroup, String articleID) { if (article_headers.containsKey(newsgroup + articleID)) return (String)article_headers.get( newsgroup + articleID); String result = ""; try { checkConnection(); synchronized (client) { client.selectNewsgroup(newsgroup); client.selectArticle(articleID); result = getAsString( client.retrieveArticleHeader(articleID), false); } java.util.StringTokenizer myTokenizer =

new java.util.StringTokenizer(result, "\n", true);

result = "";

while (myTokenizer.hasMoreTokens()) {

String current = myTokenizer.nextToken(); current =

Utilities.replaceToken(current, "<", "&lt;"); current =

Utilities.replaceToken(current, ">", "&gt;");

if (current.startsWith("From:"))

result = result + current + "<br />";

NNTP Implementation 63

Listing 4-5 (continued)

if (current.startsWith("Subject:")) result = result + current + "<br />";

if (current.startsWith("Date:"))

result = result + current + "<br />"; }

article_headers.put(newsgroup + articleID, result);

} catch (Exception e) {

result = "Unable to retrive."; e.printStackTrace();

}

return result; }

/** The various body & header methods return Readers, not * Strings. This utiltiy method converts from a Reader to a * String, and also performs some basic HTML formatting if * requested. */

public String getAsString(java.io.Reader in, boolean HTML) {

if (in == null) return "";

java.io.BufferedReader bufferedReader = new java.io.BufferedReader(in);

StringBuffer temp = new StringBuffer(); boolean read = true;

String current = ""; while (read) { try { current = bufferedReader.readLine(); } catch (Exception e) { read = false; } if (current != null) { current = current + "\n"; if (HTML) { current = Utilities.replaceToken( current, "<", "&lt;"); current = Utilities.replaceToken( current, ">", "&gt;"); temp.append(current); temp.append("<br />"); } else { temp.append(current); } } else read = false;

}

return temp.toString(); }

/** Verifies that the NNTP connection is valid, and attempts to * reestablish if not. */

64 Net Chapter 4

Listing 4-5 (continued)

protected synchronized void checkConnection() { if (client.isConnected()) { synchronized (client) { try { client.stat(); return; } catch (Exception e) {

// Ok, failed, so let's try to reconnect. try

{

client.disconnect(); } catch (Exception e1) { // No need to report this } } } } try { synchronized (client) { client.connect(NetConnectionTokens.nntp_server); client.getReplyCode(); client.authenticate( NetConnectionTokens.nntp_username, NetConnectionTokens.nntp_password); } } catch (Exception e) {

System.err.println("Unable to connect to NNTP server!"); e.printStackTrace();

} }

public synchronized void close() { try { synchronized (client) { client.logout(); } } catch (Exception e) { // Silent failure. } try { synchronized (client) { client.disconnect(); }

} catch (Exception e) { // Silent failure. }

} }

In a "real" NNTP client application, one can think of all sorts of additional features, such as preloading and sorting the headers on another thread, per- haps providing a threaded view of the messages, and interweaving the replies (for more information on NNTP message threading, see http://nagoya. apache.org/bugzilla/show_bug.cgi?id=26282).

SUMMARY

In this chapter, two approaches for dealing with the Jakarta Commons libraries were presented. It’s easy to envision much more sophisticated approaches, with complex clients performing specialized operations. For exam- ple, an FTP client could synchronize between a remote server and a local file system, or an NNTP client might generate automated status posts.

As you can see, resource management quickly becomes important when working with these protocols. A connection to a service is an important resource, as is the data that is transmitted. In the next chapter, a library that provides additional scalability and availability for database connectivity through the use of a database connection pool will be examined.

Project Ideas

Use Net components in conjunction with the FileUpload package (described in Chapter 2) to allow users to post files to an FTP server via a browser.

Build web browser interfaces to the various protocols. Some of these (such as Telnet) imply state to be presented to the user; others (such as Finger) do not. How does this affect building a web interface?

Try using TFTP and ZeroConf (also known as Rendezvous) to build a simple file local network sharing application.

Tip: If you are interested in setting up your own NNTP server, you may want to investigate James,http://james.apache.org/, a Java-based SMTP, POP3, and NNTP server.

C H A P T E R

5

Pool

67 Not all Java objects are created equal. For example, some take considerably longer to create than others—an object that establishes a secure network con- nection can take several seconds to properly initialize. Other objects might consume significant resources. In order to minimize the creation of these objects, you may want to maintain a pool of objects—a little bit of bookkeeping to keep track of a group of objects, checked out and returned as needed. The Apache Jakarta Commons Pool package provides interfaces and implementa- tion to make that task much easier.

Although some people might think that an object pool is easy to write, by using the Pool package, a wide suite of built-in, well-tested behaviors is pro- vided for free. For example, you can use the Commons package to create a pool that will only allocate a maximum of ten objects and then block on future allo- cations until an object is returned or create up to fifty objects, with the pool automatically shrinking when the garbage collector needs the memory. By implementing a single interface and using the right combination of Apache Pool interfaces and implementations, you can configure a wide suite of pools, suitable for a broad range of uses.

It’s worth noting that one of the most popular uses of a pool is to manage database connectivity. This is popular enough to warrant a specific Commons package, DBCP, which is based on the Pool suite described in this chapter. DBCP is described in the next chapter. In addition, Java 5.0 (also known as JDK 1.5) includes built-in support for ThreadPools and other useful opera-

tions, as described athttp://java.sun.com/j2se/1.5.0/docs/guide/concurrency/

index.html.

Avoid Preoptimization

Maintaining an object pool simply to minimize the overhead of JVM-level object creation is generally not worth the bother. Modern JVM implemen- tations are quite good at optimizing object creation. If the objects are expensive to create, however, it may be worth creating a pool.

Figure 5-1 Generic pool c lass hierarc hy . y t t l t y 68

Thread Pool Example 69