by Bear Bibeault
"How do I call a bean method from a Javascript onclick handler?" "When I write to a file in my JSP, why does it appear on the server? I want to write to the client system!" "Why does everyone tell me to use a servlet to send binary data to the browser? I wanna use a JSP!" To those that frequent the JSP Forum of the JavaRanch Big Moose Saloon, these questions may appear hauntingly familiar as they are among some of the most recurring questions asked by new-comers to the Ranch and to JSP. While these questions may seem somewhat unrelated to each other, they all have a similar root: a fundamental misconception of what JavaServer Pages (JSP) technology is, and of the life-cycle of a JSP page. In this article, I hope to clarify what JSP is, how JSP pages work, and how understanding their life-cycle clears up the answers to questions such as those above, and many more. What's it all about, Jasper?One thing that JSP technology is not is a means to cause Java code to be executed as part of a web page on the client-side browser. That's what Applets are for. It is also not a means to deploy stand-alone Java applications to a client system for local execution; that's the job of Java Web Start. So what is JSP Technology? Stripped down to its very core: JSP is a templating technology used to dynamically generate content for delivery to a client, usually a browser. <cricket> <cricket> <cricket> ... Hello? Ohhhh-kay... let's try that again... The Two County ClerksA traditional web server actually has a pretty simple job. When a request (in the guise of an HTTP request) is made for a particular resource (most often an HTML page or image file), the web server responds (in the guise of an HTTP response) by reading the bytes of the requested resource and feeding them across the net to the requesting browser. Whatever is in the file is what gets delivered. A templating engine on the other hand, uses the information in the requested resource, combined with data that it can garner from various resources, to dynamically generate the content to be delivered. To illustrate this difference, let's imagine that you need to visit your local county tax office in order to straighten out what you think may be an error on your yearly property tax bill. In order to research the issue, you determine that you need two documents:
So you arrive at the Tax Office and determine which clerk to go to in order to obtain the County Tax Rules Document. You queue up into line, and when it comes your turn, you request a copy of the County Tax Rules Document. The clerk responds by turning to his file cabinet stuffed with documents, retrieves one of those documents, places it on his copy machine, and hands you the results of the copy operation. You walk away with a static document in your hands. The document is an identical copy of the document that was in the clerk's file cabinet with no interpretation. If you were to go back and ask for the document again, you would receive a copy exactly like the one that you just obtained. Anyone else who requests a copy of the document also receives a copy that is identical to the one that you have in your hands. This first clerk operates in much the same manner as a traditional web server. Its primary job is to send, verbatim, the contents of a specific file on the server to the client upon request. To obtain the second document — the Property Assessment document for your property — you visit another clerk. This clerk is a more capable worker, able to handle the complexities of creating a document for you that is more than a simple copy of an existing document; one that is customized with information specific to you (or as in this case, your property). When your turn comes, you request a copy of a Property Assessment document for your property. Unlike, the previous document that you requested, this document requires that you also provide a means of uniquely identifying the property for which the document is to contain the assessment information. In this case, the uniquely identifying piece of information is the address of the property. In response to your request, the clerk fetches a document from her file cabinet — much in the same manner that the first clerk did — but does not hand a copy of it directly to you. Rather, she also turns to another file cabinet and looks up the information specific to your property and lays it out on her desk. She then makes a copy of the Property Assessment document that she fetched, and places it into her typewriter. As she does so you see that this is a document that is only partially filled in. It has lots of writing already on it — labels, lines, and other markings — but also contains lots of blanks where property-specific information should be. With deft fingers, and using the property information she retrieved for your specific property, she fills in those blanks with the appropriate data. For some of this information she turns to her calculator to compute the appropriate values to fill in using the information available to her. When she is finished, she pulls the document from her typewriter and hands it to you. You walk away with a dynamic document in your hands. This document differs from your first, static document in that it is tailored with information that is specific to your property. If you, or anyone else, were to request the exact same document, but supplied a different property address, the document's content would be different; tailored to the information appropriate for the property identified by the supplied address. This second clerk operates in a similar fashion to the way that a templating engine such as a JSP container would operate. She generated a document tailored with specific data by starting with a source template and filling in information specific to a particular element in her "database". Similarly, a JSP container will take a source template, a JSP page, and using JSP markup within that page generate a dynamic document tailored with specific information. So why would we want to use such templating technology when traditional web serving is so much more simple and straight-forward? Well, let's revisit the Tax Office... A frugal, but uninsightful, taxpayer might argue that the County is wasting tax dollars by operating in this fashion. After all, would it not be cheaper, not to mention faster, just to have a low-level clerk simply look up and hand you a Property Assessment document already filled with your property's information? Could they not save salary dollars by hiring less-capable clerks who only have to know how to use a copy machine rather than a much more complicated typewriter? Of course, the answer is no. If the Tax Office were to attempt to operate in that fashion, then they would need to have a pre-filled Property Assessment document for each and every property in the county. And what about other documents which are also property-specific? Not only would the Tax Office need to hire a whole gaggle of clerks to create those documents in the first place, they would also need to store those documents causing them to incur rental costs for the enormous amount of space that would be necessary. And what about when the information on the various properties changes? Each change would mean throwing away all of the pre-created documents and typing them in all over again. Expensive, inefficient and wasteful! So with even a cursory examination, we see that the Tax Office is making good use of our tax dollars by storing one template for each document type, and filling that template in with up-to-date information at the time that the document is requested. This is why technologies such as JSP make so much sense. By creating dynamic documents on-the-fly with real-time data, there is no need to anticipate and store every possible document variation that might ever be requested, and the information in those documents can always be kept up-to-date by simply keeping a single instance of the data current. Anatomy of a JSPLike the fill-in-the-blanks form of our plucky county clerk, a JSP page is a template for creating the end document. The template contains both static and dynamic elements. The static elements are copied verbatim to become part of the document, while the dynamic elements are interpreted at request time in order to "fill in the blanks" with appropriate data. Let's take a look at a simple JSP page, with highlighting to point out the various types of elements: 1<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 3<html> 4 <head> 5 <title>A simple JSP page</title> 6 </head> 7 8 <body> 9 Testing 10 <c:forEach begin="1" end="3" var="n"> 11 ${n} 12 </c:forEach> 13 ! 14 </body> 15</html> The background colors in this code example are keyed as follows:
1 This article is most emphatically not meant to be a tutorial on JSP and all its capabilities. That would take a very long article indeed. In fact, that's why entire books are devoted to this subject. The facets of JSP discussed within this article extend to only those needed to illustrate the concepts being addressed. Looking at this example we see that, except for dynamic elements on lines 1, 10, 11 and 12, that the page consists mostly of template text. This is typical of many JSP pages. All of that template text will be sent, verbatim, to the final document (line breaks included), while the dynamic elements will be interpreted. On line 1, we see a dynamic element that is an example of a JSP directive. This particular directive instructs the JSP engine to find a custom action library2 using the specified URI, and to load it for the scope of the page using the XML namespace of c. This element produces no output to the final document. 2 You may be more accustomed to the term tag rather than action in this context, but as of the JSP 2.0 specification, what was formerly known as standard and custom tags are now termed the standard and custom actions. A perhaps unfortunate naming collision as many web application frameworks, such as Struts, use the term "action" to mean a unit of execution.
On lines 10 and 12 we see a custom action from that library in use.
The library in this example is the JSTL
set of core actions, and the action
employed is the On line 11 we see another dynamic element, in this case an Expression Language (EL) expression which causes its evaluated value to be emitted to the final document. Visiting this page (when hosted on an appropriate JSP 2.0 server) results in the following simple display:
While that's not very impressive or even interesting, what is interesting is examining the final document as sent to the browser. Viewing the source of the response document shows us the specific text of the final document as it was sent to the browser. (This, by the way, is the first debugging step you should take when a JSP is not producing the output you expected when displayed in a browser. Examining the rendered HTML document can usually quickly tell you whether the problem lies in your HTML formatting or elsewhere). Performing a 'View Source' in the browser shows us: 1 2 3<html> 4 <head> 5 <title>A simple JSP page</title> 6 </head> 7 8 <body> 9 Testing 10 11 1 12 13 2 14 15 3 16 17 ! 18 </body> 19</html> There are a number of intriguing details to note about this final document. Firstly we can see that all of the template text has been dutifully reproduced in this final document. We can also see that none of the dynamic elements remain. Those elements were interpreted on the server and were not written to the final document. And we can see that the EL expression element from line 11 of our JSP template caused dynamic data, in this case the numerals 1, 2 and 3, to be placed into the final document. Also interesting to note is that all of the template text, including the line terminators, has made its way into the final document (resulting in a lot of the "blank lines" that are seen in that document). This is one of the reasons that JSP is a rather unsuitable technology for delivering anything but text content to the browser. JSP technology was designed as a templating mechanism for text content, not binary content. Its underlying mechanisms assume that the document begin created is text, and details such as the dutiful emission of all template text including leading and trailing spaces, as well as line terminators makes JSP a problematic technology for delivering content whose structure would be corrupted by the addition of these template characters. Moreover, there are even types of text documents that are not well-suited to JSP. RTF (Rich Text Format) is a good example. RTF is very sensitive to the placement of line terminators and white-space characters. Put one in the wrong place, and "poof!", your formatting goes right out the window! Therefore, JSP's suitability is not only limited to the generation of text documents, but to text documents of a format that is at least fairly insensitive to the placement of white-space (including line terminators). This makes JSP best-suited for documents in HTML and XML formats, where interstitial white-space is relatively meaningless. OK, that's all well and good. But how did all that magic happen "under the hood"? The next section takes us through a day in the life of a JSP page. Assuming that by "day", we mean a request-response cycle. The Life and Times of a JSPAs we have seen, a JSP page is merely a text file containing elements, both static and dynamic, that define a template for a document. When a request for that document is made, the JSP engine interprets the JSP template and formats an HTML document that gets sent to the browser. How does it do that? In a nutshell, the JSP Engine turns your JSP template into a servlet. Whenever you request the document represented by the JSP, the engine runs that generated servlet in order to create the HTML document to be sent to the browser. But let's take a little bit of a closer look at the steps that need to be taken in order to make that happen. Let's assume that we have just written the simple sample JSP page from the previous section. We place it on the server in a place accessible to the JSP engine (web application setup is beyond the scope of this article; see the documentation for your JSP container), and enter the appropriate URL that references the JSP page. The JSP Engine receives the request for the JSP page and first checks to see if the JSP has been referenced before. If not, it needs to pass the JSP through a translation phase. This is the phase in which the text of your JSP page is parsed, and a servlet is generated on behalf of the JSP. Most containers will actually generate a text .java file and place it in the file system of the server. This java file is compiled and the resulting servlet class, also usually written to disk, is loaded by the engine's class loader and is registered as a servlet in the enclosing web application. The first time that your JSP is requested, obviously the translation phase will not have occurred, and so the translation phase will be triggered. However, the translation phase will also be automatically triggered if the JSP has been changed since the last time it was translated. In either case, the result is a servlet class, loaded and ready to run in the execution phase. In the execution phase, the servlet is executed by calling its service method which results in executing the code that the JSP engine generated on behalf of your JSP. This results in "writing" an HTML document which is then sent to the browser. When next the JSP is requested, the JSP engine merely needs to execute the JSP's servlet for each request (assuming that the JSP has not been changed in the meantime). The following diagram shows a simplified overview of this process: Armed with this knowledge, let's go back and re-examine the questions posed at the beginning of this article. The answers should now be readily apparent and understandable. "How do I call a bean method from a Javascript onclick handler?" Obviously, you can't. As we have seen, the JSP executes on the server (or rather, the JSP's servlet executes) and formats an HTML document that is sent to the browser. The browser, which then interprets the HTML and any Javascript in the document (which, from the point of view of the JSP engine is just template text along with the rest of the HTML markup) is completely unaware that the document it has received was generated via a dynamic mechanism. It's as if the second clerk of our Tax Office analogy operated behind an opaque screen. You asked for a document, and you got handed one that contains the information you requested. Whether the clerk merely made a copy of a static document, used her typewriter to fill in the blanks (putting aside visual differences that could clue you in), or pulled the document out of a magic top hat, is a mystery to you. All you know is that you have the document you requested, and any further interaction with the clerk would have to be in the form of another document request. "When I write to a file in my JSP, why does it appear on the server? I want to write to the client system!" This is an easy one. We know that the servlet generated on behalf of the JSP executes on the server. So any file operations performed by that servlet will take place on the server's file system. "Why does everyone tell me to use a servlet to send binary data to the browser? I wanna use a JSP!" Since we know that the JSP engine expects to be generating text data (the underlying input/output mechanisms are text-oriented) and that all template text in a JSP including white-space is emitted as part of the document, we can see that using a JSP to emit binary data (or even strictly formatted text data) whose structure would be corrupted by such extraneous characters is a rather foolish thing to try to do. A JSP Exposed!Before we close, let's take some of the clothes off of our sample JSP and take a small peek at its innards to help illustrate some of the previous points. The following code sample is the portion of the servlet generated on behalf of our sample JSP that emits the majority of the output for the document: (examining the entire servlet source code is beyond the scope of this article and would be very container-specific — the code sample shown here was generated by Tomcat 5.0.28) out = pageContext.getOut(); _jspx_out = out; out.write("\r\n"); out.write("\r\n"); out.write("<html>\r\n"); out.write(" <head>\r\n"); out.write(" <title>A simple JSP page</title>\r\n"); out.write(" </head>\r\n"); out.write("\r\n"); out.write(" <body>\r\n"); out.write(" Testing \r\n"); out.write(" "); if (_jspx_meth_c_forEach_0(_jspx_page_context)) return; out.write("\r\n"); out.write(" !\r\n"); out.write(" </body>\r\n"); out.write("</html>\r\n"); out.write("\r\n"); Note how the template text ends up begin defined within string literals, and that all of the template text, line terminators and all, is dutifully emitted. SummarySo what have we learned? Hopefully, at least that:
Peace out. Return to Top |
|||||||||||||||||||||
by Jeanne Boyarsky In networking, one of the most expensive things you can do is make a server roundtrip. In JDBC, this corresponds to a database call. If you are doing inserts, updates or deletes, you can use the executeBatch() signature to cut down the number of trips to the server. Unfortunately, there is no such built-in mechanism for select queries. Suppose you want to get the names for a given list of ids. Logically, we would want to do something like: PreparedStatement stmt = conn.prepareStatement( "select id, name from users where id in (?)"); stmt.setString("1,2,3"); However, this will not work. JDBC only allows you to substitute a single literal value in the "?". You can't substitute things that are meant to become part of the SQL statement. This is necessary because if the SQL itself can change, the driver can't precompile the statement. It also has the nice side effect of preventing SQL injection attacks. Instead, you have four options for the implementation:
Option 1: Run the query separately for each idIf you have 100 ids, this option results in 100 database calls. PreparedStatement stmt = conn.prepareStatement( "select id, name from users where id = ?"); for ( int i=0; i < 3; i++ ) { stmt.setInt(i); // or whatever values you are trying to query by // execute statement and get result } This option is the easiest to code, but results in the slowest performance. Option 2: Build one query to do everythingAt runtime, you use a loop to build a statement like this: PreparedStatement stmt = conn.prepareStatement( "select id, name from users where id in (?, ?, ?)"); stmt.setInt(1); stmt.setInt(2); stmt.setInt(3); This solution is next easiest to code (compared to running the query separately) and solves the server roundtrip problem. Unfortunately, the prepared statement must be recompiled each time you call it with a different number of parameters. This is due to the fact that the string literal of the SQL is matched against. So if I call it with ten ids, then three ids, then 100 ids, there will be three prepared statements in the cache. Aside from the added time from recompiling the prepared statement, other queries may be removed from the pool of available statements causing them to be recompiled again too. Finally, this solution can take longer for very large queries if memory is exceeded and paging to disk occurs. Another variant of this approach is to hard code the values in the statement: PreparedStatement stmt = conn.prepareStatement( "select id, name from users where id in (1, 2, 3)"); This approach is even worse as there is almost no chance of the statement being reused. At least with the "?", the statement will be reused for queries with same number of parameters. Still another variant of this approach is to send multiple statements: PreparedStatement stmt = conn.prepareStatement( "select id, name from users where id in (?) ; " + "select id, name from users where id in (?); " + "select id, name from users where id in (?)"); stmt.setInt(1); stmt.setInt(2); stmt.setInt(3); This approach has the advantage of the database seeing the statement as being the same each time. The database will not need to figure out the execution path each time. However, the SQL is different each time from the driver's point of view and the prepared statement must still be prepared and stored in the cache each time. Also, note that not all database support sending multiple statements separated with semicolons. Option 3: Use a stored procedureA stored procedure executes on the database server. Therefore, it can make many queries without any network overhead. The stored procedure can collect all the results and return them in one trip. This has the advantage of being the fastest solution. However, it ties you to one database and requires you to split logic between the application server and database server. If your architecture already uses stored procedures, this is the typically the best solution. Option 4: Select batchingSelect batching is a compromise between options one and two. The ideas is to predetermine a few numbers of parameters that you want to run in each query. Then you build a bunch of queries with those numbers of parameters. Since there are a finite number of queries involved, you get the advantages of prepared statements (pre-compiling and not bumping other prepared statements from the cache.) The batching of multiple values in the same query retains the benefit of minimizing the number of server roundtrips. Finally, you avoid the memory problems of large queries by controlling the upper limit of the batch sizes. This option is the best approach if you have a critical query in terms of performance that needs batching and are not using stored procedures. Now, we walk through an example and note the important things to consider. Examplepublic static final int SINGLE_BATCH = 1; public static final int SMALL_BATCH = 4; public static final int MEDIUM_BATCH = 11; public static final int LARGE_BATCH = 51; The first thing to do is pick how many batches you want and what sizes they should be. [Note that in real code, these values should be in a property file, not hard coded. That way, you can experiment and change the batch sizes at runtime.] Regardless, of the actual batch sizes, you need a single batch - a batch of size 1. This is so you can do queries if someone only asks for one value or the "left over" values in a big query. For the batch sizes, relatively prime numbers work well. In other words, the sizes should not cleanly divide into each other or be cleanly divisible by the same number. This maximizes the number of requests that will have the fewest server roundtrips. The number of batch sizes and exact sizes vary based on your configuration. Note that the large batch size should not be too big or you will run into memory issues. Also note that the smallest batch size should be very small. You will be using this one a lot since most queries eventually get down to this point. while ( totalNumberOfValuesLeftToBatch > 0 ) { Everything that follows is done repeatedly until we run out of values. int batchSize = SINGLE_BATCH; if ( totalNumberOfValuesLeftToBatch >= LARGE_BATCH ) { batchSize = LARGE_BATCH; } else if ( totalNumberOfValuesLeftToBatch >= MEDIUM_BATCH ) { batchSize = MEDIUM_BATCH; } else if ( totalNumberOfValuesLeftToBatch >= SMALL_BATCH ) { batchSize = SMALL_BATCH; } totalNumberOfValuesLeftToBatch -= batchSize; The idea here is to find the largest batch size possible that is bigger than the number of values we are trying to query. If none of the batches are big enough, we use the single batch size of 1. As an example of how this works, suppose I do a query for 75 values. First I do a query for 51 elements. Now there are 24 values left to query. Then I do a query for 11 elements. Now there are 13 values left to query. Since there are still more than 11 elements left, I do another query for 11 elements. Now there only 2 values left to query. This is less than my smallest batch size of 4, so I do two single queries for 1 value each. So I have made a total of 5 roundtrips and used three prepared statements in the cache. A significant improvement over the 75 queries of doing this individually! Finally, we need to keep track of how many values are left to query. StringBuilder inClause = new StringBuilder(); boolean firstValue = true; for (int i=0; i < batchSize; i++) { inClause.append('?'); if ( firstValue ) { firstValue = false; } else { inClause.append(','); } } PreparedStatement stmt = conn.prepareStatement( "select id, name from users where id in (" + inClause.toString() + ')'); Now we build the actual prepared statement. Since we always build the query in the same way, the driver will notice the SQL is identical on later executions of the same batch size. [Note: If you are not on Java 5 yet, replace StringBuilder with StringBuffer so this compiles.] It is important to return the id we are querying on so that we can later determine which name goes with which id. for (int i=0; i < batchSize; i++) { stmt.setInt(i); // or whatever values you are trying to query by } Set the proper number of values to query by. It is OK to include other search criteria in the query. Just put these parameters before or after the in clause parameters. In this case you will want to keep track of the current index. From this point, you just execute the query and get the result normally. On the first try, you should expect to see a performance improvement over options 1 and 2. It will take some tweaking to optimize the batch sizes to the best ones for your situation. Disclaimer: As noted in the famous quote "premature optimization is the root of all evil", select batching should be used to solve a performance problem. This is especially important here as you need the original numbers to confirm that you are actually getting a benefit. Return to Top |
|||||||||||||||||||||
by Michael Ernest The first and best thing one can say about Carol Murphy is she's fearless. She's the first person I've met who deals drugs in broad daylight. She gets caught at it every time, but has yet to be arrested for it! Impressive. Carol teaches as a substitute, so she's not afraid of sniper fire, and she puts in time moderating the Cattle Drive as she teaches herself Java. Carol was born in San Diego, CA, but grew up in Livermore. Her escape? Professional dancing. So from dancing to pharmacy to schoolteaching to programming, that's a zig-zag career path that would befuddle any parent. Since we can't record how Carol explains herself to family at Thanksgiving dinner, we just asked what we think her parents would have asked. The interview rules are simple. 10-15 blind questions. Answering quickly is paramount; we want gut-reaction answers so that you, the loyal reader, gets the full experience of socking it to a moderator — but good! JavaRanch: How come sign language is not considered a dance
form?
JavaRanch: Who has more evil cunning, Catbert or Garfield?
JavaRanch: Which B vitamin do you admire the most? Only
don't pick riboflavin, that's mine.
JavaRanch: I'm asking the questions here.
JavaRanch: What's your favorite aspect of working on the
Cattle Drive?
JavaRanch: Be honest. You've never worn chaps while
programming? Not once?
JavaRanch: Hmm. Evasive. What is your primary motivation for
learning Java?
JavaRanch: True or false. The government is keeping vitamins
B7 through B11 from the public and using them to make
genetically-modified super soldiers.
JavaRanch: I'm putting down 'True.' OK, Word association. I say
a word, you respond with the first one that comes to mind. Ready?
JavaRanch: boolean
JavaRanch: Julian
JavaRanch: cerulean
JavaRanch: antediluvian
JavaRanch: What's your favorite disco song?
JavaRanch: Complete this sentence: The one thing I would do to
spice up JavaRanch is ____?
JavaRanch: The one thing JavaRanch should never change is ___?
JavaRanch: OK, someone is signing to you and shuffling their feet
nervously. Now is it dance, or are you just a snobby purist?
JavaRanch: OK, last question. You're at the movies by yourself
with no cash left. The person on your left has Red Vines and Junior
Mints. The person on your right has heavily buttered popcorn and a
small Sprite. You only have one shot at smiling before the feature
starts. Which way do you turn?
Return to Top |
|||||||||||||||||||||
by Carol Murphy I just blinked and suddenly it's October. Time to ride on out and check up on the progress of our cowpokes. Riding back along the trail, the first driver I can spot is Juliane Gross. She's moving her little bit of the herd through Servlet territory. I think she's over there on Servlets 3a. Funny I didn't spot her before this, but then, I kinda had my head down. (More on that later.) Let's see, there's a dust cloud just over the horizon. Somebody must be ridin' hard and fast. Wonder if it's one of our drivers? We'll just mosey on over and have a look. Yep, it's Tom Henner, who has been positively prolific punchin' them doggies, ( no, no cows were actually assaulted ) and recently bagged his first Moose Head for completing the first 8 assignments. Don't think he even stopped to water his horse before movin' on to the next part of the trail. He's currently rounding up strays in Oop2 country. Dav McCartney is lurking around these parts as well, but he hasn't been spotted since July, so it's possible he's holed up somewheres takin' a wee break. Maybe we'll pass him a bit later. Working my way back up the trail, I can just see Tommy Leak and John Cooper way over yonder. They're both stuck in the badlands better known as Say 4b. Don't think I'll ride over to say Hi, though, too many bad memories. We'll get in touch when they find a way out of there! Now, according to my map, we should be coming up on one of our newer drivers, Barb Rudnick. She started the drive back in July, and her last known position was Assignment 3. Have to keep a sharp eye out and see if I can spot her somewhere along here. Also have to keep an eye out for Patrick van Zandbeek and Daniel Hyslop. They're our 2 newest buckaroos, and they should be around here somewhere. Patrick last checked in at Assignment 3, and Daniel, a cowboy from the UK, is close behind working on 1b, last I heard. Let's give them a hearty welcome, and be on the lookout for these two, just in case they need a little help along the trail. Speaking of help, yours truly would like to thank all those who came to her aid in recent days. As mentioned in the last edition, I have been tossing around the idea of getting back in the saddle and going after the new versions of the Servlet assignments using JSP. I finally screwed up the courage and took the plunge. Yes, the Village Idiot rides again. If anybody is feeling discouraged about their progress on the drive, please check out any one of my recent posts in the Cattle Drive forum. Baby, if I can do this, anybody can do this. I would like to thank specifically Michael Ernest, Marilyn de Queiroz, and Kristin Stromberg for their help, without which I would probably have hacked my monitor to pieces with a hatchet by now. After riding into many a box canyon, and dead-end paths, I have finally rounded up enough strays to submit a working version of Servlets 3a. Me and my over-worked cowpony are resting up awaiting the results of the nit-picking. Anybody who has completed the original Servlet assignments is hereby challenged by me, the Village Idiot, to go back and do the assignments in their current form. I double-dog dare you. Well, I think that's about it for this update of the Cattle Drive. I'm gonna hunker down by the camp fire and reflect upon the beauty of the night sky. See ya' on the trail! Mosey on over to The Cattle Drive Forum in The Big Moose Saloon! Return to Top |
|||||||||||||||||||||
Discuss this book review in The Big Moose Saloon! Return to Top |