Web Project Structural Options in IDEA 7

-          Damodar Chetty

-          March 13, 2008

-          Updated: March 31, 2008

 

Update Notes:

I added in an important kind of file to my toy project below - a configuration file. I've modified User.java to make use of User.properties.

I also split Option 1 into two parts to make the steps clearer.

Corrected the assertion that the Web module's output path was immaterial.

 

Last week we set up an IDEA web project for a fairly complex project (12000+ classes). However, the feedback I received indicated that the perspective was too conceptual, with requests for a blow-by-blow account of the actual steps required. Well, here's a walkthrough using a simple project that models my real world experience.

A typical web development project consists of Java sources, JSPs, static HTML content, and third party libraries. So, I've hand rolled a trivial project with one instance of each. The intent is to exercise IDEA's Tomcat deployment functionality, so the project itself is not very fancy - no JSP or rich components to see here.

 

My test bed project is set up as follows:

1.       Content Root: c:\dev

2.       Source Folder (within my Content Root): c:\dev\src
Our Java sources (User.java and User.properties) are at com.swengsol.web.user.

3.       Compiler Output Folder: c:\dev\dist\classes

4.       Location of my web.xml file: c:\web.xml

5.       Exploded WAR directory: c:\dev\ROOT

6.       Web resource directory: c:\dev\ROOT
The file
c:\dev\ROOT\index.html is my only HTML resource.

7.       Location for my JSP sources:
I've chosen alternative locations for my JSPs to show multiple project organization techniques.

a.       JSPs may live within the web resource folder, i.e., under c:\dev\ROOT\jsps\user.jsp

b.       Or, they may be mapped to our Java source folder, c:\dev\src\com\swengsol\web\jsps\user.jsp.

As described later in this document, option (a) is the most versatile option, and will work whether you set up your web code as a web facet of the main project, or as a separate web module; whereas option (b) will only work when used within a web facet of the main project.

8.       Location for my third party libraries: c:\dev\lib
Contains
commons-lang-2.3.jar which can be found at the Commons web site.

 

The files in our test bed will include:

c:\web.xml:

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

          http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

           version="2.5">

</web-app>

 

c:\dev\ROOT\index.html:

<html>

<head><title>Index test page</title></head>

<body>Click <a href="jsps/user.jsp">here</a> to launch the JSP.</body>

</html>

 

c:\dev\src\com\swengsol\web\jsps\user.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%@ page import="com.swengsol.web.user.User, org.apache.commons.lang.StringUtils" %>

<!doctype html public "-//w3c//dtd html 4.0 transitional//en">

<% User u = new User("YUSERJSP", 100); %>

<html>

<head><title>Test Page</title></head>

<body>

    User is: <%= u.toString() %>

    <p>

    Name is (in lower case): <%= StringUtils.lowerCase(u.getName()) %>

  </body>

</html>

 

c:\dev\com\swengsol\web\user\User.java:

package com.swengsol.web.user;

import org.apache.commons.lang.StringUtils;

public class User {

    private String name;

    private int age;

    private Properties props;

    public static void main(String[] args) {

        User user = new User("YUSER1", 30);

        System.out.println(StringUtils.lowerCase(user.toString()));

    }

    public User(String name, int age) {

        this.name = name;

        this.age = age;

        props = new Properties();

        URL myurl = this.getClass().getResource("User.properties");

        try {

            props.load(myurl.openStream());

        } catch (IOException e) {

            System.out.println("Unable to read properties");

        }

    }

    public String getName() {return name;}

    public void setName(String name) {this.name = name;}

    public int getAge() {return age;}

    public void setAge(int age) {this.age = age;}

    public String toString() {

    return props.getProperty("user.username.label") + ": " + name + ". Age: " + age;

    }

}

 

c:\dev\com\swengsol\web\user\User.properties:

user.username.label=User Name

 

 

Option 1: Using a Web Facet on the Main project

 

This is by far the easiest way to setup a web project - you simply add a web facet directly to the main project file. Behind the scenes, Java class files and any resources (incl. JSPs) are copied to the exploded deployment folder, creating a complete web deployment for us.

 

As stated earlier, with a web facet, your JSP files may live within the source tree of main.ipr (the image to the left), or they could be mapped directly to c:\dev\ROOT\jsps by the version control system (the image to the right).

              

 

In the former case, IDEA's build/deploy process for the web facet will copy the files over as required, and in the latter, the files are already available in the exploded deployment folder, and no copying by IDEA is necessary.

Both cases are functionally equivalent - you just have to make judicious use of the Excluded paths capability (found in your module's settings, under the Sources tab) to ensure that the correct files are marked as Sources, and that the rest are marked as transitory output resources. These are explained in detail in the following steps.

Option 1-a: JSPs within the Java source tree

1.       To simulate the terrifying effect of being the new developer on a team who has to setup his development environment, go ahead and set up your folder structure as shown below:

Your JSPs are part of your Java package structure. They won't be found by Tomcat, unless IDEA's deployment process copies these over to under the deployment root.

2.       In IDEA, choose File > New Project.   


3.       Build the project using the following steps:

Click the rename button, and change the name of the module to main. You may choose to rename the library if you so desire.


4.       Here's the resulting project.
                  

5.       Next, open up the compiler settings (File > Settings > Compiler) and tell it to never deploy web applications to the server after compilation:


6.       Open up the project settings. Note that IDEA is smart enough to detect that a web facet is required (based on the presence of our web.xml file).

Set the module compile output path so that the build process knows where to copy the compiled .class files:




7.       Setup the Web facet to ensure that the required JARs are copied over to the deployment folder, and that the resource directories are setup correctly.

Note that our JSPs have to be registered as a "web resource" to force them to be copied over to the deployment folder, otherwise they won't be available to the web server.


I.e., IDEA will move the contents of the
c:\dev\src\com\swengsol\web\jsps folder to c:\dev\ROOT\jsps.

Setup the exploded deployment folder as shown. All resources in the previous tab are deployed relative to this exploded root (
c:\dev\ROOT).


Note that I have deselected Excluded from module content. Checking this box hides the
c:\dev\ROOT folder from the Project explorer in IDEA. I.e., your project would look like this:

See - no ROOT!

Unchecking it gives us a more complete picture:


8.       Choose Rebuild Project.
JSP validation will sometimes fail if it is not able to find the required deployment dependencies. To fix this, simply rebuild your project, and then right click the JSP and choose to validate it.  

9.       Go back to your project settings, and mark WEB-INF as a non-source folder:

Doing this ensures that WEB-INF is treated as an output folder (which it is). If you do not mark this explicitly as a non source folder, you see the following effect when you click on Goto > File and start to type in the properties file name. To hide the duplicate entry, mark the ROOT\WEB-INF folder as being a non-source folder.


10.   Setup a deployment configuration (my server.xml file has the HTTP Connector listening on port 80).




11.   Start up your deployment. Your browser should automatically launch your index page:


12.   Click the hyperlink and prepare to be overwhelmed:


13.   To verify that everything works as expected, perform the following tasks:

a.       With the web server running, verify that the page displayed looks like:

b.       Edit User.toString() to read:
public String toString() {       
   return        props.getProperty("user.username.label") + "%:%" + name + ". Age: "        + age;   
}

c.       Choose Build > Compile User.java

d.       Now refresh the page to see the display updated to:

e.       Edit User.properties to change the label value:
user.username.label=[User Name]

f.         Refresh the web page (we don't need to compile a properties file), to see the display updated to:

An interesting point to note here is the effect of deselecting both Build on Frame Deactivation checkboxes. Lets deselect them, and redo steps 12 a, 12 b, and 12 c, above. Now refreshing the page shows us exactly what we saw earlier in step 12 d. Interestingly, even if we shut down and restart Tomcat we find that the changes have taken (if you wonder why this is surprising, look at the equivalent step in Option 2 at the bottom of this document).

But, if you repeat step 12 e and 12 f, you find that the properties file change is not reflected on a refresh. In step 12 f, you need to explicitly perform a Build > Compile User.properties, to see a browser refresh reflect your change!

Option 1-b: JSPs within the deployment root

1.       To simulate this option, setup your folder structure as follows:

Note that your JSPs are part of what will become your deployment root. As a result, they are already in a position that Tomcat expects, and IDEA need do nothing more.

2.       This is the same as Option 1-a.

3.       This is the same as Option 1-a.

4.       The resulting project will look as:

5.       This is the same as Option 1-a.

6.       This is the same as Option 1-a. Except that your folder structure on the right of the Sources tab will reflect your new reality.

7.       This is mostly the same as Option 1-a. Except that you don't need to have IDEA move your JSPs during deployment.




8.       This is the same as Option 1-a.

9.       You have a bit more some work to do here. Notice that there are 2 sets of duplicates, for your resources as well as for your JSPs.


The fix is to mark your JSP folders as Excluded as well:

This has the effect of hiding these folders in your Project view. I.e. your project view now hides the additional folders under ROOT.


10.   The remaining steps are the same as Option 1-a.

 

Option 2: Using a Web Module

 

While using a Web facet in the main project is probably the simplest option when it comes to project setup, there is an unfortunate downside. Adding a facet to a Java project means that non web developers on your team have to endure the web build and deploy process. Fortunately, a simple fix is to factor out the common code into its own module - IDEA's unit of reuse.

 

In our toy example, we set up a separate project for the main Java files, that has a single Java module for the utility classes (User.java in this case). Next, set up a web project with a single module that contains our web code, with our static resources (index.html), and our sole JSP (user.jsp).

 

Unfortunately, the obvious project setup (at least in my case) was not permitted by IDEA. I.e., have two modules within the project share the same content root, except that they add in different source folders. Trying this gives you this error dialog:

 

I even tried to cleverly get around this limitation by setting my main module's content root as c:\dev, and my web module's content root at c:\dev\ROOT. However, doing so constrains your JSPs to be located somewhere under c:\dev\ROOT. This is because IDEA lets you only select a folder below your content root (i.e., c:\dev\ROOT, and so anything under c:\dev\src is no longer accessible). When you try to force this issue with IDEA, it stubbornly presents the following dialog:

 

While this isn't isn't such an awful compromise - it does tend to offend the purists who require source structures to be completely agnostic of the IDEs used.

 

For the remainder of this section, please note the constraint that our JSP sources will be located under our web module's content root, e.g., c:\dev\ROOT\jsps\user.jsp.

 

One positive with this approach is that your static content as well as your JSPs are mapped straight into where they should be deployed anyway, reducing any copying that would be required.

 

1.       Initialize our directory structures.
Lets start from scratch - except this time our JSP lives under
c:\dev\ROOT\jsps.



2.       Setup the utility project.
We'll go through the same steps as with Option 1 above, except that we won't select any facet. After all, this is going to be our plain vanilla Java utility project - so it really needs to know nothing about our JSPs and HTMLs.


3.       Here's our completed project:



4.       Now, lets go check its configuration.
We won't do anything except set the output path for the
.class files generated from a build of this project.


 

5.       Set up a run configuration for User.java:


6.       To prove that our utility project is up and functional, execute the run configuration:
 

7.       Close this project.

8.       Build the Web project that will reuse the utility module created earlier (main.iml).

9.       This gives us a fairly bland project:



10.   As before, open up the compiler settings (File > Settings > Compiler) and tell it to never deploy web applications to the server after compilation:


11.   In the Project Settings, add in the utility module (main.iml):


12.   Add a dependency between the web module and the main module:

And, pick the main module:


Leave the Sources unchanged - we'll come back to edit this later.

Set the output path for this module to your WEB-INF folder. Note that Exclude output paths must be checked. This has the effect of marking the output folder as Excluded on the Sources tab (which we'll return to in a bit).

This is an important step as otherwise things don't work very well at all (in particular, your compiled .class files that go into c:\dev\dist\classes never get copied out to c:\dev\ROOT\WEB-INF\classes, and .properties files from the main module are treated as ordinary source files in the web module).

13.   Finally, add a Web facet to our web module. Select the web module, and


14.   Set the properties for the Web facet as:

Note the simplicity of the web resource directories.

Note that I have selected both Build on frame deactivation checkboxes. This forces IDEA to refresh the WEB-INF\classes folders when changes to classes and resources occur within the compiler output folder for the main module.

15.   IDEA can be quite annoying at times. In this case it automatically added a new facet to my utility project, which is exactly what I was trying to avoid. You need to explicitly click on the "Remove Web and don't detect Web Facets in module" link to ensure that this doesn't happen again.
Unfortunately, this detection is geared off of the presence of J2EE descriptor files, and there isn't any way to turn this off globally. The other alternative is to go back into Project Settings and forcibly delete the spurious facet.


16.   Our project now looks like this:


This is another of IDEA's idiosyncrasies ... close the project and reopen it, and you'll see IDEA's revised understanding of the relationship between your projects:


17.   Choose Rebuild Project to get all the files where they're supposed to be.

18.   Go back to the Project Settings, and set the Sources tab of the web module to exclude WEB-INF. This ensures that even the lib folder gets the protection that we gave to the classes folder when we selected Exclude output paths in step 12 earlier.

19.   Run a new Tomcat Run/Debug configuration that is built exactly as before.

20.   To verify that everything works as expected, perform the following tasks:

a.       With the web server running, verify that the page displayed looks like:

b.       Edit User.toString() to read:
public String toString() {       
   return        props.getProperty("user.username.label") + "%:%" + name + ". Age: "        + age;   
}

c.       Choose Build > Compile User.java

d.       Now refresh the page to see the display updated to:

e.       Edit User.properties to change the label value:
user.username.label=[User Name]

f.         Refresh the web page, to see the display updated to:

An interesting point to note here is the effect of deselecting the Build on Frame Deactivation checkboxes. Lets redo steps 20 a, 20 b, and 20 c, above. Now refreshing the page shows us exactly what we saw earlier in step 20 d. But all is not well. If we shut down Tomcat, and restart, we see the page displayed as in step 20 a! What happened? Turns out that the User.java was compiled, and its latest output is found in dist\classes, but that .class file never made it into WEB-INF\classes. You can fix by replacing step 20 c by Build > Make Project.

As far as I can tell - there is no real difference between the two approaches. So, its up to user preference.

 

That's all there is to it! Happy Hunting!

 

 

 

---