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:
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:
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:
Limitations
What are the limitations of this technique?
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 3000I'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.
- choose Edit Configurations... from the Run menu
- add a new Remote configuration
- fill in the parameters to connect to the local machine at the port chosen before for ATG to listen on
- set up the javac destination directory to be the one previously defined in the MANIFEST.MF file
- compile the classes and launch ATG
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?
- 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
- 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)
Hi, I wanna to know the version of your ATG. I can‘t use the “-debug“ parameter to start the ATG on my pc. It show “Unknown option: -debug” message.
thanks.