Starting from Oracle 11gR2, exp is deprecated and will exclude empty tables from the dump file (not even the table definition). The fact that exp is deprecated does not make it less than a bug, especially when the proposed replacement expdp is a DBA-only tool that generates the dump files on the database server...

At least for this release, setting DEFERRED_SEGMENT_CREATION to false and issuing alter table T move; and alter index I rebuild; for all tables and indexes created previously seems to make exp to work a little better. For the next releases this may not work and there may not be any suitable workaround.

Oracle really needs to rethink expdp and make it work from a client installation only, the workaround that involves installing another Oracle DB of the exact same version and connecting the 2 databases via a DB link is nothing short of absurd.

Here's a way to have actual Nucleus component paths in log files with ATG 2007.1 and JBoss: create a localconfig/atg/dynamo/service/logging/ScreenLog.properties file with the following content
useNucleusPathForClassName=true
useFullPaths=true
The result is:
15:52:15,650 INFO  [/atg/registry/PipelineRegistry] Starting Pipeline Registry.
15:52:16,232 INFO  [/atg/reporting/md/DatasetDomain] Domain service started
15:52:16,442 INFO  [/atg/reporting/ChartTemplateScheduler] DSS Template scheduler started
15:52:16,754 INFO  [/atg/epub/file/RepositoryGroupRefresherService] Refreshed repository groups
15:52:16,847 INFO  [/atg/epub/file/RestartableComponentCheckinListener] Restartable component checkin listener started up successfully.
15:52:17,323 INFO  [/atg/epub/DeploymentServer] no targets defined in topology definition file
This configuration could possibly cause a small performance hit, but in my view makes the log much more useful and it should become the default.

One could play also with the Log4J configuration, for example this pattern in log4j.xml
<param name="ConversionPattern" value="**** %-5p %d{ABSOLUTE} %c{1} %m%n"/>
gives a more familiar look (for those accustomed to DAS, anyway):
**** INFO  15:57:29,842 /atg/epub/file/RepositoryGroupRefresherService Refreshed repository groups
**** INFO  15:57:29,936 /atg/epub/file/RestartableComponentCheckinListener Restartable component checkin listener started up successfully.
**** INFO  15:57:30,552 /atg/epub/DeploymentServer no targets defined in topology definition file
I decided to try some of the most known Java IDE on the market to see which one would work better for me: the idea was to install each IDE and work with it for at least a week on a real project, to test if I could adapt to a new IDE and the IDE could adapt to my working patterns in a relatively short time and in a "real world" situation. I wanted a comparison that made sense to me at this time and in the context of this particular project, not trying every single feature of each IDE: I wanted to test

  • editing and refactoring code
  • cvs integration
  • configurability (how much the IDE can adapt and how much it gets in my way of working)
  • remote debugging
  • speed of operation

I didn't care at all about Web services, JUnit integration, JSF support, etc.: all of this does not matter to me at this particular time.

Setup and constraints
The setup on this project is a little unusual, but there are good reasons for it: the IDE runs on my laptop, where the code being edited and ran is on another workstation, mounted as a Windows shared drive (Z:\) on the laptop. The code is a multi-module ATG application, consisting of about several hundreds of Java classes, properties files and JSP pages: all the files are kept in a CVS server, and the CVS working directory is also the ATG module directory so that only one copy of each file exists at any given time. IDE-specific project files should not go into CVS, to avoid conflicts between workstations with different path names.

Eclipse 3.2.1
Despite my good intentions, Eclipse did last only half a day... I had configured my workspace and the project files on drive D:\, then attempted to configure the project source folders via the Link Source feature: it worked fine up to the point of checking out the sources from CVS, when I discovered that Eclipse is unable to check out/check in. There is a long standing bug that apparently won't be fixed.

This is a pity, because Eclipse is a decent tool despite its shortcomings and many inconsistencies (often debugging cannot "see" the project sources, 2 or 3 different perspectives to work with source control, etc.). I tried to run it on the workstation, but adding it to the 3/4 ATG instances already running was asking too much to the hardware.

NetBeans 5.5
NetBeans was more willing to cooperate with my setup, and it lasted a whole week of intense work. Nothing much to say about the CVS integration and the project organization, at least it allowed me to place the files where I wanted them to be; the speed of the IDE was something I could live with, even if not extraordinary, but the editor had its own share of problems:
  • at first I could not find how to generate getters and setters for a class member variable, then I found I must select the variable and choose Refactor > Encapsulate Field: I found that counterintuitive but acceptable, I could not accept however that it didn't respect the code conventions I configured ("m" prefix for member variables, "p" prefix for parameters)
  • saving a file took 30 seconds on average, probably due to the working directory being on a shared drive and the IDE trying to synchronize too many files in the proceeding
  • the JSP code assistance produced weird code, for example I typed <dsp:param name="propertyName" value="phoneNumber"/> and then Enter, and it added a </dsp:param> that makes no sense
  • after writing a Java method will all of its parameters and return type, typing "/**" to start a javadoc comment does not generate the @param and @return tags
  • very often the remote debugger didn't work, in many cases I couldn't get the local variables of a method (Evaluating... was displayed for minutes in the debugger panel) and in some other cases it hung up the whole IDE (the only recurse was force-quitting it)
  • once, after an intense, day-long editing session I had an icon flashing in the lower-right corner of the IDE window with a "too many window handles" message: switching between Windows process with Alt-Tab gave weird results, with windows flashing all over the screen, and in NetBeans I couldn't do anything; in the end I had to kill it
So NetBeans was better than Eclipse in my situation (I could at least *use* it to work) and it didn't have many of Eclipse's interface annoyances: however I didn't find it good enough to keep working with it.

JDeveloper 10.1.3.1.0
I spent more time setting up a project with JDeveloper than with the other IDEs as I didn't understand at first the system and application navigators: what I want is see the files as they are laid out on disk, and only then maybe overlay a custom view on top. I discovered in the end that setting the "flat level" to 1 in the application navigator came close to what I want. I liked the structure panel of the JSP editor, but I couldn't find an option to open JSPs by default in the source editor rather than in the visual editor, which was useless to me; saving files was even slower than in NetBeans and refactoring was sometimes bizarre (renaming a dynamohttpservletrequest method parameter to pReq produced invalid syntax...).

I don't have much else to say about JDeveloper because in the end it lasted less than one day... In a couple of occasions it took 99% of the CPU and swallowed more than 680MB of RAM, maybe the sources on a shared drive bothered it but I didn't spend too much time analyzing the problem and quickly proceeded to the next one.

IntelliJ IDEA 6.0.4
I know, I know... this is not a free IDE. But I never said I would limit myself to free offering, and by the way in my tests it came out as the best of the pack. I was able to configure a multi-module project without difficulties, the only trick is to start small (for example with a Java module) and then add the config directory and the JSP directories either as a source directory or as a separate web module. The editor is the best part of the product, fast, straightforward to use and full of class acts (try selecting a method parameter, hit Shift-F6, type something and see what happens to the method source code). I liked very much the multi-language support, very handy when editing a complex JSP page that uses several JavaScript libraries and fragments; code assistance is available for XML documents even when a DTD is not loaded, after a while the editor "learns" the syntax and pops up a choice from the previously entered tags and attributes.
The CVS integration worked well for the basic operations of check-ins, check-outs and version comparison, I hadn't however had the opportunity to try to tagging or merging of branches so I can't say how it compares to Eclipse's. Debugging also worked fine, the basics (setting breakpoint, evaluating expressions, hotswap reloading) did just what I expected to do without getting in the way.

And that's the major decider for me... IDEA simply worked without getting in my way with arbitrary limitations. Where it shines is in the editor, the configurability and the speed at which everything operates: in a "shopping list" of features it might lose to the others, but it wins because the implementation and the design feel more consistent to me.
Of the 12 or so Java libraries used in this site, 6 of them by default have their configuration files in the CLASSPATH:
  • log4j (log4j.properties / log4j.xml)
  • commons-logging (commons-logging.properties)
  • XWork (xwork.xml)
  • WebWork (webwork.properties, validators.xml plus all the <action>-validator.xml)
  • OSCache (oscache.properties)
  • iBATIS (the sql map configuration and definition)
Various resource bundle also litter the CLASSPATH, thanks mainly to WebWork action messages and global resource bundle. Enough of that for me, time to end that incestuous relationship, stop wasting time with .cvsignore and svn:ignore: I want Java classes in the CLASSPATH and nothing else. I could convince at least log4j and Ibatis to look elsewhere:
  • log4j has PropertyConfigurator.configureAndWatch(), where the argument is the full path of WEB-INF/config/log4j/log4j.properties
  • Ibatis has SqlMapClientBuilder.buildSqlMapClient(), where the argument is a java.io.Reader from the full path of WEB-INF/config/ibatis/sqlMapConfig.xml (nice side effect, I can freely reload my Sql map definitions at runtime)
For resource bundles I now use a database, a simple table with (key, language, message) and a class that implements ResourceBundle, no more .properties and native2ascii to deal with. As you see, I've started to use WEB-INF/config and subdirectories, whose full paths are determined via ServletContext.getRealPath("/") + "/WEB-INF/config". The benefits:
  • the files won't be served to HTTP clients
  • very easy to put under source control, no gymnastics with .cvsignore or svn:ignore required
There is one disadvantage, it won't work if the web application runs from a compressed WAR file: my guess that WEB-INF/classes became a popular dumping ground because other than Thread.getContextClassLoader().getResource() there is no portable way of reading files in a web application. How many web application actually run from a compressed WAR file? What's the rationale for having it in the Servlet spec?
I've written already about debugging a server-side application within an IDE as a very useful technique, but I want to point out that it should absolutely not be the only one debugging facility that a developer should use. In general, the writer of server-side code MUST place logging calls that trace the execution and allow the consumer of the code to configure the logging level.

I say must and I really mean it, there are cases in which using a debugger is not practical (a library supplied in binary-only form) or not even possible (a bug in a production environment, accessible only to the operations people): imagine that you turn on all the debug flags and there is no line of logging output, what can you do now? Do you try to attach a debugger to a production JVM? I can hear the operations people rolling on the floor laughing at you...

I consider code without logging as untested; stepping inside it once with a debugger doesn't count. If the code doesn't log anything it means it has not been put to test into "real world" conditions, and I do not want to be a guinea pig again, I've had my share of crap code to deal with and that's enough. My tolerance level has dropped to zero.
I want to debunk a myth: in an ATG application, when you recompile a class you have to restart the application server to load your changes.
I call this a "myth" because it has a grain of truth to it (it was true for a while), but it is no longer true; it is unfortunately perpetrated by ATG's own product literature, and I will try to better explain the situation to future students in my courses.

ATG provides the Disposable Class Loader to facilitate the development of applications, it is fairly simple to use and works well, if one keeps in mind its limitations. For example, instead of stopping a droplet that needs to be reloaded, one can set the component scope to request when developing, make changes to the class and then once done set the scope back to global as it should. The other solution that I'll describe can be used if the JDK is at least at version 1.4.2 as it leverages HotSwap reloading of classes in a debugger.

Setup
First of all, ATG needs to be set up for debugging: to do this, launch the ATG instance with the -debug and -debugPort ... switches, for example:
bin/startDynamo -m filebrowser.FileBrowser -debug -debugPort 3000
I'm choosing port 3000 for the JPDA debugger connection, and I'm launching ATG with my FileBrowser module, to demonstrate how HotSwap reloading works I'll add uploading of files in the current directory.

Then, set up your module's classpath to use "naked" classes rather than a JAR file: while there shouldn't be any difference, I found out that this way the IDE seems to have a better understanding of which classes need to be reloaded. Edit the MANIFEST.MF file to have a line like this:
ATG-Class-Path: classes/
Now set up your IDE to compile classes in the right directory and to connect to the ATG instance for debugging: I tried IntelliJ IDEA for this, and it worked beautifully.
  1. choose Edit Configurations... from the Run menu
  2. add a new Remote configuration
  3. fill in the parameters to connect to the local machine at the port chosen before for ATG to listen on
  4. set up the javac destination directory to be the one previously defined in the MANIFEST.MF file
  5. compile the classes and launch ATG
If everything went well, choosing Debug from the Run menu opens the debugger panel:

and a new option in the Run menu is enabled, Reload Changed Classes


Execution
Let's make a change: in the FileFormHandler.handleUpload() method, which looks like this:
public boolean handleUpload (DynamoHttpServletRequest pRequest,
                             DynamoHttpServletResponse pResponse)
throws ServletException, IOException {
String ctx = "handleUpload - ";
if (getUploadedFile() == null) {
if (isLoggingDebug()) {
logDebug(ctx + "uploadedFile is null");
}
return true;
}
...
the getUploadedFile() == null is not properly detecting the case of users clicking on the Upload button without choosing a file. Let's make it work better:
public boolean handleUpload (DynamoHttpServletRequest pRequest,
                             DynamoHttpServletResponse pResponse)
throws ServletException, IOException {
String ctx = "handleUpload - ";
if (StringUtils.isEmpty(getUploadedFile().getFilename())
    && (0 == getUploadedFile().getFileSize())) {
logError(ctx + "no file chosen for upload");
addFormException(new DropletException("no file chosen for upload"));
return true;
}
...
and then I choose Reload Changed Classes: the IDE recompiles the class and then asks the target VM to reload the new version. Now, clicking on the Upload button without choosing a file causes the error to appear in the logs:
**** Error      Sat Dec 30 11:24:47 CET 2006    1167474287328
/atg/dynamo/servlet/pipeline/RequestScopeManager/RequestScope-15/atg/filebrowser/FileFormHandler
handleUpload - no file chosen for upload

Limitations
What are the limitations of this technique?
  1. adding or removing methods doesn't work, the VM is not able to apply the change; for this case, either use ATG's Disposable Class Loader or restart
  2. resource bundles aren't considered by Hotswap, so adding a key-value pair raises java.util.MissingResourceException; resource bundles in .properties files are a bad idea anyway, you shoud really use a database (this will be the subject for a next post)
Students who take the ATG Portal course often have serious problems tracking down and solving 404 errors in portal pages: as the portal works by dynamically including many JSP fragments, there may be multiple concurring causes for those errors and few of them have to do with an true typing mistake of an include tag. I use the following checklist to solve those problems:
  1. look in the http://localhost:8840/nucleus/atg/dynamo/service/j2ee/J2EEContainer/ component to see if the portlet webapp is correctly started; if the portlet is not there, edit its J2EEContainer.properties file and add it, like
    applications+={atg.dynamo.root}/MyPortlets/j2ee-apps/myportlet_war
  2. check the portlet.xml file for any typos in the JSP mode mappings, re-run the portletDeployTool script and redeploy the portlet
  3. check the dynamoJ2EESpecifier.xml file to see if there is a web-application-mapping for the portlet webapp, if not then add it
  4. check the web.xml and the dynamoJ2EESpecifier.xml file again to see if all the taglibs have the correct definitions
In my experience, the most critical step is the third one: if the J2EE container does not know where to map requests for your portlet, it will always reply with a 404 error. A great improvement would be having in the error page the full path of the target file, that is the path to the file that was supposed to be included: I really do not want to enable debugging on the FileFinderServlet yet another time...
... synchronously. Recently I had to deal with nonsensical email bounces from a site I occasionally update, errors of the type:
unrouteable mail domain "hotmail.com"
Of course, domains such as hotmail.com, yahoo.com, etc. are perfectly capable of receiving all the messages one wants to throw at them, so the problem was in our own mail server. It turns out that the mail server returns this bogus message when you've tried to send too many message over a certain period (say for example more than 20 pieces per minute, the actual limit may vary from configuration to configuration), and to make matters worse, the limit is a global one: all the sites hosted on the same box concur to the limit, so if site A sends 15 pieces and site B sends 5, then site C would not be able to send email at all in that minute.

My site was indeed sending messages synchronously, after a form submission, and it tried to send more than 250 pieces each time; this also made the user interface completely unresponsive, at the point that some users repeatedly clicked on the "Send" button, aggravating the problem even further. Also, all the bounces arrived in my inbox...

Here's how I solved the problem: I created a new database table EMAIL_MESSAGES to store the messages with the following structure
create table EMAIL_MESSAGES (
  id integer not null primary key,
  sender varchar2(200),
  reply_to varchar2(200),
  destination varchar2(200),
  subject varchar2(200),
  body clob,
  creation_date date default sysdate,
  sent_date date,
  sent_flag integer default 0
)

and then changed the form submission code to insert into the table a copy of the message for each recipient. I created a script that selects X messages maximum with sent_flag = 0 and sends each copy, setting the flag to 1 afterwards: the trick is scheduling the script with the correct frequency, not too often to avoid crossing the limits and not too rarely to freak out people not receiving emails. We also can selectively resend only the messages that bounced and not the whole lot, and the user interface has greatly improved since it returns immediately after the form submission. Why this solution is scalable? Because at any time I could easily schedule another instance of the script to send via a different SMTP server...

Old messages do not stay in the database indefinitely, actually the first thing the script does is deleting messages older than 15 days, irrespective of whether they've been sent successfully or not.

While my actual implementation was in PHP/MySQL (difficult to find a worse combination...) the principles of this solution are applicable to any language and database: for example Tom Kyte wrote a while back how to do the same thing with the Oracle database, ATG has the TemplateEmailSender class and many others undoubtedly exist out there. Make sure you look for one of them the next time you have to send some emails.
Old news by now, but some of weeks ago Qualcomm announced a change in the development of its venerable email client, Eudora: future version will be based on the Thunderbird technology, and will be free and open sourced.

Yesterday
I'm using email since 1995, and is now an integral part of my day. At that time I worked on the original Mac OS platform, and the client of choice for reading messages offline was Eudora: in those days we had only modems, we paid far too much for slow and unstable connections, and reading emails online was possible just for those who were within an university network. As a result I was, as everybody else at the time, much more inclined to think before write, to compose long and coherent messages and to quote properly (like in putting the new text below the quoted passage...)

Eudora was a small program that I could run on my LC without any memory shortage even with large number of messages and mailboxes (MacSOUP was another gem for offline newsgroup reading), incredibly configurable thru preferences visible and hidden, and it communicated the sense of humour of its author Steve Dorner. Using Eudora shaped the way I read email even today, on a PC laptop, with a new window for every message and an incredibly fast search.

Today
But today Eudora is not an application anymore, it is a product. And products have strange, interesting lives. For example, by being produced by companies they're usually supposed to make some profit first, and to make clients happy second. I don't know if Eudora was profitable for Qualcomm, but surely many clients were not happy: the Eudora forums registered many complaints about the brokenness of the IMAP implementation in the 7.0 version, so many that a dedicated forum was created to gather all the discussions. Indeed, Eudora 7.0 was so broken for IMAP that I reverted immediately to 6.2, patting myself on the back for the backup I made previously.
The announce sparked a number of reactions and speculations about the possible evolutions for Eudora, the more interesting coming from the Mac side (notably, the Macintouch report and the TidBITS thread).

Tomorrow (or, what's wrong with Eudora?)
What's exactly wrong with Eudora today? It's weak in HTML mail rendering, and people accustomed to the Email for Dummies interfaces of Outlook, Thunderbird and Apple Mail do not seem to be able to grasp the multi window approach. There is not a single, cross-platform codebase: the Windows and Mac versions are separated applications, without feature parity.

To summarize, Eudora is a Mac-like application on Mac OS, and a Windows-like application on Windows, is extremely fast at searching, holds large quantities of messages, has an efficient interface for power users and is very configurable. Let me repeat the question, what exactly is wrong with Eudora? It's just HTML rendering and a cross-platform codebase? I hope that doesn't have to come along with bloat and clunkiness.
About a month ago this blog started to be hit by comment spam attacks, not at the scale that other more popular sites may experience, but nevertheless a bother: those comments wasted bandwidth, database space and my own time. For a while I manually deleted them with the embrionic admin interface that I'll never get around to properly finish, or with straight database delete SQL calls, but leaving for a weekend was enough to find more than 500 comments attached to a single post. I'm obviously not the only one to be hit by this problem, for example two blogs I like have disabled comments for this very reason.

It was time to think about a solution that didn't involve an ongoing investment on my time, so I immediately discarded comment moderation; I didn't want to bother potential commenters with registration systems or CAPTCHAs either, so I decided to analyze the spam by looking at the server's logs. Here are the countermeasures I came up with:
  1. IP blacklist: I created a table in the database that contains the IP addresses of the most egregious offenders and a servlet filter mapped to *.jsp and *.action, such that when a request is coming from one of the IP addresses in the table it gets immediately rejected with a 500 error code
  2. Stricter parameter validation: spammers hit directly the *.action URL by making a POST request from some spammer tools, without stopping by the comment form like legitimate humans, so the action now checks for a couple of parameters; when those parameters are absent, the response is another 500 error
  3. Akismet spam filtering system: the 2 methods above are pretty effective already against existing spammers, but new spammers may come up and existing ones could learn how to bypass the filters; the final barrier is Akismet by Matt Mullenweg, that now gets every comment posted to the site via the Java API by David A. Czarnecki, and responds by marking a comment as either spam or ham
The net result is that I enjoy a spam-free blog, at least for now: spammers tools could become more sophisticated, and the Akismet service could go down (it happened at least once already) or move behind a pay-wall. How many IPs I would have to blacklist then?
Werner Vogels is the CTO of Amazon, and that alone is an indicator that he's smart... But there are some gems in the interview really worth pointing out.

They can do any design methodology they want as long as they deliver the actual functionality that they've been tasked with. Now, with this flexibility comes also the responsibility of actually operating the service. The same group of people that actually builds the service is also responsible for operating it. So there is no wall over which things are being thrown at Amazon, to which the operators who are then responsible for handling the service, but they are the same developers, the same group of people that has built a service are also responsible for operating it.

This is like proverbial Columbus' Egg: the people responsible for operating the service are those that know it best, they are aware of the operating difficulties and can design to solve them, they cannot throw any problem to the operating people and the operating people of course cannot throw any dumb problem to the developers.
Agile Project Management with Scrum (Microsoft Professional)

The book is interesting in the way it presents case studies of applying Scrum to software development projects in different kind of organizations, and in what one has to do to stop relying on meaningless Gantt charts and Microsoft Project constructions to plan and report project progress. I felt that the day-to-day activities of Scrum needed a bit more space than the appendix, and to how Scrum can be integrated with other practices, such as task estimation techniques: for example, we're told that at the Project Backlog meeting the Team should estimate the work it can do in a Sprint, and while I agree that the estimating activity isn't part of Scrum I would find very useful mentioning which kind of estimating technique can be used in such a short time.

Also the treatment of Scrum for fixed-price, fixed-date contracts is way too light for me, as it is one of the situations I'm most likely to find myself into (I find very interesting that fixed-scope doesn't get mentioned along fixed-price and fixed-date). For this reason and for the anecdotical tone of the book, my global judgment is fair: it was difficult not to feel that a Scrum project cannot be successful unless the author is present...
At last, a technique for supporting the post-redirect-get pattern on WebWork 2! I asked for it at some point, now there's hope that it will be added to the official WW releases.

The sad things?
  1. it took the ROR fanboys bragging about their "flash" scope to have one of the WW developers look at the problem
  2. all that activity on including AJAX support in WW2...
  3. this feature exists in Dynamo since ages (4.5.1, 7.2)
Conclusion? It was about friggin' time!
Given that in my daily work routine I browse several huge documentation libraries, I have two problems:
  1. sometimes I do not have any network access and thus I cannot access anything online, thus no documentation [sic...]
  2. if I store everything locally, it takes up way too much space (JDKs, ATG, Oracle, etc.)
It would be wonderful if I could compress the data to a zip archive and have transparent or near-transparent access. Indeed, there are Java IDEs that can do that... For example, IntelliJ IDEA and NetBeans can access the Javadocs HTML pages on a ZIP archive. A different solution is the Apache module built for this purpose, mod_ziplook2, that allows to treat ZIP files as directories, and it was what I was using on the old laptop. On the new one, I never managed to properly compile the Cygwin Apache 2 package, and the 1.3 Apache hangs completely after 5/6 requests... It doesn't seem very much up-to-date however, and my days as a C hacker are long gone.

So I decided to write a mod_ziplook equivalent as a Java servlet, using the excellent TrueZIP library: returning an HTML page to a browser is extremely simple, for example http://localhost/docs/jdk-142-docs.zip/jdk-142-docs/api/java/lang/String.html resolves to this handleFile() method where all the "real" work is done by the catTo() call:
private void handleFile (de.schlichtherle.io.File pFileEntry,
                         HttpServletRequest pReq,
                         HttpServletResponse pRes) throws IOException {  
  // get the content type of the file to be transmitted
  String mimeType = getServletContext().getMimeType(pFileEntry.getName());
  
  // get the size of the uncompressed data
  long fileSize = pFileEntry.length();
  
  // write the response headers
  pRes.setContentType(mimeType);
  pRes.setContentLength((int)fileSize);
  
  // write the response data
  pFileEntry.catTo(pRes.getOutputStream());
}
Download the result and deploy the dist/zipdocs.war archive to your favorite container. I'm currently using the Sun Java System Web Server and it works just dandy :-).
A new ATG module named FileBrowser is available for download. This module allows to explore a server's filesystem from within an ATG instance, and it's a JSP port of Will Sargent's Filebrowser module with additional functionalities (downloading and deleting of files, revised interface).

A screenshot of the module in action, exploring my laptop's C:\WINDOWS\system32 directory (click the picture for a larger image):

Screenshot of the FileBrowser module exploring C:\WINDOWS\system32


Download the module, unzip it and place it into the ATG module root, start Dynamo with bin/startDynamo -m FileBrowser, and access the application at http://host:port/file-browser/.

Update: added version 1.1 with file upload functionality