You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by gl...@apache.org on 2003/03/04 05:09:17 UTC

cvs commit: jakarta-tomcat-4.0/webapps/tomcat-docs/config manager.xml

glenn       2003/03/03 20:09:17

  Modified:    .        RELEASE-NOTES-4.1.txt
               catalina/src/share/org/apache/catalina/session
                        JDBCStore.java StoreBase.java
               webapps/tomcat-docs/config manager.xml
  Log:
  [4.1.22] JDBCStore:
           Optimize keys() method SQL WHERE clause.
           Implement a new db field so that the session can be localized to
           the Engine, Host, and Context (Web Application).
  
  [4.1.22] #17591
           JDBCStore
           Synchronize methods which use db so that use of db connection is
           thread safe.
  
  [4.1.22] #17587
           Session Manager StoreBase
           Fix a NPE bug when the background thread expires sessions.
  
  Revision  Changes    Path
  1.58      +14 -1     jakarta-tomcat-4.0/RELEASE-NOTES-4.1.txt
  
  Index: RELEASE-NOTES-4.1.txt
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat-4.0/RELEASE-NOTES-4.1.txt,v
  retrieving revision 1.57
  retrieving revision 1.58
  diff -u -r1.57 -r1.58
  --- RELEASE-NOTES-4.1.txt	25 Feb 2003 10:21:13 -0000	1.57
  +++ RELEASE-NOTES-4.1.txt	4 Mar 2003 04:09:17 -0000	1.58
  @@ -700,6 +700,19 @@
   [4.1.21] MbeanUtils:
            Add JSR 77 servlet registration.
   
  +[4.1.22] JDBCStore:
  +         Optimize keys() method SQL WHERE clause.
  +         Implement a new db field so that the session can be localized to
  +         the Engine, Host, and Context (Web Application).
  +
  +[4.1.22] #17591
  +         JDBCStore
  +         Synchronize methods which use db so that use of db connection is
  +         thread safe.
  +
  +[4.1.22] #17587 
  +         Session Manager StoreBase
  +         Fix a NPE bug when the background thread expires sessions.
   
   ----------------
   Coyote Bug Fixes:
  
  
  
  1.10      +266 -185  jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/JDBCStore.java
  
  Index: JDBCStore.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/JDBCStore.java,v
  retrieving revision 1.9
  retrieving revision 1.10
  diff -u -r1.9 -r1.10
  --- JDBCStore.java	11 Feb 2003 11:54:12 -0000	1.9
  +++ JDBCStore.java	4 Mar 2003 04:09:17 -0000	1.10
  @@ -80,6 +80,7 @@
   import java.sql.PreparedStatement;
   import java.sql.ResultSet;
   import java.sql.SQLException;
  +import java.util.ArrayList;
   import org.apache.catalina.Container;
   import org.apache.catalina.LifecycleException;
   import org.apache.catalina.Loader;
  @@ -105,6 +106,11 @@
       protected static String info = "JDBCStore/1.0";
   
       /**
  +     * Context name associated with this Store
  +     */
  +    private String name = null;
  +
  +    /**
        * Name to register for this Store, used for logging.
        */
       protected static String storeName = "JDBCStore";
  @@ -137,6 +143,11 @@
       protected String sessionTable = "tomcat$sessions";
   
       /**
  +     * Column to use for /Engine/Host/Context name
  +     */
  +    protected String sessionAppCol = "app";
  +
  +    /**
        * Id column to use.
        */
       protected String sessionIdCol = "id";
  @@ -203,6 +214,28 @@
       }
   
       /**
  +     * Return the name for this instance (built from container name)
  +     */
  +    public String getName() {
  +        if (name == null) {
  +            Container container = manager.getContainer();
  +            String contextName = container.getName();
  +            String hostName = "";
  +            String engineName = "";
  +
  +            if (container.getParent() != null) {
  +                Container host = container.getParent();
  +                hostName = host.getName();
  +                if (host.getParent() != null) {
  +                    engineName = host.getParent().getName();
  +                }
  +            }
  +            name = "/" + engineName + "/" + hostName + contextName;
  +        }
  +        return name;
  +    }
  +
  +    /**
        * Return the thread name for this Store.
        */
       public String getThreadName() {
  @@ -278,6 +311,26 @@
       }
   
       /**
  +     * Set the App column for the table.
  +     *
  +     * @param sessionAppCol the column name
  +     */
  +    public void setSessionAppCol(String sessionAppCol) {
  +        String oldSessionAppCol = this.sessionAppCol;
  +        this.sessionAppCol = sessionAppCol;
  +        support.firePropertyChange("sessionAppCol",
  +                                   oldSessionAppCol,
  +                                   this.sessionAppCol);
  +    }
  +
  +    /**
  +     * Return the Id column for the table.
  +     */
  +    public String getSessionAppCol() {
  +        return(this.sessionAppCol);
  +    }
  +
  +    /**
        * Set the Id column for the table.
        *
        * @param sessionIdCol the column name
  @@ -388,45 +441,49 @@
        */
       public String[] keys() throws IOException {
           String keysSql =
  -            "SELECT COUNT(s."+sessionIdCol+"), c."+sessionIdCol+
  -            " FROM "+sessionTable+" s, "+sessionTable+" c"+
  -            " GROUP BY c."+sessionIdCol;
  -
  -        Connection _conn = getConnection();
  +            "SELECT " + sessionIdCol + " FROM " + sessionTable +
  +            " WHERE " + sessionAppCol + " = ?";
           ResultSet rst = null;
           String keys[] = null;
           int i;
   
  -        if(_conn == null)
  -            return(new String[0]);
  +        synchronized(this) {
  +            Connection _conn = getConnection();
   
  -        try {
  -            if(preparedKeysSql == null)
  -                preparedKeysSql = _conn.prepareStatement(keysSql);
  +            if(_conn == null) {
  +                return(new String[0]);
  +            }
   
  -            rst = preparedKeysSql.executeQuery();
  -            if (rst != null && rst.next()) {
  -                keys = new String[rst.getInt(1)];
  -                keys[0] = rst.getString(2);
  -                i=1;
  -
  -                while(rst.next())
  -                    keys[i++] = rst.getString(2);
  -            } else {
  -                keys = new String[0];
  -            }
  -        } catch(SQLException e) {
  -            log(sm.getString(getStoreName()+".SQLException", e));
  -        } finally {
               try {
  -                if(rst != null)
  -                    rst.close();
  +                if(preparedKeysSql == null) {
  +                    preparedKeysSql = _conn.prepareStatement(keysSql);
  +                }
  +
  +                preparedKeysSql.setString(1, getName());
  +                rst = preparedKeysSql.executeQuery();
  +                if (rst != null && rst.next()) {
  +                    ArrayList tmpkeys = new ArrayList();
  +                    while(rst.next()) {
  +                        tmpkeys.add(rst.getString(1));
  +                    }
  +                    keys = (String[])
  +                        tmpkeys.toArray(new String[tmpkeys.size()]);
  +                } else {
  +                    keys = new String[0];
  +                }
               } catch(SQLException e) {
  -                ;
  -            }
  +                log(sm.getString(getStoreName()+".SQLException", e));
  +            } finally {
  +                try {
  +                    if(rst != null) {
  +                        rst.close();
  +                    }
  +                } catch(SQLException e) {
  +                    ;
  +                }
   
  -            release(_conn);
  -            _conn = null;
  +                release(_conn);
  +            }
           }
   
           return(keys);
  @@ -441,35 +498,41 @@
        */
       public int getSize() throws IOException {
           int size = 0;
  -        String sizeSql = "SELECT COUNT("+sessionIdCol+
  -            ") FROM ".concat(sessionTable);
  -        Connection _conn = getConnection();
  +        String sizeSql = 
  +            "SELECT COUNT(" + sessionIdCol + ") FROM " + sessionTable +
  +            " WHERE " + sessionAppCol + " = ?";
           ResultSet rst = null;
   
  -        if(_conn == null)
  -            return(size);
  +        synchronized(this) {
  +            Connection _conn = getConnection();
   
  -        try {
  -            if(preparedSizeSql == null)
  -                preparedSizeSql = _conn.prepareStatement(sizeSql);
  +            if(_conn == null) {
  +                return(size);
  +            }
   
  -            rst = preparedSizeSql.executeQuery();
  -            if (rst.next())
  -                size = rst.getInt(1);
  -        } catch(SQLException e) {
  -            log(sm.getString(getStoreName()+".SQLException", e));
  -        } finally {
               try {
  -                if(rst != null)
  -                    rst.close();
  +                if(preparedSizeSql == null) {
  +                    preparedSizeSql = _conn.prepareStatement(sizeSql);
  +                }
  +
  +                preparedSizeSql.setString(1, getName());
  +                rst = preparedSizeSql.executeQuery();
  +                if (rst.next()) {
  +                    size = rst.getInt(1);
  +                }
               } catch(SQLException e) {
  -                ;
  -            }
  +                log(sm.getString(getStoreName()+".SQLException", e));
  +            } finally {
  +                try {
  +                    if(rst != null)
  +                        rst.close();
  +                } catch(SQLException e) {
  +                    ;
  +                }
   
  -            release(_conn);
  -            _conn = null;
  +                release(_conn);
  +            }
           }
  -
           return(size);
       }
   
  @@ -485,76 +548,78 @@
       public Session load(String id)
           throws ClassNotFoundException, IOException {
           ResultSet rst = null;
  -        Connection _conn = getConnection();
           StandardSession _session = null;
           Loader loader = null;
           ClassLoader classLoader = null;
           ObjectInputStream ois = null;
           BufferedInputStream bis = null;
           Container container = manager.getContainer();
  -        String loadSql = "SELECT "+sessionIdCol+
  -            ", "+sessionDataCol+" FROM "+sessionTable+
  -            " WHERE "+sessionIdCol+" = ?";
  +        String loadSql =
  +            "SELECT " + sessionIdCol + ", " + sessionDataCol + " FROM " +
  +            sessionTable + " WHERE " + sessionIdCol + " = ? AND " +
  +            sessionAppCol + " = ?";
  +
  +        synchronized(this) {
  +            Connection _conn = getConnection();
  +            if(_conn == null) {
  +                return(null);
  +            }
  +
  +            try {
  +                if(preparedLoadSql == null) {
  +                    preparedLoadSql = _conn.prepareStatement(loadSql);
  +                }
   
  -        if(_conn == null)
  -            return(null);
  +                preparedLoadSql.setString(1, id);
  +                preparedLoadSql.setString(2, getName());
  +                rst = preparedLoadSql.executeQuery();
  +                if (rst.next()) {
  +                    bis = new BufferedInputStream(rst.getBinaryStream(2));
   
  -        try {
  -            if(preparedLoadSql == null)
  -                preparedLoadSql = _conn.prepareStatement(loadSql);
  +                    if (container != null) {
  +                        loader = container.getLoader();
  +                    }
  +                    if (loader != null) {
  +                        classLoader = loader.getClassLoader();
  +                    }
  +                    if (classLoader != null) {
  +                        ois = new CustomObjectInputStream(bis,
  +                                                          classLoader);
  +                    } else {
  +                        ois = new ObjectInputStream(bis);
  +                    }
   
  -            preparedLoadSql.setString(1, id);
  -            rst = preparedLoadSql.executeQuery();
  -            if (rst.next()) {
  -                bis = new BufferedInputStream(rst.getBinaryStream(2));
  -
  -                if (container != null)
  -                    loader = container.getLoader();
  -
  -                if (loader != null)
  -                    classLoader = loader.getClassLoader();
  -
  -                if (classLoader != null)
  -                    ois = new CustomObjectInputStream(bis,
  -                                                      classLoader);
  -                else
  -                    ois = new ObjectInputStream(bis);
  -            } else if (debug > 0) {
  -                log(getStoreName()+": No persisted data object found");
  -            }
  -        } catch(SQLException e) {
  -            log(sm.getString(getStoreName()+".SQLException", e));
  -        } finally {
  -            try {
  -                if(rst != null)
  -                    rst.close();
  -            } catch(SQLException e) {
  -                ;
  -            }
  +                    if (debug > 0) {
  +                        log(sm.getString(getStoreName()+".loading",
  +                                         id, sessionTable));
  +                    }
   
  -            release(_conn);
  -            _conn = null;
  -        }
  +                    _session = (StandardSession) manager.createEmptySession();
  +                    _session.readObjectData(ois);
  +                    _session.setManager(manager);
   
  -        if(ois != null) {
  -            try {
  -                _session = (StandardSession) manager.createEmptySession();
  -                _session.readObjectData(ois);
  -                _session.setManager(manager);
  +                } else if (debug > 0) {
  +                    log(getStoreName()+": No persisted data object found");
  +                }
  +            } catch(SQLException e) {
  +                log(sm.getString(getStoreName()+".SQLException", e));
               } finally {
  +                try {
  +                    if(rst != null) {
  +                        rst.close();
  +                    }
  +                } catch(SQLException e) {
  +                    ;
  +                }
                   if (ois != null) {
                       try {
                           ois.close();
  -                        bis = null;
                       } catch (IOException e) {
                           ;
                       }
                   }
  +                release(_conn);
               }
  -
  -            if (debug > 0)
  -                log(sm.getString(getStoreName()+".loading",
  -                                 id, sessionTable));
           }
   
           return(_session);
  @@ -570,28 +635,35 @@
        * @exception IOException if an input/output error occurs
        */
       public void remove(String id) throws IOException {
  -        Connection _conn = getConnection();
  -        String removeSql = "DELETE FROM "+sessionTable+" WHERE "+
  -            sessionIdCol+" = ?";
  +        String removeSql =
  +            "DELETE FROM " + sessionTable + " WHERE " + sessionIdCol +
  +            " = ?  AND " + sessionAppCol + " = ?";
   
  -        if(_conn == null)
  -            return;
  +        synchronized(this) {
  +            Connection _conn = getConnection();
   
  -        try {
  -            if(preparedRemoveSql == null)
  -                preparedRemoveSql = _conn.prepareStatement(removeSql);
  +            if(_conn == null) {
  +                return;
  +            }
  +
  +            try {
  +                if(preparedRemoveSql == null) {
  +                    preparedRemoveSql = _conn.prepareStatement(removeSql);
  +                }
   
  -            preparedRemoveSql.setString(1, id);
  -            preparedRemoveSql.execute();
  -        } catch(SQLException e) {
  -            log(sm.getString(getStoreName()+".SQLException", e));
  -        } finally {
  -            release(_conn);
  -            _conn = null;
  +                preparedRemoveSql.setString(1, id);
  +                preparedRemoveSql.setString(2, getName());
  +                preparedRemoveSql.execute();
  +            } catch(SQLException e) {
  +                log(sm.getString(getStoreName()+".SQLException", e));
  +            } finally {
  +                release(_conn);
  +            }
           }
   
  -        if (debug > 0)
  +        if (debug > 0) {
               log(sm.getString(getStoreName()+".removing", id, sessionTable));
  +        }
       }
   
       /**
  @@ -600,22 +672,27 @@
        * @exception IOException if an input/output error occurs
        */
       public void clear() throws IOException {
  -        Connection _conn = getConnection();
  -        String clearSql = "DELETE FROM ".concat(sessionTable);
  +        String clearSql =
  +            "DELETE FROM " + sessionTable + " WHERE " + sessionAppCol + " = ?";
   
  -        if(_conn == null)
  -            return;
  +        synchronized(this) {
  +            Connection _conn = getConnection();
  +            if(_conn == null) {
  +                return;
  +            }
   
  -        try {
  -            if(preparedClearSql == null)
  -                preparedClearSql = _conn.prepareStatement(clearSql);
  +            try {
  +                if(preparedClearSql == null) {
  +                    preparedClearSql = _conn.prepareStatement(clearSql);
  +                }
   
  -            preparedClearSql.execute();
  -        } catch(SQLException e) {
  -            log(sm.getString(getStoreName()+".SQLException", e));
  -        } finally {
  -            release(_conn);
  -            _conn = null;
  +                preparedClearSql.setString(1, getName());
  +                preparedClearSql.execute();
  +            } catch(SQLException e) {
  +                log(sm.getString(getStoreName()+".SQLException", e));
  +            } finally {
  +                release(_conn);
  +            }
           }
       }
   
  @@ -626,69 +703,72 @@
        * @exception IOException if an input/output error occurs
        */
       public void save(Session session) throws IOException {
  -        String saveSql = "INSERT INTO "+sessionTable+" ("+
  -            sessionIdCol+", "+
  -            sessionDataCol+", "+
  -            sessionValidCol+", "+
  -            sessionMaxInactiveCol+", "+
  -            sessionLastAccessedCol+") VALUES (?, ?, ?, ?, ?)";
  -        Connection _conn = getConnection();
  +        String saveSql =
  +            "INSERT INTO " + sessionTable + " (" + sessionIdCol + ", " +
  +            sessionAppCol + ", " +
  +            sessionDataCol + ", " +
  +            sessionValidCol + ", " +
  +            sessionMaxInactiveCol + ", " +
  +            sessionLastAccessedCol + ") VALUES (?, ?, ?, ?, ?, ?)";
           ObjectOutputStream oos = null;
           ByteArrayOutputStream bos = null;
           ByteArrayInputStream bis = null;
           InputStream in = null;
   
  -        if(_conn == null)
  -            return;
  +        synchronized(this) {
  +            Connection _conn = getConnection();
  +            if(_conn == null) {
  +                return;
  +            }
   
  -        // If sessions already exist in DB, remove and insert again.
  -        // TODO:
  -        // * Check if ID exists in database and if so use UPDATE.
  -        remove(session.getId());
  +            // If sessions already exist in DB, remove and insert again.
  +            // TODO:
  +            // * Check if ID exists in database and if so use UPDATE.
  +            remove(session.getId());
   
  -        try {
  -            bos = new ByteArrayOutputStream();
  -            oos = new ObjectOutputStream(new BufferedOutputStream(bos));
  +            try {
  +                bos = new ByteArrayOutputStream();
  +                oos = new ObjectOutputStream(new BufferedOutputStream(bos));
   
  -            ((StandardSession)session).writeObjectData(oos);
  -            oos.close();
  +                ((StandardSession)session).writeObjectData(oos);
  +                oos.close();
   
  -            byte[] obs = bos.toByteArray();
  -            int size = obs.length;
  -            bis = new ByteArrayInputStream(obs, 0, size);
  -            in = new BufferedInputStream(bis, size);
  -
  -            if(preparedSaveSql == null)
  -                preparedSaveSql = _conn.prepareStatement(saveSql);
  -
  -            preparedSaveSql.setString(1, session.getId());
  -            preparedSaveSql.setBinaryStream(2, in, size);
  -            preparedSaveSql.setString(3, session.isValid()?"1":"0");
  -            preparedSaveSql.setInt(4, session.getMaxInactiveInterval());
  -            preparedSaveSql.setLong(5, session.getLastAccessedTime());
  -            preparedSaveSql.execute();
  -        } catch(SQLException e) {
  -            log(sm.getString(getStoreName()+".SQLException", e));
  -        } catch (IOException e) {
  -            ;
  -        } finally {
  -            if(bis != null)
  -                bis.close();
  -
  -            if(in != null)
  -                in.close();
  -
  -            bis = null;
  -            bos = null;
  -            oos = null;
  -            in = null;
  +                byte[] obs = bos.toByteArray();
  +                int size = obs.length;
  +                bis = new ByteArrayInputStream(obs, 0, size);
  +                in = new BufferedInputStream(bis, size);
   
  -            release(_conn);
  -            _conn = null;
  +                if(preparedSaveSql == null) {
  +                    preparedSaveSql = _conn.prepareStatement(saveSql);
  +                }
  +
  +                preparedSaveSql.setString(1, session.getId());
  +                preparedSaveSql.setString(2, getName());
  +                preparedSaveSql.setBinaryStream(3, in, size);
  +                preparedSaveSql.setString(4, session.isValid()?"1":"0");
  +                preparedSaveSql.setInt(5, session.getMaxInactiveInterval());
  +                preparedSaveSql.setLong(6, session.getLastAccessedTime());
  +                preparedSaveSql.execute();
  +            } catch(SQLException e) {
  +                log(sm.getString(getStoreName()+".SQLException", e));
  +            } catch (IOException e) {
  +                ;
  +            } finally {
  +                if(bis != null) {
  +                    bis.close();
  +                }
  +                if(in != null) {
  +                    in.close();
  +                }
  +
  +                release(_conn);
  +            }
           }
  -        if (debug > 0)
  +
  +        if (debug > 0) {
               log(sm.getString(getStoreName()+".saving",
                                session.getId(), sessionTable));
  +        }
       }
   
       // --------------------------------------------------------- Protected Methods
  @@ -708,8 +788,9 @@
                   conn = DriverManager.getConnection(connString);
                   conn.setAutoCommit(true);
   
  -                if(conn == null || conn.isClosed())
  +                if(conn == null || conn.isClosed()) {
                       log(sm.getString(getStoreName()+".checkConnectionDBReOpenFail"));
  +                }
               }
           } catch (SQLException ex){
               log(sm.getString(getStoreName()+".checkConnectionSQLException",
  
  
  
  1.7       +17 -9     jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/StoreBase.java
  
  Index: StoreBase.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/StoreBase.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- StoreBase.java	28 Aug 2002 17:08:58 -0000	1.6
  +++ StoreBase.java	4 Mar 2003 04:09:17 -0000	1.7
  @@ -291,8 +291,9 @@
           long timeNow = System.currentTimeMillis();
           String[] keys = null;
   
  -        if(!started)
  +        if(!started) {
               return;
  +        }
   
           try {
               keys = keys();
  @@ -305,11 +306,16 @@
           for (int i = 0; i < keys.length; i++) {
               try {
                   StandardSession session = (StandardSession) load(keys[i]);
  -                if (!session.isValid())
  +                if (session == null) {
  +                    continue;
  +                }
  +                if (!session.isValid()) {
                       continue;
  +                }
                   int maxInactiveInterval = session.getMaxInactiveInterval();
  -                if (maxInactiveInterval < 0)
  +                if (maxInactiveInterval < 0) {
                       continue;
  +                }
                   int timeIdle = // Truncate, do not round up
                       (int) ((timeNow - session.getLastAccessedTime()) / 1000L);
                   if (timeIdle >= maxInactiveInterval) {
  @@ -341,16 +347,18 @@
           Logger logger = null;
           Container container = manager.getContainer();
   
  -        if (container != null)
  +        if (container != null) {
               logger = container.getLogger();
  +        }
   
           if (logger != null) {
               logger.log(getStoreName()+"[" + container.getName() + "]: "
                          + message);
           } else {
               String containerName = null;
  -            if (container != null)
  +            if (container != null) {
                   containerName = container.getName();
  +            }
               System.out.println(getStoreName()+"[" + containerName
                                  + "]: " + message);
           }
  
  
  
  1.6       +9 -1      jakarta-tomcat-4.0/webapps/tomcat-docs/config/manager.xml
  
  Index: manager.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat-4.0/webapps/tomcat-docs/config/manager.xml,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- manager.xml	12 Jan 2003 17:26:48 -0000	1.5
  +++ manager.xml	4 Mar 2003 04:09:17 -0000	1.6
  @@ -344,6 +344,12 @@
         <p>Java class name of the JDBC driver to be used.</p>
       </attribute>
   
  +    <attribute name="sessionAppCol" required="true">
  +      <p>Name of the database column, contained in the specified session
  +      table, that contains the Engine, Host, and Web Application Context
  +      name in the format <code>/Engine/Host/Context</code>.</p>
  +    </attribute>
  +
       <attribute name="sessionDataCol" required="true">
         <p>Name of the database column, contained in the specified
         session table, that contains the serialized form of all session
  @@ -399,7 +405,9 @@
     valid_session  char(1) not null,
     max_inactive   int not null,
     last_access    bigint not null,
  -  session_data   mediumblob
  +  app_name       varchar(255),
  +  session_data   mediumblob,
  +  KEY kapp_name(app_name)
   );
   </source>
   
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: tomcat-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tomcat-dev-help@jakarta.apache.org