Using the JDBCRealm

Tomcat supports Container Managed Security. You don’t need to package your Servlets or JSPs with ugly authentication code, let Tomcat do it for you!

A Realm is a simply a collection of usernames, passwords and roles. This Q/A deals with how to store this information in a database table and integrate it with Tomcat.

A user has a password and can have multiple roles. You can give access to a resource (or a set of resources, eg. *.jsp) by associating it with one or more roles.

For example: suppose you want all JSP’s under the subdirectory /protected to be accessible by only the users that have the (custom) role member. You can declaratively do this in web.xml:

<web-app>   
   <security-constraint>      
      <web-resource-collection>         
         <web-resource-name>admin</web-resource-name>         
         <url-pattern>/protected/*.jsp</url-pattern>      
      </web-resource-collection>      
      <auth-constraint>         
         <role-name>member</role-name>         
      </auth-constraint>   
   </security-constraint>   
</web-app>

So, where are the users/passwords that have the member role? That depends on the type of Realm you’re using. A MemoryRealm stores this information in a file (see TOMCAT-HOME/conf/tomcat-users.xml). A JDBCRealm uses a database.

So let’s create a web application with the protected directory protected. All members having either role silver or role gold are allowed to access the resources in that directory.

Here is how our new web.xml looks like:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">

<web-app>   
   <security-constraint>      
      <web-resource-collection>         
         <web-resource-name>admin</web-resource-name>         
         <url-pattern>/protected/*</url-pattern>      
      </web-resource-collection>      
      <auth-constraint>         
         <role-name>Silver</role-name>         
         <role-name>Gold</role-name>         
      </auth-constraint>   
   </security-constraint>
   
   <login-config>
      <auth-method>BASIC</auth-method>      
      <realm-name>Log In</realm-name>
   </login-config>
</web-app>

Note the login-config tag. It specifies how a user should authenticate. We use BASIC authentication which will cause a popup to appear whenever an unauthenticated user tries to access one of the protected resources:

Authentication information will be stored in a database and accessed with a JDBC driver. In this example, we use mysql with the driver MySQL Connector/J (formerly mm.mysql).
Download the JAR file (mm.mysql-2.0.4-bin.jar) and store it in [TOMCAT-HOME]/common/lib, if it will be only visible by your web applications or [TOMCAT-HOME]/server/lib if it’s used by Tomcat 4 as well.

Modify [TOMCAT-HOME]/conf/server.xml to configure Tomcat to use the database as an authentication mechanism. You can host the following tag in the Engine, Host or Context element, depending on the scope you want (inside Engine = share across all web applications and all virtual hosts, inside Host = share accross all web applications of that particular host, inside Context = use this Realm only for this web application).

      <Realm className="org.apache.catalina.realm.JDBCRealm" debug="00"
             driverName="org.gjt.mm.mysql.Driver"
             connectionURL="jdbc:mysql://localhost/esusdb"
             connectionName=""
             connectionPassword=""
             userTable="members"
             userNameCol="username"
             userCredCol="password"
             userRoleTable="memberroles"
             roleNameCol="role" /> 
  • driverName specifies the JDBC driver to be used (added previously to the lib directory).
  • connectionURL specifies the database URL to connect to.
  • connectionName and connectionPassword are needed if your database is password protected.
  • userTable specifies the table in your database in which authentication information is stored. It should contain a user per row, with the username/password stored in the columns specified in userNameCol and userCredCol.
  • userRoleTable specifies the table in your database that contains an association between the users and their roles. It should contain at least the columns specified in userNameCol and roleNameCol.

Now create the necessary mysql database tables:

C:mysqlbin>mysql
Welcome to the MySQL monitor.  Commands end with ; or g.
Your MySQL connection id is 5 to server version: 3.23.47-nt
 
Type 'help;' or 'h' for help. Type 'c' to clear the buffer.
 
mysql> create database esusdb;
Query OK, 1 row affected (0.00 sec)
 
mysql> use esusdb;
Database changed
mysql> create table members (uid int(10) not null primary key, username varchar(
20) not null, password varchar(20) not null);
Query OK, 0 rows affected (0.00 sec)
 
mysql> create table memberroles (username varchar(20) not null, role varchar(10)
 not null, primary key (username, role));
Query OK, 0 rows affected (0.01 sec)
 
mysql> desc members;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| uid      | int(10)     |      | PRI | 0       |       |
| username | varchar(20) |      |     |         |       |
| password | varchar(20) |      |     |         |       |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
 
mysql> desc memberroles;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| username | varchar(20) |      | PRI |         |       |
| role     | varchar(10) |      | PRI |         |       |
+----------+-------------+------+-----+---------+-------+
2 rows in set (0.02 sec)
 
mysql>

…and insert some data:

 
mysql> insert into members values (1, 'John', '123456');
Query OK, 1 row affected (0.00 sec)
 
mysql> insert into members values (2, 'Lisa', '654321');
Query OK, 1 row affected (0.00 sec)
 
mysql> insert into memberroles values ('John', 'Gold');
Query OK, 1 row affected (0.00 sec)
 
mysql> insert into memberroles values ('Lisa', 'Silver');
Query OK, 1 row affected (0.00 sec)
 
mysql>

We have created two users: John with role Gold and Lisa with role Silver.

Our web application will use the JDBCRealm to connect to this database and authenticate users.
To create the example web application, create a directory structure as follows:

loginexample2
   |
   +- index.jsp
   |
   +--- protected
   |       |
   |       +- confidential.jsp
   |
   +--- WEB-INF
           |
           +- web.xml

Use the deployment descriptor web.xml that is shown above. The rest of the example JSP’s can be cut’n’pasted here:

loginexample2/index.jsp:

<html>
<body>
Access <a href="./protected/confidential.jsp">protected resource</a>
</body>
</html>

loginexample2/protected/confidential.jsp:

<html>
<body>
<%
   if (request.isUserInRole("Gold")) {
%>
   You have the GOLD role<br>
<% 
   }
   if (request.isUserInRole("Silver")) {
%>
   You have the SILVER role<br>
<%
   }
%>
<br> 
You have successfully accessed the protected resource!
</body>
</html>

Go to http://localhost:8080/loginexample2/index.jsp and try to access the protected resource. A popup window will show up. Notice that, once you’re logged in, you cannot log out, except by closing the browser window.

Using the MemoryRealm

The following example will create a web application containing a jsp that will behave based on who is logged in. The possible users that can be logged in will be defined in an xml file called tomcat-users.xml located in the [TOMCAT-HOME]/conf directory.

This example will recognize two types of custom defined roles: GOLD and SILVER roles. Every user will be assigned one or both roles.

1. Create the following xml file in the [TOMCAT-HOME]/conf directory: tomcat-users.xml (or add the user tags to the existing tomcat-users.xml file):

<tomcat-users>
  <user name="gary"    password="yrag"   roles="gold"/>
  <user name="alicia"  password="aicila" roles="gold"/>
  <user name="john"    password="nhoj"   roles="silver"/>
  <user name="joris"   password="siroj"  roles="gold,silver"/>
</tomcat-users>

2. The JSP is as simple as it can be. It prints out the user that is logged in and prints out to which role it belongs.

confidential.jsp:

<html>
<body>
Hi, <%=request.getUserPrincipal().getName()%><br><br>
<%
   if (request.isUserInRole("gold")) {
%>
      You have the GOLD role<br>
<%
   }
   if (request.isUserInRole("silver")) {
%>
      You have the SILVER role<br>
<%
   }
%>
</body>
</html>

3. Create the WEB-INF/web.xml deployment descriptor.

<?xml version="1.0" encoding="ISO-8859-1"?>
 
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
 
<web-app>
   <security-constraint>
      <web-resource-collection>
         <web-resource-name>admin</web-resource-name>
         <url-pattern>*.jsp</url-pattern>
      </web-resource-collection>
      <auth-constraint>
         <role-name>gold</role-name>
         <role-name>silver</role-name>
      </auth-constraint>
   </security-constraint>
   <login-config>
      <auth-method>BASIC</auth-method>
      <realm-name>Log In</realm-name>
   </login-config>
</web-app>

This deployment descriptor specifies that only users with roles gold or silver are authorized to access the *.jsp resources. The method of authorization is BASIC. Other possible authorization methods are DIGEST and FORM (see other Q/A’s for examples).

4. Now package everything up in a web application loginexample1.war:

C:loginexample1>jar -cvf loginexample1.war *
added manifest
adding: confidential.jsp(in = 275) (out= 160)(deflated 41%)
adding: WEB-INF/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/web.xml(in = 673) (out= 328)(deflated 51%)

and drop loginexample1.war into the /webapps directory.

5. Start up Catalina and point your browser to http://localhost:8080/loginexample1/confidential.jsp.

Because a security constraint is defined in web.xml, specifying that only gold and silver roles can access all jsp pages, and because BASIC authentication is specified, the following window will popup to ask for the username and password:

Try one of the entries in tomcat-users.xml. Notice that you cannot log out, except by closing the browser window.

Changing the default session timeout with Tomcat

You can specify the session timeout in your web.xml deployment descriptor. The <session-timeout%gt; tag specifies the number of minutes of inactivity that the container will allow before the HttpSession object becomes invalid.

eg.

    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>

You can also set your own timeout interval in your servlet or jsp:

<html>
<body>
<% 
   session.setMaxInactiveInterval(30);
%>
</body>
</html>

Controlling what and where Tomcat logs information

Log files are written by the Tomcat valve AccessLogValve. A valve, Tomcat only, is a Java class that preprocesses access requests. You can associate valves in server.xml to the containers engine, host and context.

To change the location of the log files:

Search TOMCAT_HOME/conf/server.xml for “AccessLogValve”. Specify the attribute directory as shown in this example:

   <Valve className="org.apache.catalina.valves.AccessLogValve"
          directory="c:\tomcatlogs"  prefix="localhost_access_log." suffix=".txt"
          pattern="common"/>

This will cause all the access logs of the hosting container to be written to c:\tomcatlogs.

To change the type of information written in the log files:

Possible attributes for the AccessLogValve are the following.

  • directory: the directory to where the log files are written, can be relative to CATALINA_HOME or absolute, default value: logs
  • pattern: specifies what type of infomration a log line should contain. You can combine any of the following patterns:
    %a - Remote IP address 
    %A - Local IP address 
    %b - Bytes sent, excluding HTTP headers, or '-' if zero 
    %B - Bytes sent, excluding HTTP headers 
    %h - Remote host name (or IP address if resolveHosts is false) 
    %H - Request protocol 
    %l - Remote logical username from identd (always returns '-') 
    %m - Request method (GET, POST, etc.) 
    %p - Local port on which this request was received 
    %q - Query string (prepended with a '?' if it exists) 
    %r - First line of the request (method and request URI) 
    %s - HTTP status code of the response 
    %t - Date and time, in Common Log Format 
    %u - Remote user that was authenticated (if any), else '-' 
    %U - Requested URL path 
    %v - Local server name 
    

    Specifying common is the same as specifying %h %l %u %t “%r” %s %b.
    For example:
    With pattern common:

    127.0.0.1 - - [21/Jul/2002:20:16:31 -0800] "GET /index.html HTTP/1.1" 200 6827
    127.0.0.1 - - [21/Jul/2002:20:16:31 -0800] "GET /tomcat.gif HTTP/1.1" 200 1934
    127.0.0.1 - - [21/Jul/2002:20:16:31 -0800] "GET /jakarta-banner.gif HTTP/1.1" 304 -
    127.0.0.1 - - [21/Jul/2002:20:16:32 -0800] "GET /tomcat-power.gif HTTP/1.1" 304 -
    

    With pattern Request from %a: %r:

    Request from 127.0.0.1: GET /index.html HTTP/1.1
    Request from 127.0.0.1: GET /tomcat.gif HTTP/1.1
    Request from 127.0.0.1: GET /jakarta-banner.gif HTTP/1.1
    Request from 127.0.0.1: GET /tomcat-power.gif HTTP/1.1
    
  • prefix: a string that should be added to the start of each log filename, default value: access_log
  • suffix: a string that should be added to the end of each log filename, default value: empty
  • resolveHosts: a boolean value, if set to true, it will look up the corresponding host name for the requesting IP address. Use it only if absolutely necessary, it will slow down Tomcat a lot.

Configuring Tomcat to use SSL

SSL stands for Secure Socket Layer. It allows the communication between your browser and the webserver to be encrypted. Tomcat is a Servlet/JSP container but is just a simple webserver as well. Typically, the Apache web server is used to serve static pages while the servlet/JSP requests are redirected to the Tomcat container. Then you can simply configure SSL on the Apache Web server level, in which case you would not need to do SSL configuration on Tomcat itself. It is useful however if you are running Tomcat standalone.

1) Install JSSE

If you are not using JDK1.4+, you need to install JSSE separately. I was using JDK1.3.1 to create this example, and to make it easy, I copied to files jcert.jar, jnet.jar, jsse.jar into c:\jdk1.3.1\jre\lib\ext. If you do this, you don’t need to fiddle around with the classpath. You can d/l JSSE here: http://java.sun.com/products/jsse/.

2) Create a keystore file that contains your certificate:

C:\Program Files\Apache Tomcat 4.0>keytool -genkey -alias tomcat -keyalg RSA 
                                           -keystore tomcatkeystore.kst
Enter keystore password:  123456
What is your first and last name?
  [Unknown]:  Joris Van den Bogaert
What is the name of your organizational unit?
  [Unknown]:  Esus
What is the name of your organization?
  [Unknown]:  Esus
What is the name of your City or Locality?
  [Unknown]:  Brussels
What is the name of your State or Province?
  [Unknown]:
What is the two-letter country code for this unit?
  [Unknown]:  BE
Is <CN=Joris Van den Bogaert, OU=Esus, O=Esus, L=Brussels, ST=Unknown, C=BE> correct?
  [no]:  yes
 
Enter key password for <tomcat>
        (RETURN if same as keystore password):  123456

3) Modify TOMCAT-HOME/bin/server.xml

You can just uncomment the SSL connector. Modify it so that it points to the keystore file that you just created:

    <!-- Define an SSL HTTP/1.1 Connector on port 8443 -->
    <Connector className="org.apache.catalina.connector.http.HttpConnector"
               port="8443" minProcessors="5" maxProcessors="75"
               enableLookups="true"
	       acceptCount="10" debug="0" scheme="https" secure="true">
      <Factory className="org.apache.catalina.net.SSLServerSocketFactory"
               clientAuth="false" protocol="TLS"
               keystoreFile="tomcatkeystore.kst" 
               keystorePass="123456"/>
    </Connector>

Note 1: 8443 is the port number that Tomcat will listen to for secure connections. If you want
to use another port number, make sure you also change the redirectPort attribute in the non-SSL connector to point to the port you choose.

Note 2: If you didn’t specify the option -keystore when creating your certificate, the keystore would have been stored in your home directory as .keystore. In that case, you don’t need to specify the keystoreFile nor keystorePass.

4) Restart Tomcat

5) Check it out: https://localhost:8443

Adding a JMenuBar to the two components of a JSplitPane

Main.java:

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
 
public class Main extends JFrame
{
   JSplitPane splitPane;
  
   public Main() {
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            System.exit(0);
         }
      });
 
      JPanel firstPanel = createPanel();
      JPanel secondPanel = createPanel();
      splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, firstPanel, secondPanel);
 
      createMainMenuBar();
 
      getContentPane().add(splitPane);
      setSize(300, 300);
      setVisible(true);
 
      splitPane.setDividerLocation(0.5); 
   }
 
   public void createMainMenuBar() {
      JMenuBar mainBar = new JMenuBar();
      JMenu menu = new JMenu(&amp;quot;JSplitPane&amp;quot;);
      JMenuItem item1 = new JMenuItem(&amp;quot;HORIZONTAL_SPLIT&amp;quot;);
      JMenuItem item2 = new JMenuItem(&amp;quot;VERTICAL_SPLIT&amp;quot;);
      menu.add(item1);
      menu.add(item2);
      mainBar.add(menu);
      setJMenuBar(mainBar);
 
      item1.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            splitPane.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
         }
      });
 
      item2.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
         }
      });
   }
 
   public JPanel createPanel() {
      JPanel panel = new JPanel();
      panel.setLayout(new BorderLayout());
      JMenuBar menuBar = new JMenuBar();
      for (int i=0; i&amp;lt;3; i++) {
         JMenu menu = new JMenu(&amp;quot;JMenu &amp;quot; + (i+1));
         for (int j=0; j&amp;lt;3; j++) {
            JMenuItem item = new JMenuItem(&amp;quot;JMenuItem &amp;quot; + (j+1));
            menu.add(item);
         }
         menuBar.add(menu);
      }
      panel.add(BorderLayout.NORTH, menuBar);
 
      return panel;
   }
 
   public static void main(String []args) {
      Main main = new Main();
   }
}

Showing the trademark symbol in a JLabel

Main.java:

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
   
public class Main extends JFrame {
   public Main() {
      getContentPane().setLayout(new FlowLayout());
 
      final JLabel label = new JLabel("Javau2122");
      getContentPane().add(label);
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      pack();
   }
 
   public static void main(String[] args) {
      (new Main()).show();
   }
}

Showing the copyright symbol in a JLabel

Main.java:

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
   
public class Main extends JFrame {
   public Main() {
      getContentPane().setLayout(new FlowLayout());
 
      final JLabel label = new JLabel("Copyrightu00a9, All rights reserved");
      getContentPane().add(label);
 
      label.putClientProperty("html", null);
  
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      pack();
   }
 
   public static void main(String[] args) {
      (new Main()).show();
   }
}

Underlining a cell in a JTable

The easiest way is to use HTML in your text, check How do I create a JLabel with the text underlined?

But here’s a code sample that does not make use of the HTML feature. It underlines all Integers in the JTable whose values are between 1970 and 1980.

Main.java:

import javax.swing.table.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
 
public class Main extends JFrame 
{
   public Main() {
      super("Table example, Wines from Bordeaux");
 
      Object[][] tabledata = {
            { "Chateau Meyney, St. Estephe", 	   new Integer(1994), "$18.75"},
            { "Chateau Montrose, St. Estephe", 	   new Integer(1975), "$54.25" },
            { "Chateau Gloria, St. Julien", 	   new Integer(1993), "$22.99" },
            { "Chateau Beychevelle, St. Julien",   new Integer(1970), "$61.63" },
            { "Chateau La Tour de Mons, Margeaux", new Integer(1975), "$57.03" },
            { "Chateau Brane-Cantenac, Margeaux",  new Integer(1978), "$49.92" },
      };
 
      String columnheaders[] = { "Wine", "Vintage", "Price" };
 
      JTable table = new JTable(tabledata, columnheaders);
      table.setPreferredScrollableViewportSize(new Dimension(500, 70));
 
      UnderlineTableCellRenderer renderer = new UnderlineTableCellRenderer();
      table.setDefaultRenderer(Object.class, renderer);
      table.setRowHeight(30);
 
      JScrollPane scrollPane = new JScrollPane(table);
      getContentPane().add(scrollPane);
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            System.exit(0);
         }
      });
 
      pack();
   }
 
   public static void main(String []args) {
      Main main = new Main();
      main.show();
   }
}
 
class UnderlineTableCellRenderer extends DefaultTableCellRenderer 
{
   public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                                                  boolean hasFocus, int row, int column) {
      if (value instanceof Integer) {
         Integer amount = (Integer) value;
         if (amount.intValue() > 1970 && amount.intValue() < 1980) {
            return new UnderlinedJLabel("" + amount.intValue());
         }
      }
 
      return super.getTableCellRendererComponent
                      (table, value, isSelected, hasFocus, row, column);
   }
}
 
class UnderlinedJLabel extends JLabel
{
   public UnderlinedJLabel() {
   }
 
   public UnderlinedJLabel(Icon image) {
      super(image);
   }
 
   public UnderlinedJLabel(Icon image, int horizontalAlignment) {
      super(image, horizontalAlignment);
   }
 
   public UnderlinedJLabel(String text) {
      super(text);
   }
 
   public UnderlinedJLabel(String text, Icon icon, int horizontalAlignment) {
      super(text, icon, horizontalAlignment);
   }
 
   public UnderlinedJLabel(String text, int horizontalAlignment) {
      super(text, horizontalAlignment);
   }
 
   public void paint(Graphics g) {
      super.paint(g);
      underline(g);
   }
 
   protected void underline(Graphics g) {
      Insets insets = getInsets();
      FontMetrics fm = g.getFontMetrics();
      Rectangle textR = new Rectangle();
      Rectangle viewR = new Rectangle(
                                  insets.left, 
                                  insets.top, 
                                  getWidth() - (insets.right + insets.left), 
                                  getHeight() - (insets.bottom + insets.top));
  
      // compute and return location of the icons origin,
      // the location of the text baseline, and a possibly clipped
      // version of the compound label string.  Locations are computed 
      // relative to the viewR rectangle.
      String text = SwingUtilities.layoutCompoundLabel(
                         this,                        // this JLabel
                         fm,                          // current FontMetrics
                         getText(),                   // text
                         getIcon(),                   // icon
                         getVerticalAlignment(),      
                         getHorizontalAlignment(),
                         getVerticalTextPosition(),
                         getHorizontalTextPosition(), 
                         viewR,                       
                         new Rectangle(),             // don't care about icon rectangle
                         textR,                       // resulting text locations
                         getText() == null ? 0 : ((Integer)UIManager.get("Button.textIconGap")).intValue());
 
      // draw line
      int textShiftOffset = ((Integer) UIManager.get("Button.textShiftOffset")).intValue();
      g.fillRect(textR.x +
                 textShiftOffset - 4,
                 textR.y + fm.getAscent() + textShiftOffset + 2,
                 textR.width, 
                 1);
   }
}

Creating an underlined JLabel without using HTML

Main.java:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
 
public class Main extends JFrame
{
   public Main() {
      getContentPane().setLayout(new GridLayout(1, 2));
      JPanel panel1 = new JPanel();
      panel1.setLayout(new GridLayout(5, 1));
      panel1.add(new JLabel(&quot;JLabel/icon/LEFT&quot;, 
                           new ImageIcon(&quot;tiffIcon.gif&quot;), SwingConstants.LEFT));
      panel1.add(new JLabel(&quot;JLabel/icon/CENTER&quot;, 
                           new ImageIcon(&quot;tiffIcon.gif&quot;), SwingConstants.CENTER));
      panel1.add(new JLabel(&quot;JLabel/icon/RIGHT&quot;, 
                           new ImageIcon(&quot;tiffIcon.gif&quot;), SwingConstants.RIGHT));
      panel1.add(new JLabel(&quot;JLabel/icon/LEADING&quot;, 
                           new ImageIcon(&quot;tiffIcon.gif&quot;), SwingConstants.LEADING));
      panel1.add(new JLabel(&quot;JLabel/icon/TRAILING&quot;, 
                           new ImageIcon(&quot;tiffIcon.gif&quot;), SwingConstants.TRAILING));
 
      getContentPane().add(new JScrollPane(panel1));
 
      JPanel panel2 = new JPanel();
      panel2.setLayout(new GridLayout(5, 1));
      panel2.add(new UnderlinedJLabel(&quot;JLabel/icon/LEFT&quot;, 
                                      new ImageIcon(&quot;tiffIcon.gif&quot;), SwingConstants.LEFT));
      panel2.add(new UnderlinedJLabel(&quot;JLabel/icon/CENTER&quot;, 
                                      new ImageIcon(&quot;tiffIcon.gif&quot;), SwingConstants.CENTER));
      panel2.add(new UnderlinedJLabel(&quot;JLabel/icon/RIGHT&quot;, 
                                      new ImageIcon(&quot;tiffIcon.gif&quot;), SwingConstants.RIGHT));
      panel2.add(new UnderlinedJLabel(&quot;JLabel/icon/LEADING&quot;, 
                                      new ImageIcon(&quot;tiffIcon.gif&quot;), SwingConstants.LEADING));
      panel2.add(new UnderlinedJLabel(&quot;JLabel/icon/TRAILING&quot;, 
                                      new ImageIcon(&quot;tiffIcon.gif&quot;), SwingConstants.TRAILING));
 
      getContentPane().add(new JScrollPane(panel2));
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            System.exit(1);
         }
      });
   }
 
   public static void main(String []args) {
      Main main = new Main();
      main.setSize(300, 150);
      main.setVisible(true);   
   }
}
 
class UnderlinedJLabel extends JLabel
{
   public UnderlinedJLabel() {
   }
 
   public UnderlinedJLabel(Icon image) {
      super(image);
   }
 
   public UnderlinedJLabel(Icon image, int horizontalAlignment) {
      super(image, horizontalAlignment);
   }
 
   public UnderlinedJLabel(String text) {
      super(text);
   }

   public UnderlinedJLabel(String text, Icon icon, int horizontalAlignment) {
      super(text, icon, horizontalAlignment);
   }
 
   public UnderlinedJLabel(String text, int horizontalAlignment) {
      super(text, horizontalAlignment);
   }
 
   public void paint(Graphics g) {
      super.paint(g);
      underline(g);
   }
 
   protected void underline(Graphics g) {
      Insets insets = getInsets();
      FontMetrics fm = g.getFontMetrics();
      Rectangle textR = new Rectangle();
      Rectangle viewR = new Rectangle(
                                  insets.left, 
                                  insets.top, 
                                  getWidth() - (insets.right + insets.left), 
                                  getHeight() - (insets.bottom + insets.top));
  
      // compute and return location of the icons origin,
      // the location of the text baseline, and a possibly clipped
      // version of the compound label string.  Locations are computed 
      // relative to the viewR rectangle.
      String text = SwingUtilities.layoutCompoundLabel(
                         this,                        // this JLabel
                         fm,                          // current FontMetrics
                         getText(),                   // text
                         getIcon(),                   // icon
                         getVerticalAlignment(),      
                         getHorizontalAlignment(),
                         getVerticalTextPosition(),
                         getHorizontalTextPosition(), 
                         viewR,                       
                         new Rectangle(),             // don't care about icon rectangle
                         textR,                       // resulting text locations
                         getText() == null ? 0 : 
                            ((Integer)UIManager.get(&quot;Button.textIconGap&quot;)).intValue());
 
      // draw line
      int textShiftOffset = ((Integer) UIManager.get(&quot;Button.textShiftOffset&quot;)).intValue();
      g.fillRect(textR.x +
                 textShiftOffset - 4,
                 textR.y + fm.getAscent() + textShiftOffset + 2,
                 textR.width, 
                 1);
   }
}