Best Practices

This section discusses Best Practices for designing and building Click application. The following topics are covered:

 

Security

For application security it is highly recommended that you use the JEE Servlet path role based security model. While Click pages provide an onSecurityCheck() method for rolling your own security model, the JEE model provides numerous advantages.

These advantages include:

The JEE Servlet security model requires users to be authenticated and in the right roles before they can access secure resources. Relative to many of the JEE specifications the Servlet security model is surprisingly simple.

For example to secure admin pages, you add a security constraint in your web.xml file. This requires users to be in the admin role before they can access to any resources under the admin directory:

<security-constraint>
   <web-resource-collection>
      <web-resource-name>admin</web-resource-name>   
      <url-pattern>/admin/*</url-pattern>
   </web-resource-collection>
   <auth-constraint>
      <role-name>admin</role-name>
   </auth-constraint>
</security-constraint> 
The application user roles are defined in the web.xml file as security-role elements:
<security-role>
   <role-name>admin</role-name>
</security-role>  
The Servlet security model supports three different authentication method: The authentication method is specified in the <login-method> element. For example to use the BASIC authentication method you would specify:
<login-config>
   <auth-method>BASIC</auth-method>
   <realm-name>Admin Realm</realm-name>
</login-config>  
To use the FORM method you also need to specify the path to the login page and the login error page:
<login-config>
   <auth-method>FORM</auth-method>
   <realm-name>Secure Realm</realm-name>
   <form-login-config>
      <form-login-page>/login.htm</form-login-page>
      <form-error-page>/login.htm?auth-error=true</form-error-page>
   </form-login-config>
</login-config> 
In your Click login.htm page you need to include a special j_security_check form which includes the input fields j_username and j_password. For example:
#if ($request.getParameter("auth-error"))
<div style="margin-bottom:1em;margin-top:1em;color:red;">
  Invalid User Name or Password, please try again.<br/>
  Please ensure Caps Lock is off.
</div>
#end

<form method="POST" action="j_security_check" name="form">
<table border="0" style="margin-left:0.25em;">
 <tr>
   <td><label>User Name</label><font color="red">*</font></td>
   <td><input type="text" name="j_username" maxlength="20" style="width:150px;"/></td>
   <td> </td>
  </tr>
  <tr>
   <td><label>User Password</label><font color="red">*</font></td>
   <td><input type="password" name="j_password" maxlength="20" style="width:150px;"/></td>
   <td><input type="image" src="$context/images/login.png" title="Click to Login"/></td>
  </tr>
</table>
</form>

<script type="text/javascript">
  document.form.j_username.focus(); 
</script> 
When using FORM based authentication do NOT put application logic in a Click Login Page class, as the role of this page is to simply render the login form. If you attempt to put navigation logic in your Login Page class, the JEE Container may simply ignore it or throw errors.

Putting this all together below is a web.xml snippet which features security constraints for pages under the admin path and the user path. This configuration uses the FORM method for authentication, and will also redirect unauthorized (403) requests to the /not-authorized.htm page.

<web-app>

    ..

    <error-page>
        <error-code>403</error-code>
        <location>/not-authorized.htm</location>
    </error-page>	

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>admin</web-resource-name>   
            <url-pattern>/admin/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>admin</role-name>
        </auth-constraint>
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>user</web-resource-name>   
            <url-pattern>/user/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>admin</role-name>
            <role-name>user</role-name>
        </auth-constraint>
    </security-constraint>

    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>Secure Zone</realm-name>
        <form-login-config>
            <form-login-page>/login.htm</form-login-page>
            <form-error-page>/login.htm?auth-error=true</form-error-page>
        </form-login-config>
    </login-config>

    <security-role>
        <role-name>admin</role-name>
    </security-role>

    <security-role>
        <role-name>user</role-name>
    </security-role>

</web-app>

Resources

For more information on using security see the resources below:

Page Auto Mapping

You should use the Click page automapping configuration feature. See the Page Automapping topic for details.

Automapping will save manually configure URL path to Page class mappings in your click.xml file. If you follow this convention it is very easy to maintain applications and you can quickly determine what the corresponding Page class is for a page HTML template and visa versa.

An example click.xml automapping configuration is provided below:

<click-app>
  <pages package="com.mycorp.dashboard.page" automapping="true"/>
</click-app> 
To see how the page templates are mapped to Page classes set the application mode to debug and at startup the mappings will be listed out. An example Click startup listing is provided below:
[Click] [debug] automapped pages:
[Click] [debug] /category-tree.htm -> com.mycorp.dashboard.page.CategoryTree
[Click] [debug] /process-list.htm -> com.mycorp.dashboard.page.ProcessList
[Click] [debug] /user-list.htm -> com.mycorp.dashboard.page.UserList  

Navigation

When navigating between Pages using forwards and redirects, you should refer to the target page using the Page class rather than using path. This provides you compile time checking and will save you from having to update path strings in Java code if you move pages about.

To forward to another page using the Page class:

public class CustomerListPage extends Page {

    private ActionLink customerLink = 
        new ActionLink("customerLink", this, "onCustomerClick");
    
    ..
    
    public boolean onCustomerClick() {
        Integer id = customerLink.getValueInteger();
        Customer customer = getCustomerService().getCustomer(id); 
   
        CustomerDetailPage customerDetailPage = (CustomerDetailPage)
            getContext().createPage(CustomerDetailPage.class);

        customerDetailPage.setCustomer(customer);
        setForward(customerDetailPage);
   
        return false;
    }
} 
To redirect to another page using the Page class you can obtain the pages path from the Context. In the example below we are passing through the customer id as a request parameter to the target page.
public class CustomerListPage extends Page {

    private ActionLink customerLink = 
        new ActionLink("customerLink", this, "onCustomerClick");
    
    ..
    
    public boolean onCustomerClick() {
        String id = customerLink.getValueInteger();
   
        String path = getContext().getPagePath(CustomerDetailPage.class);		
        setRedirect(path + "?id=" + id);
   
        return false;
    }
} 
A quick way of redirecting to another page is to simply refer to the target class. The example below logs a user out, by invalidating their session, and then redirects them to the applications home page.
    public boolean onLogoutClick() {
        getContext().getSession().invalidate();
        
        setRedirect(HomePage.class);
        
        return false;
    }

Templating

Use Page templating it is highly recommended. Page templates provide numerous advantages including: To see how to use templates see the Page Templating topic. Also see the Click Examples use of page templating.

Menus

For many applications using the Menu control to centralize application navigation is very useful. Menus are defined in a WEB-INF/menu.xml file which changes very easy.

An menu is typically defined in the a page border template so they are available through out the application. The Menu control does not support HTML rendering, so you need to define a Velocity macro to programattically render the menu. You would call the macro in your border template with code like this:

#writeMenu($rootMenu) 
An advantage of using a macro to render you menu is that you can reuse the code across different applications, and to modify an applications menu you simply need to edit the WEB-INF/menu.xml file. A good place to define your macros is in the webroot /macro.vm file as it is automatically included by Click.

Using macros you can create dynamic menu behaviour such as only rendering menus items a user is authorized to access with isUserInRoles().

#if ($menu.isUserInRoles())
   ..
#end 
You can also use JavaScript to add dynamic behaviour such as drop down menus, for example see the Menu page in Click Examples.

Logging

For page logging you should use Log4j library. An alternative library is the Commons Logging. If you are using Commons Logging please be aware that there have been class loader issues with this library on some application servers. If you are using Commons Logging please make sure you have the latest version.

The best place to define your logger is in a common base page, for example:

public class BasePage extends Page {
	
    protected Logger logger;
	
    public Logger getLogger() {
        if (logger == null) {
            logger = Logger.getLogger(getClass());
        }
        return logger;
    }
} 
Using this pattern all your application bases should extend BasePage so they can use the getLogger() method.
public class CustomerListPage extends BasePage {
	
    public void onGet() {
        try {
            ..
        
        } catch (Exception e) {
            getLogger().error(e);
        }
    }
} 
If you have some very heavy debug statement you should possibly use an isDebugEnabled switch so it is not invoked if debug is not required.
public class CustomerListPage extends BasePage {
	
    public void onGet() {
        if (getLogger().isDebugEnabled()) {
            String msg = ..
            
            getLogger().debug(msg);
        }
        
        ..
    }
} 

Please note the Click logging facility is not designed for application use, and is for Click internal use only. When Click is running in production mode it will not produce any logging output.

Error Handling

In Click unhandled errors are directed to the ErrorPage for display. If applications require additional error handling they can create and register a custom error page in WEB-INF/click.xml. For example:
<pages package="com.mycorp.page" automapping="true"/>
  <page path="click/error.htm" classname="ErrorPage"/> 
</pages> 
Generally application handle transactional errors using service layer code or via a servlet Filter and would not need to include error handling logic in an error page.

Potential uses for a custom error page include custom logging.

For example if an application requires unhandled errors to be logged to an application log (rather than System.out) then a custom ErrorPage could be configured. An example ErrorPage error logging page is provided below:

package com.mycorp.page.ErrorPage;
..

public class ErrorPage extends net.sf.click.util.ErrorPage {
	
    public void onDestory() {
    	Logger.getLogger(getClass()).error(getError());
    }
}