SLING-3642 - Support modifying of DataSource properties at runtime without restart

-- Added support to refresh connectionPool associated with DataSource with new config
-- Made the metatype more richer

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1600199 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/extensions/datasource/internal/DataSourceFactory.java b/src/main/java/org/apache/sling/extensions/datasource/internal/DataSourceFactory.java
index 43b5815..eb56710 100644
--- a/src/main/java/org/apache/sling/extensions/datasource/internal/DataSourceFactory.java
+++ b/src/main/java/org/apache/sling/extensions/datasource/internal/DataSourceFactory.java
@@ -20,25 +20,30 @@
 
 import java.lang.management.ManagementFactory;
 import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Dictionary;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 
 import javax.management.InstanceNotFoundException;
 import javax.management.MBeanServer;
 import javax.management.ObjectName;
-import javax.sql.DataSource;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.ConfigurationPolicy;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
 import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyOption;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.apache.tomcat.jdbc.pool.ConnectionPool;
-import org.apache.tomcat.jdbc.pool.DataSourceProxy;
+import org.apache.tomcat.jdbc.pool.DataSource;
 import org.apache.tomcat.jdbc.pool.PoolConfiguration;
 import org.apache.tomcat.jdbc.pool.PoolProperties;
 import org.osgi.framework.BundleContext;
@@ -61,6 +66,9 @@
     @Property
     static final String PROP_DATASOURCE_NAME = "datasource.name";
 
+    @Property(value = PROP_DATASOURCE_NAME)
+    static final String PROP_DS_SVC_PROP_NAME = "datasource.svc.prop.name";
+
     @Property
     static final String PROP_DRIVERCLASSNAME = "driverClassName";
 
@@ -73,12 +81,103 @@
     @Property(passwordValue = "")
     static final String PROP_PASSWORD = "password";
 
+    /**
+     * Value indicating default value should be used. if the value is set to
+     * this value then that value would be treated as null
+     */
+    static final String DEFAULT_VAL = "default";
+
+    @Property(value = DEFAULT_VAL, options = {
+            @PropertyOption(name = "Default", value = DEFAULT_VAL),
+            @PropertyOption(name = "true", value = "true"),
+            @PropertyOption(name = "false", value = "false")})
+    static final String PROP_DEFAULTAUTOCOMMIT = "defaultAutoCommit";
+
+    @Property(value = DEFAULT_VAL, options = {
+            @PropertyOption(name = "Default", value = DEFAULT_VAL),
+            @PropertyOption(name = "true", value = "true"),
+            @PropertyOption(name = "false", value = "false")})
+    static final String PROP_DEFAULTREADONLY = "defaultReadOnly";
+
+    @Property(value = DEFAULT_VAL, options = {
+            @PropertyOption(name = "Default", value = DEFAULT_VAL),
+            @PropertyOption(name = "NONE", value = "NONE"),
+            @PropertyOption(name = "READ_COMMITTED", value = "READ_COMMITTED"),
+            @PropertyOption(name = "READ_UNCOMMITTED", value = "READ_UNCOMMITTED"),
+            @PropertyOption(name = "REPEATABLE_READ", value = "REPEATABLE_READ"),
+            @PropertyOption(name = "SERIALIZABLE", value = "SERIALIZABLE")})
+    static final String PROP_DEFAULTTRANSACTIONISOLATION = "defaultTransactionIsolation";
+
+    @Property
+    static final String PROP_DEFAULTCATALOG = "defaultCatalog";
+
     @Property(intValue = PoolProperties.DEFAULT_MAX_ACTIVE)
     static final String PROP_MAXACTIVE = "maxActive";
 
+    @Property(intValue = PoolProperties.DEFAULT_MAX_ACTIVE)
+    static final String PROP_MAXIDLE = "maxIdle"; //defaults to maxActive
+
+    @Property(intValue = 10)
+    static final String PROP_MINIDLE = "minIdle"; //defaults to initialSize
+
+    @Property(intValue = 10)
+    static final String PROP_INITIALSIZE = "initialSize";
+
+    @Property(intValue = 30000)
+    static final String PROP_MAXWAIT = "maxWait";
+
+    @Property(intValue = 0)
+    static final String PROP_MAXAGE = "maxAge";
+
+    @Property(boolValue = false)
+    static final String PROP_TESTONBORROW = "testOnBorrow";
+
+    @Property(boolValue = false)
+    static final String PROP_TESTONRETURN = "testOnReturn";
+
+    @Property(boolValue = false)
+    static final String PROP_TESTWHILEIDLE = "testWhileIdle";
+
+    @Property
+    static final String PROP_VALIDATIONQUERY = "validationQuery";
+
+    @Property(intValue = -1)
+    static final String PROP_VALIDATIONQUERY_TIMEOUT = "validationQueryTimeout";
+
+    @Property(intValue = 5000)
+    protected static final String PROP_TIMEBETWEENEVICTIONRUNSMILLIS = "timeBetweenEvictionRunsMillis";
+
+    @Property(intValue = 60000)
+    protected static final String PROP_MINEVICTABLEIDLETIMEMILLIS = "minEvictableIdleTimeMillis";
+
+    @Property
+    protected static final String PROP_CONNECTIONPROPERTIES = "connectionProperties";
+
+    @Property
+    protected static final String PROP_INITSQL = "initSQL";
+
+    @Property(value = "StatementCache;SlowQueryReport(threshold=10000);ConnectionState")
+    protected static final String PROP_INTERCEPTORS = "jdbcInterceptors";
+
+    @Property(intValue = 30000)
+    protected static final String PROP_VALIDATIONINTERVAL = "validationInterval";
+
+    @Property(boolValue = true)
+    protected static final String PROP_LOGVALIDATIONERRORS = "logValidationErrors";
+
     @Property(value = {}, cardinality = 1024)
     static final String PROP_DATASOURCE_SVC_PROPS = "datasource.svc.properties";
 
+    /**
+     * Property names where we need to treat value 'default' as null
+     */
+    private static final Set<String> PROPS_WITH_DFEAULT =
+            Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+                    PROP_DEFAULTAUTOCOMMIT,
+                    PROP_DEFAULTREADONLY,
+                    PROP_DEFAULTTRANSACTIONISOLATION
+            )));
+
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     @Reference
@@ -86,125 +185,167 @@
 
     private String name;
 
+    private String svcPropName;
+
     private ObjectName jmxName;
 
     private ServiceRegistration dsRegistration;
 
     private DataSource dataSource;
 
+    private BundleContext bundleContext;
+
     @Activate
-    protected void activate(BundleContext bundleContext, Map<String,?> config) throws Exception {
-        Properties props = new Properties();
-        name = PropertiesUtil.toString(config.get(PROP_DATASOURCE_NAME), null);
+    protected void activate(BundleContext bundleContext, Map<String, ?> config) throws Exception {
+        this.bundleContext = bundleContext;
+        name = getDataSourceName(config);
 
         checkArgument(name != null, "DataSource name must be specified via [%s] property", PROP_DATASOURCE_NAME);
+        dataSource = new LazyJmxRegisteringDataSource(createPoolConfig(config));
+
+        svcPropName = getSvcPropName(config);
+        registerDataSource(svcPropName);
+
+        log.info("Created DataSource [{}] with properties {}", name, dataSource.getPoolProperties().toString());
+    }
+
+    @Modified
+    protected void modified(Map<String, ?> config) throws Exception {
+        String name = getDataSourceName(config);
+        String svcPropName = getSvcPropName(config);
+
+        if (!this.name.equals(name) || !this.svcPropName.equals(svcPropName)) {
+            log.info("Change in datasource name/service property name detected. DataSource would be recreated");
+            deactivate();
+            activate(bundleContext, config);
+            return;
+        }
+
+        //Other modifications can be applied at runtime itself
+        //Tomcat Connection Pool is decoupled from DataSource so can be closed and reset
+        dataSource.setPoolProperties(createPoolConfig(config));
+        closeConnectionPool();
+        dataSource.createPool();
+        log.info("Updated DataSource [{}] with properties {}", name, dataSource.getPoolProperties().toString());
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        if (dsRegistration != null) {
+            dsRegistration.unregister();
+            dsRegistration = null;
+        }
+
+        closeConnectionPool();
+        dataSource = null;
+    }
+
+    private void closeConnectionPool() {
+        unregisterJmx();
+        dataSource.close();
+    }
+
+    private PoolConfiguration createPoolConfig(Map<String, ?> config) {
+        Properties props = new Properties();
 
         //Copy the other properties first
-        Map<String,String> otherProps = PropertiesUtil.toMap(config.get(PROP_DATASOURCE_SVC_PROPS), new String[0]);
-        for(Map.Entry<String, String> e : otherProps.entrySet()){
-            props.setProperty(e.getKey(), e.getValue());
+        Map<String, String> otherProps = PropertiesUtil.toMap(config.get(PROP_DATASOURCE_SVC_PROPS), new String[0]);
+        for (Map.Entry<String, String> e : otherProps.entrySet()) {
+            set(e.getKey(), e.getValue(), props);
         }
 
         props.setProperty(org.apache.tomcat.jdbc.pool.DataSourceFactory.OBJECT_NAME, name);
 
-        for(String propName : DummyDataSourceFactory.getPropertyNames()){
+        for (String propName : DummyDataSourceFactory.getPropertyNames()) {
             String value = PropertiesUtil.toString(config.get(propName), null);
-            if(value != null){
-                props.setProperty(propName, value);
-            }
+            set(propName, value, props);
         }
 
-        dataSource = createDataSource(props, bundleContext);
-        registerDataSource(bundleContext);
-        log.info("Created DataSource [{}] with properties {}", name, getDataSourceDetails());
-    }
-
-    @Deactivate
-    protected void deactivate(){
-        if(dsRegistration != null){
-            dsRegistration.unregister();
-        }
-
-        unregisterJmx();
-
-        if(dataSource instanceof DataSourceProxy){
-            ((DataSourceProxy) dataSource).close();
-        }
-
-    }
-
-    private DataSource createDataSource(Properties props, BundleContext bundleContext) throws Exception {
-        PoolConfiguration poolProperties = org.apache.tomcat.jdbc.pool.DataSourceFactory.parsePoolProperties(props);
-
-        DriverDataSource driverDataSource = new DriverDataSource(poolProperties, driverRegistry,
-                bundleContext, this);
-
         //Specify the DataSource such that connection creation logic is handled
         //by us where we take care of OSGi env
-        poolProperties.setDataSource(driverDataSource);
+        PoolConfiguration poolProperties = org.apache.tomcat.jdbc.pool.DataSourceFactory.parsePoolProperties(props);
+        poolProperties.setDataSource(createDriverDataSource(poolProperties));
 
-        // Return the configured DataSource instance
-        return new LazyJmxRegisteringDataSource(poolProperties);
+        return poolProperties;
     }
 
-    private void registerDataSource(BundleContext bundleContext) {
-        Dictionary<String,Object> svcProps = new Hashtable<String, Object>();
-        svcProps.put(PROP_DATASOURCE_NAME, name);
+    private DriverDataSource createDriverDataSource(PoolConfiguration poolProperties) {
+        return new DriverDataSource(poolProperties, driverRegistry, bundleContext, this);
+    }
+
+    private void registerDataSource(String svcPropName) {
+        Dictionary<String, Object> svcProps = new Hashtable<String, Object>();
+        svcProps.put(svcPropName, name);
         svcProps.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
         svcProps.put(Constants.SERVICE_DESCRIPTION, "DataSource service based on Tomcat JDBC");
-        dsRegistration = bundleContext.registerService(DataSource.class, dataSource, svcProps);
+        dsRegistration = bundleContext.registerService(javax.sql.DataSource.class, dataSource, svcProps);
     }
 
-    void registerJmx(ConnectionPool pool) throws SQLException {
-            org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmxPool = pool.getJmxPool();
-            if (jmxPool == null) {
-                //jmx not enabled
-                return;
-            }
-            Hashtable<String, String> table = new Hashtable<String, String>();
-            table.put("type", "ConnectionPool");
-            table.put("class", DataSource.class.getName());
-            table.put("name", ObjectName.quote(name));
+    private void registerJmx(ConnectionPool pool) throws SQLException {
+        org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmxPool = pool.getJmxPool();
+        if (jmxPool == null) {
+            //jmx not enabled
+            return;
+        }
+        Hashtable<String, String> table = new Hashtable<String, String>();
+        table.put("type", "ConnectionPool");
+        table.put("class", javax.sql.DataSource.class.getName());
+        table.put("name", ObjectName.quote(name));
 
-            try {
-                jmxName = new ObjectName("org.apache.sling", table);
-                MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
-                mbs.registerMBean(jmxPool, jmxName);
-            } catch (Exception e) {
-                log.warn("Error occurred while registering the JMX Bean for " +
-                        "connection pool with name {}", jmxName, e);
-            }
+        try {
+            jmxName = new ObjectName("org.apache.sling", table);
+            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+            mbs.registerMBean(jmxPool, jmxName);
+        } catch (Exception e) {
+            log.warn("Error occurred while registering the JMX Bean for " +
+                    "connection pool with name {}", jmxName, e);
+        }
     }
 
     ConnectionPool getPool() {
-        return ((DataSourceProxy) dataSource).getPool();
+        return dataSource.getPool();
     }
 
-    private void unregisterJmx(){
+    private void unregisterJmx() {
         try {
-            if(jmxName != null) {
+            if (jmxName != null) {
                 MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
                 mbs.unregisterMBean(jmxName);
             }
         } catch (InstanceNotFoundException ignore) {
             // NOOP
         } catch (Exception e) {
-            log.error("Unable to unregister JDBC pool with JMX",e);
+            log.error("Unable to unregister JDBC pool with JMX", e);
         }
     }
 
-    private String getDataSourceDetails() {
-        if(dataSource instanceof DataSourceProxy){
-            return ((DataSourceProxy) dataSource).getPoolProperties().toString();
-        }
-        return "<UNKNOWN>";
+    //~----------------------------------------< Config Handling >
+
+    private static String getDataSourceName(Map<String, ?> config) {
+        return PropertiesUtil.toString(config.get(PROP_DATASOURCE_NAME), null);
     }
 
+    private static String getSvcPropName(Map<String, ?> config) {
+        return PropertiesUtil.toString(config.get(PROP_DS_SVC_PROP_NAME), PROP_DATASOURCE_NAME);
+    }
 
+    private static void set(String name, String value, Properties props) {
+        if (PROPS_WITH_DFEAULT.contains(name) && DEFAULT_VAL.equals(value)) {
+            value = null;
+        }
 
-    public static void checkArgument(boolean expression,
-                                     String errorMessageTemplate,
-                                     Object... errorMessageArgs) {
+        if (value != null) {
+            value = value.trim();
+        }
+
+        if (value != null && !value.isEmpty()) {
+            props.setProperty(name, value);
+        }
+    }
+
+    private static void checkArgument(boolean expression,
+                                      String errorMessageTemplate,
+                                      Object... errorMessageArgs) {
         if (!expression) {
             throw new IllegalArgumentException(
                     String.format(errorMessageTemplate, errorMessageArgs));
@@ -225,10 +366,16 @@
             return pool;
         }
 
+        @Override
+        public void close() {
+            initialized = false;
+            super.close();
+        }
+
         private void registerJmxLazily(ConnectionPool pool) throws SQLException {
-            if(!initialized){
-                synchronized (this){
-                    if(initialized){
+            if (!initialized) {
+                synchronized (this) {
+                    if (initialized) {
                         return;
                     }
                     DataSourceFactory.this.registerJmx(pool);
@@ -242,7 +389,7 @@
      * Dummy impl to enable access to protected fields
      */
     private static class DummyDataSourceFactory extends org.apache.tomcat.jdbc.pool.DataSourceFactory {
-        static String[] getPropertyNames(){
+        static String[] getPropertyNames() {
             return ALL_PROPERTIES;
         }
     }
diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties
index e0e9e5c..0984696 100644
--- a/src/main/resources/OSGI-INF/metatype/metatype.properties
+++ b/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -19,8 +19,9 @@
 
 # suppress inspection "UnusedProperty" for whole file
 
-datasource.component.name = Apache Sling JDBC DataSource
-datasource.component.description = Creates a DataSource services based on configuration provided
+datasource.component.name = Apache Sling Connection Pooled DataSource
+datasource.component.description = Creates a DataSource services based on configuration provided. For more details \
+  on the various properties refer to http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Common_Attributes
 
 datasource.name.name = Datasource name(*)
 datasource.name.description = Name of this data source (required)
@@ -42,6 +43,94 @@
 password.name=Password
 password.description=The connection password to be passed to our JDBC driver to establish a connection.
 
+defaultAutoCommit.name=Auto Commit
+defaultAutoCommit.description=The default auto-commit state of connections created by this pool. (If 'default' \
+  then the setAutoCommit method will not be called.)
+
+defaultReadOnly.name=Readonly
+defaultReadOnly.description=The default read-only state of connections created by this pool.
+
+defaultTransactionIsolation.name=Transaction Isolation
+defaultTransactionIsolation.description=The default TransactionIsolation state of connections created by this \
+  pool. If 'default', the method will not be called and it defaults to the JDBC driver.
+
+defaultCatalog.name=Catalog Name
+defaultCatalog.description=The default catalog of connections created by this pool.
+
 maxActive.name=Max Active Connections
 maxActive.description=The maximum number of active connections that can be allocated from this pool at \
-  the same time. The default value is 100
\ No newline at end of file
+  the same time. The default value is 100
+
+maxIdle.name=Max Idle Connections
+maxIdle.description=The maximum number of connections that should be kept in the pool at all times. Idle \
+  connections are checked periodically (if enabled) and connections that been idle for longer than \
+  minEvictableIdleTimeMillis will be released. (also see testWhileIdle)
+
+minIdle.name=Min Idle Connections
+minIdle.description=The minimum number of established connections that should be kept in the pool at all times. \
+  The connection pool can shrink below this number if validation queries fail. Default value is derived from\
+   initialSize:10 (also see testWhileIdle)
+
+initialSize.name=Initial Size
+initialSize.description=The initial number of connections that are created when the pool is started. \
+  Default value is 10
+
+maxWait.name=Max Wait
+maxWait.description=The maximum number of milliseconds that the pool will wait (when there are no available\
+   connections) for a connection to be returned before throwing an exception. Default value is 30000 (30 seconds)
+
+maxAge.name=Max Age
+maxAge.description=Time in milliseconds to keep this connection.
+
+validationQuery.name=Validation Query
+validationQuery.description=The SQL query that will be used to validate connections from this pool before \
+  returning them to the caller. If specified, this query does not have to return any data, it just can't \
+  throw a SQLException. The default value is null. Example values are SELECT 1(mysql), select 1 from \
+  dual(oracle), SELECT 1(MS Sql Server)
+
+validationQueryTimeout.name=Validation Query timeout
+validationQueryTimeout.description=The timeout in seconds before a connection validation queries fail. A value \
+  less than or equal to zero will disable this feature. The default value is -1.
+
+testOnBorrow.name=Test on Borrow
+testOnBorrow.description=The indication of whether objects will be validated before being borrowed from the pool.
+
+testOnReturn.name=Test on Return
+testOnReturn.description=The indication of whether objects will be validated before being returned to the pool.
+
+testWhileIdle.name=Test while Idle
+testWhileIdle.description=The indication of whether objects will be validated by the idle object evictor (if any).
+
+timeBetweenEvictionRunsMillis.name=Eviction Run Interval
+timeBetweenEvictionRunsMillis.description=The number of milliseconds to sleep between runs of the idle connection\
+   validation/cleaner thread.
+
+minEvictableIdleTimeMillis.name=Eviction Idle Time
+minEvictableIdleTimeMillis.description=The minimum amount of time an object may sit idle in the pool before it \
+  is eligible for eviction.
+
+connectionProperties.name=Connection Properties
+connectionProperties.description=The connection properties that will be sent to our JDBC driver when establishing \
+  new connections. Format of the string must be [propertyName=property;]* NOTE - The "user" and "password" properties\
+   will be passed explicitly, so they do not need to be included here.
+
+initSQL.name=Init Sql
+initSQL.description=A custom query to be run when a connection is first created
+
+jdbcInterceptors.name=JDBC Interceptors
+jdbcInterceptors.description= A semicolon separated list of classnames of JDBCInterceptor. See \
+  http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Configuring_JDBC_interceptors for more details
+
+validationInterval.name=Validation Interval
+validationInterval.description=avoid excess validation, only run validation at most at this frequency - time in \
+  milliseconds. If a connection is due for validation, but has been validated previously within this interval, \
+  it will not be validated again.
+
+logValidationErrors.name=Log Validation Error
+logValidationErrors.description=Set this to true to log errors during the validation phase to the log file
+
+datasource.svc.prop.name.name=DataSource Service Property Name
+datasource.svc.prop.name.name.description=Name of the service property which would store the DataSource Name while\
+  registering the DataSource instance as OSGi service
+
+
diff --git a/src/test/java/org/apache/sling/extensions/datasource/DataSourceIT.java b/src/test/java/org/apache/sling/extensions/datasource/DataSourceIT.java
index e75e962..a830a5c 100644
--- a/src/test/java/org/apache/sling/extensions/datasource/DataSourceIT.java
+++ b/src/test/java/org/apache/sling/extensions/datasource/DataSourceIT.java
@@ -21,6 +21,7 @@
 import java.sql.Connection;
 import java.util.Dictionary;
 import java.util.Hashtable;
+import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
 import javax.sql.DataSource;
@@ -36,6 +37,7 @@
 import static org.apache.commons.beanutils.BeanUtils.getProperty;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 @RunWith(PaxExam.class)
 public class DataSourceIT extends DataSourceTestBase{
@@ -50,15 +52,18 @@
     @Inject
     ConfigurationAdmin ca;
 
+    @SuppressWarnings("unchecked")
     @Test
     public void testDataSourceAsService() throws Exception{
         Configuration config = ca.createFactoryConfiguration(PID, null);
         Dictionary<String, Object> p = new Hashtable<String, Object>();
         p.put("url","jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
         p.put("datasource.name","test");
+        p.put("initialSize","5");
+        p.put("defaultAutoCommit","default");
+        p.put("defaultReadOnly","false");
         p.put("datasource.svc.properties",new String[]{
-                "initialSize=5",
-                "testOnBorrow=true",
+                "initSQL=SELECT 1",
         });
         p.put("maxActive",70);
         config.update(p);
@@ -77,7 +82,17 @@
         //Cannot access directly so access via reflection
         assertEquals("70", getProperty(ds, "poolProperties.maxActive"));
         assertEquals("5", getProperty(ds, "poolProperties.initialSize"));
-        assertEquals("true", getProperty(ds, "poolProperties.testOnBorrow"));
+        assertEquals("SELECT 1", getProperty(ds, "poolProperties.initSQL"));
+        assertEquals("false", getProperty(ds, "poolProperties.defaultReadOnly"));
+        assertNull(getProperty(ds, "poolProperties.defaultAutoCommit"));
+
+        config = ca.listConfigurations("(datasource.name=test)")[0];
+        Dictionary dic = config.getProperties();
+        dic.put("defaultReadOnly", Boolean.TRUE);
+        config.update(dic);
+
+        TimeUnit.MILLISECONDS.sleep(100);
+        assertEquals("true", getProperty(ds, "poolProperties.defaultReadOnly"));
     }
 
 }