[jboss-cvs] JBossAS SVN: r110895 - in projects/jboss-jca/trunk: adapters/src/main/java/org/jboss/jca/adapters/jdbc/extensions/mysql and 6 other directories.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Fri Mar 11 12:39:44 EST 2011


Author: jesper.pedersen
Date: 2011-03-11 12:39:44 -0500 (Fri, 11 Mar 2011)
New Revision: 110895

Added:
   projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/extensions/mysql/MySQLReauthPlugin.java
   projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/spi/reauth/
   projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/spi/reauth/ReauthPlugin.java
   projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/spi/reauth/package.html
Modified:
   projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/BaseWrapperManagedConnection.java
   projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/BaseWrapperManagedConnectionFactory.java
   projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/local/LocalManagedConnectionFactory.java
   projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/xa/XAManagedConnectionFactory.java
   projects/jboss-jca/trunk/deployers/src/main/java/org/jboss/jca/deployers/common/AbstractDsDeployer.java
   projects/jboss-jca/trunk/doc/userguide/en-US/modules/deployment.xml
Log:
[JBJCA-516] Reauthentication support for JDBC

Modified: projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/BaseWrapperManagedConnection.java
===================================================================
--- projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/BaseWrapperManagedConnection.java	2011-03-11 16:27:33 UTC (rev 110894)
+++ projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/BaseWrapperManagedConnection.java	2011-03-11 17:39:44 UTC (rev 110895)
@@ -22,6 +22,8 @@
 
 package org.jboss.jca.adapters.jdbc;
 
+import org.jboss.jca.adapters.jdbc.spi.reauth.ReauthPlugin;
+
 import java.io.PrintWriter;
 import java.sql.CallableStatement;
 import java.sql.Connection;
@@ -77,7 +79,7 @@
    protected final Connection con;
 
    /** The properties */
-   protected final Properties props;
+   protected Properties props;
 
    private final int transactionIsolation;
 
@@ -158,7 +160,7 @@
     */
    public BaseWrapperManagedConnection(final BaseWrapperManagedConnectionFactory mcf,
                                        final Connection con,
-                                       final Properties props, 
+                                       Properties props, 
                                        final int transactionIsolation, 
                                        final int psCacheSize) 
       throws SQLException
@@ -354,6 +356,9 @@
     */
    public Object getConnection(Subject subject, ConnectionRequestInfo cri) throws ResourceException
    {
+      if (Boolean.TRUE.equals(mcf.getReauthEnabled()))
+         mcf.loadReauthPlugin();
+
       checkIdentity(subject, cri);
       WrappedConnection lc = WRAPPED_CONNECTION_FACTORY.createWrappedConnection(this,
                                                                                 mcf.getSpy().booleanValue(),
@@ -704,10 +709,28 @@
    private void checkIdentity(Subject subject, ConnectionRequestInfo cri) throws ResourceException
    {
       Properties newProps = mcf.getConnectionProperties(subject, cri);
-      if (!props.equals(newProps))
+
+      if (Boolean.TRUE.equals(mcf.getReauthEnabled()))
       {
-         throw new ResourceException("Wrong credentials passed to getConnection!");
+         if (!props.getProperty("user").equals(newProps.getProperty("user")))
+         {
+            try
+            {
+               ReauthPlugin plugin = mcf.getReauthPlugin();
+               plugin.reauthenticate(con, newProps.getProperty("user"), newProps.getProperty("password"));
+               props = newProps;
+            }
+            catch (SQLException se)
+            {
+               throw new ResourceException("Error during reauthentication", se);
+            }
+         }
       }
+      else
+      {
+         if (!props.equals(newProps))
+            throw new ResourceException("Wrong credentials passed to getConnection!");
+      }
    }
 
    /**

Modified: projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/BaseWrapperManagedConnectionFactory.java
===================================================================
--- projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/BaseWrapperManagedConnectionFactory.java	2011-03-11 16:27:33 UTC (rev 110894)
+++ projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/BaseWrapperManagedConnectionFactory.java	2011-03-11 17:39:44 UTC (rev 110895)
@@ -29,6 +29,7 @@
 import org.jboss.jca.adapters.jdbc.spi.StaleConnectionChecker;
 import org.jboss.jca.adapters.jdbc.spi.URLSelectorStrategy;
 import org.jboss.jca.adapters.jdbc.spi.ValidConnectionChecker;
+import org.jboss.jca.adapters.jdbc.spi.reauth.ReauthPlugin;
 import org.jboss.jca.adapters.jdbc.util.Injection;
 
 import java.io.ByteArrayInputStream;
@@ -44,9 +45,11 @@
 import java.sql.SQLException;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Locale;
 import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.Set;
+import java.util.StringTokenizer;
 
 import javax.resource.ResourceException;
 import javax.resource.spi.ConnectionManager;
@@ -209,6 +212,18 @@
    /** JNDI name */
    private String jndiName;
 
+   /** Reauth enabled */
+   private Boolean reauthEnabled = Boolean.FALSE;
+
+   /** Reauth plugin class name */
+   private String reauthPluginClassName;
+
+   /** Reauth plugin properties - format: [key|value](,key|value)+ */
+   private String reauthPluginProperties;
+
+   /** Reauth plugin */
+   private ReauthPlugin reauthPlugin;
+
    /**
     * Constructor
     */
@@ -645,6 +660,124 @@
    }
 
    /**
+    * Get reauth enabled
+    * @return The value
+    */
+   public Boolean getReauthEnabled()
+   {
+      return reauthEnabled;
+   }
+
+   /**
+    * Set reauth enabled
+    * @param v The value
+    */
+   public void setReauthEnabled(Boolean v)
+   {
+      if (v != null)
+         reauthEnabled = v;
+   }
+
+   /**
+    * Get reauth plugin class name
+    * @return The value
+    */
+   public String getReauthPluginClassName()
+   {
+      return reauthPluginClassName;
+   }
+
+   /**
+    * Set reauth plugin class name
+    * @param v The value
+    */
+   public void setReauthPluginClassName(String v)
+   {
+      if (v != null)
+         reauthPluginClassName = v;
+   }
+
+   /**
+    * Get reauth plugin properties
+    * @return The value
+    */
+   public String getReauthPluginProperties()
+   {
+      return reauthPluginProperties;
+   }
+
+   /**
+    * Set reauth plugin properties
+    * @param v The value
+    */
+   public void setReauthPluginProperties(String v)
+   {
+      if (v != null)
+         reauthPluginProperties = v;
+   }
+
+   /**
+    * Load reauth plugin
+    * @exception ResourceException Thrown in case of an error
+    */
+   synchronized void loadReauthPlugin() throws ResourceException
+   {
+      if (reauthPlugin != null)
+         return;
+
+      if (Boolean.FALSE.equals(reauthEnabled))
+         throw new IllegalStateException("Reauthentication not enabled");
+
+      if (reauthPluginClassName == null || reauthPluginClassName.trim().equals(""))
+         throw new IllegalStateException("ReauthPlugin class name not defined");
+
+      try
+      {
+         Class<?> clz = Class.forName(reauthPluginClassName, true, Thread.currentThread().getContextClassLoader());
+         reauthPlugin = (ReauthPlugin)clz.newInstance();
+
+         if (reauthPluginProperties != null)
+         {
+            Injection injector = new Injection();
+
+            StringTokenizer st = new StringTokenizer(reauthPluginProperties, ",");
+            while (st.hasMoreTokens())
+            {
+               String keyValue = st.nextToken();
+
+               int split = keyValue.indexOf("|");
+
+               if (split == -1)
+                  throw new IllegalStateException("Reauth plugin property incorrect: " + keyValue);
+
+               String key = keyValue.substring(0, split);
+               String value = "";
+
+               if (keyValue.length() > (split + 1))
+                  value = keyValue.substring(split + 1);
+
+               injector.inject(reauthPlugin, key, value);
+            }
+         }
+
+         reauthPlugin.initialize(Thread.currentThread().getContextClassLoader());
+      }
+      catch (Throwable t)
+      {
+         throw new ResourceException("Error during loading reauth plugin", t);
+      }
+   }
+
+   /**
+    * Get the reauth plugin
+    * @return The value
+    */
+   ReauthPlugin getReauthPlugin()
+   {
+      return reauthPlugin;
+   }
+
+   /**
     * Set the url delimiter.
     * @param urlDelimiter The value
     * @exception ResourceException Thrown in case of an error
@@ -951,13 +1084,11 @@
        */
       public Boolean run()
       {
-         Iterator<?> i = subject.getPrivateCredentials().iterator();
-         while (i.hasNext())
+         Set<PasswordCredential> creds = subject.getPrivateCredentials(PasswordCredential.class);
+         if (creds != null && creds.size() > 0)
          {
-            Object o = i.next();
-            if (o instanceof PasswordCredential)
+            for (PasswordCredential cred: creds)
             {
-               PasswordCredential cred = (PasswordCredential) o;
                if (cred.getManagedConnectionFactory().equals(mcf))
                {
                   props.setProperty("user", (cred.getUserName() == null) ? "" : cred.getUserName());

Added: projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/extensions/mysql/MySQLReauthPlugin.java
===================================================================
--- projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/extensions/mysql/MySQLReauthPlugin.java	                        (rev 0)
+++ projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/extensions/mysql/MySQLReauthPlugin.java	2011-03-11 17:39:44 UTC (rev 110895)
@@ -0,0 +1,93 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2011, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.jca.adapters.jdbc.extensions.mysql;
+
+import org.jboss.jca.adapters.jdbc.spi.reauth.ReauthPlugin;
+
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * MySQL plugin for reauthentication
+ *
+ * @author <a href="mailto:jesper.pedersen at jboss.org">Jesper Pedersen</a>
+ */
+public class MySQLReauthPlugin implements ReauthPlugin
+{
+   private Method changeUser;
+
+   /**
+    * Default constructor
+    */
+   public MySQLReauthPlugin()
+   {
+   }
+
+   /**
+    * Initialize
+    * @param cl The class loader which can be used for initialization
+    * @exception SQLException Thrown in case of an error
+    */
+   public synchronized void initialize(ClassLoader cl) throws SQLException
+   {
+      try
+      {
+         Class<?> mysqlConnection = cl.loadClass("com.mysql.jdbc.Connection");
+         changeUser = mysqlConnection.getMethod("changeUser", new Class[] {String.class, String.class});			
+      } 
+      catch (Throwable t) 
+      {
+         throw new SQLException("Cannot resolve com.mysq.jdbc.Connection changeUser method", t);
+      }
+   }
+
+   /**
+    * Reauthenticate
+    * @param c The connection
+    * @param userName The user name
+    * @param password The password
+    * @exception SQLException Thrown in case of an error
+    */
+   public synchronized void reauthenticate(Connection c, String userName, String password) throws SQLException
+   {
+      Object[] params = new Object[] {userName, password};
+      try
+      {
+         changeUser.invoke(c, params);
+      }
+      catch (Throwable t) 
+      {
+         Throwable cause = t.getCause();		    
+
+         if (cause instanceof SQLException)
+         {
+            throw (SQLException)cause;
+         }
+         else
+         {
+            throw new SQLException("Unexpected error in changeUser", t);				
+         }
+      }
+   }
+}

Modified: projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/local/LocalManagedConnectionFactory.java
===================================================================
--- projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/local/LocalManagedConnectionFactory.java	2011-03-11 16:27:33 UTC (rev 110894)
+++ projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/local/LocalManagedConnectionFactory.java	2011-03-11 17:39:44 UTC (rev 110895)
@@ -453,9 +453,12 @@
          {
             LocalManagedConnection mc = (LocalManagedConnection) o;
 
-            //First check the properties
-            if (mc.getProperties().equals(newProps))
+            if (Boolean.TRUE.equals(getReauthEnabled()))
             {
+               return mc;
+            }
+            else if (mc.getProperties().equals(newProps))
+            {
                //Next check to see if we are validating on matchManagedConnections
                if ((getValidateOnMatch() && mc.checkValid()) || !getValidateOnMatch())
                {

Added: projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/spi/reauth/ReauthPlugin.java
===================================================================
--- projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/spi/reauth/ReauthPlugin.java	                        (rev 0)
+++ projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/spi/reauth/ReauthPlugin.java	2011-03-11 17:39:44 UTC (rev 110895)
@@ -0,0 +1,58 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2011, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.jca.adapters.jdbc.spi.reauth;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * Define the SPI contract for a reauthentication plugin for the
+ * JDBC resource adapter.
+ *
+ * The implementing class must have a default constructor.
+ *
+ * The implementing class must be thread safe.
+ *
+ * Java bean properties can be set, using the supported types
+ * by the Java EE Connector Architecture specification.
+ *
+ * @author <a href="mailto:jesper.pedersen at jboss.org">Jesper Pedersen</a>
+ */
+public interface ReauthPlugin
+{
+   /**
+    * Initialize
+    * @param cl The class loader which can be used for initialization
+    * @exception SQLException Thrown in case of an error
+    */
+   public void initialize(ClassLoader cl) throws SQLException;
+
+   /**
+    * Reauthenticate
+    * @param c The connection
+    * @param userName The user name
+    * @param password The password
+    * @exception SQLException Thrown in case of an error
+    */
+   public void reauthenticate(Connection c, String userName, String password) throws SQLException;
+}

Added: projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/spi/reauth/package.html
===================================================================
--- projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/spi/reauth/package.html	                        (rev 0)
+++ projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/spi/reauth/package.html	2011-03-11 17:39:44 UTC (rev 110895)
@@ -0,0 +1,3 @@
+<body>
+This package contains the SPI for the reauthentication plugin.
+</body>

Modified: projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/xa/XAManagedConnectionFactory.java
===================================================================
--- projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/xa/XAManagedConnectionFactory.java	2011-03-11 16:27:33 UTC (rev 110894)
+++ projects/jboss-jca/trunk/adapters/src/main/java/org/jboss/jca/adapters/jdbc/xa/XAManagedConnectionFactory.java	2011-03-11 17:39:44 UTC (rev 110895)
@@ -435,8 +435,12 @@
          {
             XAManagedConnection mc = (XAManagedConnection) o;
             
-            if (mc.getProperties().equals(newProps))
+            if (Boolean.TRUE.equals(getReauthEnabled()))
             {
+               return mc;
+            }
+            else if (mc.getProperties().equals(newProps))
+            {
                //Next check to see if we are validating on matchManagedConnections
                if ((getValidateOnMatch() && mc.checkValid()) || !getValidateOnMatch())
                {

Modified: projects/jboss-jca/trunk/deployers/src/main/java/org/jboss/jca/deployers/common/AbstractDsDeployer.java
===================================================================
--- projects/jboss-jca/trunk/deployers/src/main/java/org/jboss/jca/deployers/common/AbstractDsDeployer.java	2011-03-11 16:27:33 UTC (rev 110894)
+++ projects/jboss-jca/trunk/deployers/src/main/java/org/jboss/jca/deployers/common/AbstractDsDeployer.java	2011-03-11 17:39:44 UTC (rev 110895)
@@ -46,7 +46,9 @@
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 import javax.resource.spi.ManagedConnectionFactory;
 import javax.resource.spi.TransactionSupport.TransactionSupportLevel;
@@ -274,8 +276,13 @@
       String securityDomain = null;
       if (ds.getSecurity() != null)
       {
-         if (ds.getSecurity().getSecurityDomain() != null)
+         if (ds.getSecurity().getReauthPlugin() != null)
          {
+            strategy = PoolStrategy.REAUTH;
+            securityDomain = ds.getSecurity().getSecurityDomain();
+         }
+         else if (ds.getSecurity().getSecurityDomain() != null)
+         {
             strategy = PoolStrategy.POOL_BY_SUBJECT;
             securityDomain = ds.getSecurity().getSecurityDomain();
          }
@@ -321,6 +328,34 @@
          injectValue(mcf, "setJndiName", jndiName);
       }
 
+      // Reauth
+      if (strategy == PoolStrategy.REAUTH)
+      {
+         injectValue(mcf, "setReauthEnabled", Boolean.TRUE);
+         injectValue(mcf, "setReauthPluginClassName", ds.getSecurity().getReauthPlugin().getClassName());
+
+         Map<String, String> mps = ds.getSecurity().getReauthPlugin().getConfigPropertiesMap();
+         if (mps != null && mps.size() > 0)
+         {
+            StringBuilder reauthPluginProperties = new StringBuilder();
+
+            Iterator<Map.Entry<String, String>> entryIterator = mps.entrySet().iterator();
+            while (entryIterator.hasNext())
+            {
+               Map.Entry<String, String> entry = entryIterator.next();
+
+               reauthPluginProperties.append(entry.getKey());
+               reauthPluginProperties.append("|");
+               reauthPluginProperties.append(entry.getValue());
+
+               if (entryIterator.hasNext())
+                  reauthPluginProperties.append(",");
+            }
+
+            injectValue(mcf, "setReauthPluginProperties", reauthPluginProperties.toString());
+         }
+      }
+
       // ConnectionFactory
       return mcf.createConnectionFactory(cm);
    }
@@ -367,8 +402,13 @@
       String securityDomain = null;
       if (ds.getSecurity() != null)
       {
-         if (ds.getSecurity().getSecurityDomain() != null)
+         if (ds.getSecurity().getReauthPlugin() != null)
          {
+            strategy = PoolStrategy.REAUTH;
+            securityDomain = ds.getSecurity().getSecurityDomain();
+         }
+         else if (ds.getSecurity().getSecurityDomain() != null)
+         {
             strategy = PoolStrategy.POOL_BY_SUBJECT;
             securityDomain = ds.getSecurity().getSecurityDomain();
          }
@@ -428,6 +468,35 @@
          injectValue(mcf, "setSpy", Boolean.TRUE);
          injectValue(mcf, "setJndiName", jndiName);
       }
+
+      // Reauth
+      if (strategy == PoolStrategy.REAUTH)
+      {
+         injectValue(mcf, "setReauthEnabled", Boolean.TRUE);
+         injectValue(mcf, "setReauthPluginClassName", ds.getSecurity().getReauthPlugin().getClassName());
+
+         Map<String, String> mps = ds.getSecurity().getReauthPlugin().getConfigPropertiesMap();
+         if (mps != null && mps.size() > 0)
+         {
+            StringBuilder reauthPluginProperties = new StringBuilder();
+
+            Iterator<Map.Entry<String, String>> entryIterator = mps.entrySet().iterator();
+            while (entryIterator.hasNext())
+            {
+               Map.Entry<String, String> entry = entryIterator.next();
+
+               reauthPluginProperties.append(entry.getKey());
+               reauthPluginProperties.append("|");
+               reauthPluginProperties.append(entry.getValue());
+
+               if (entryIterator.hasNext())
+                  reauthPluginProperties.append(",");
+            }
+
+            injectValue(mcf, "setReauthPluginProperties", reauthPluginProperties.toString());
+         }
+      }
+
       Recovery recoveryMD = ds.getRecovery();
       String defaultSecurityDomain = null;
       String defaultUserName = null;

Modified: projects/jboss-jca/trunk/doc/userguide/en-US/modules/deployment.xml
===================================================================
--- projects/jboss-jca/trunk/doc/userguide/en-US/modules/deployment.xml	2011-03-11 16:27:33 UTC (rev 110894)
+++ projects/jboss-jca/trunk/doc/userguide/en-US/modules/deployment.xml	2011-03-11 17:39:44 UTC (rev 110895)
@@ -1015,6 +1015,12 @@
                   application-policy/name attribute.
                 </entry>
               </row>
+              <row>
+                <entry><code>reauth-plugin</code></entry>
+                <entry>
+                  Defines a reauthentication plugin that can be used for reauthentication of physical connections.
+                </entry>
+              </row>
             </tbody>
           </tgroup>
         </table>



More information about the jboss-cvs-commits mailing list