SLING-3574 - JDBC DataSource Provider bundle

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1596184 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..381e79f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,59 @@
+Apache Sling DataSource Provider
+================================
+
+This bundle enables creating and configuring JDBC DataSource in OSGi environment based on 
+OSGi configuration. It uses [Tomcat JDBC Pool][1] as the JDBC Connection Pool provider.
+
+1. Supports configuring the DataSource based on OSGi config wihich rich metatype
+2. Supports deploying of JDBC Driver as independent bundles and not as fragment
+3. Exposes the DataSource stats as JMX MBean 
+
+Driver Loading
+--------------
+
+Loading of JDBC driver is tricky on OSGi env. Mostly one has to attach the Driver bundle as a
+fragment bundle to the code which creates the JDBC Connection. 
+
+With JDBC 4 onwards the Driver class can be loaded via Java SE Service Provider mechanism (SPM)
+JDBC 4.0 drivers must include the file META-INF/services/java.sql.Driver. This file contains 
+the name of the JDBC driver's implementation of java.sql.Driver. For example, to load the JDBC 
+driver to connect to a Apache Derby database, the META-INF/services/java.sql.Driver file would 
+contain the following entry:
+
+    org.apache.derby.jdbc.EmbeddedDriver
+    
+Sling DataSource Provider bundles maintains a `DriverRegistry` which contains mapping of Driver
+bundle to Driver class supported by it. With this feature there is no need to wrap the Driver
+bundle as fragment to DataSource provider bundle
+
+
+Configuration
+-------------
+
+1. Install the current bundle
+2. Install the JDBC Driver bundle
+3. Configure the DataSource from OSGi config for PID `org.apache.sling.extensions.datasource.DataSourceFactory`
+
+If Felix WebConsole is used then you can configure it via Configuration UI at
+http://localhost:8080/system/console/configMgr/org.apache.sling.extensions.datasource.DataSourceFactory
+
+Usage
+-----
+
+Once the required configuration is done the `DataSource` would be registered as part of the OSGi Service Registry
+The service is registered with service property `datasource.name` whose value is the name of datasource provided in 
+OSGi config. 
+
+Following snippet demonstrates accessing the DataSource named `foo` via DS annotation
+
+    import javax.sql.DataSource;
+    import org.apache.felix.scr.annotations.Reference;
+    
+    public class DSExample {
+        
+        @Reference(target = "(&(objectclass=javax.sql.DataSource)(datasource.name=foo))")
+        private DataSource dataSource;
+    }
+
+ 
+[1]: http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..21ed802
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,279 @@
+<?xml version="1.0"?>
+<!--
+/*************************************************************************
+ *
+ * ADOBE CONFIDENTIAL
+ * __________________
+ *
+ *  Copyright 2012 Adobe Systems Incorporated
+ *  All Rights Reserved.
+ *
+ * NOTICE:  All information contained herein is, and remains
+ * the property of Adobe Systems Incorporated and its suppliers,
+ * if any.  The intellectual and technical concepts contained
+ * herein are proprietary to Adobe Systems Incorporated and its
+ * suppliers and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * from Adobe Systems Incorporated.
+ **************************************************************************/
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.sling</groupId>
+    <artifactId>sling</artifactId>
+    <version>18</version>
+  </parent>
+
+  <artifactId>org.apache.sling.extensions.datasource</artifactId>
+  <packaging>bundle</packaging>
+  <version>0.0.1-SNAPSHOT</version>
+
+  <name>Apache Sling DataSource Provider</name>
+  <description>
+    Enables creation of DataSource based on OSGi configuration
+  </description>
+
+  <properties>
+    <sling.java.version>6</sling.java.version>
+    <pax.exam.version>3.4.0</pax.exam.version>
+    <pax.url.version>1.6.0</pax.url.version>
+    <bundle.build.name>
+      ${basedir}/target
+    </bundle.build.name>
+    <bundle.file.name>
+      ${bundle.build.name}/${project.build.finalName}.jar
+    </bundle.file.name>
+  </properties>
+
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Embed-Dependency>
+              org.apache.sling.commons.osgi;inline=org/apache/sling/commons/osgi/PropertiesUtil.class,
+              tomcat-jdbc,
+              tomcat-juli
+            </Embed-Dependency>
+          </instructions>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-scr-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.servicemix.tooling</groupId>
+        <artifactId>depends-maven-plugin</artifactId>
+        <version>1.2</version>
+        <executions>
+          <execution>
+            <id>generate-depends-file</id>
+            <goals>
+              <goal>generate-depends-file</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-failsafe-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>integration-test</goal>
+              <goal>verify</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <systemPropertyVariables>
+            <project.bundle.file>${bundle.file.name}</project.bundle.file>
+            <coverage.command>${coverage.command}</coverage.command>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <version>4.3.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <version>4.3.1</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.commons.osgi</artifactId>
+      <version>2.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-jdbc</artifactId>
+      <version>7.0.53</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-juli</artifactId>
+      <version>7.0.53</version>
+    </dependency>
+
+    <!-- OSGi test -->
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.framework</artifactId>
+      <version>4.4.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.exam</groupId>
+      <artifactId>pax-exam-container-forked</artifactId>
+      <version>${pax.exam.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.exam</groupId>
+      <artifactId>pax-exam-junit4</artifactId>
+      <version>${pax.exam.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.exam</groupId>
+      <artifactId>pax-exam-link-mvn</artifactId>
+      <version>${pax.exam.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.url</groupId>
+      <artifactId>pax-url-aether</artifactId>
+      <version>${pax.url.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.url</groupId>
+      <artifactId>pax-url-reference</artifactId>
+      <version>${pax.url.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.url</groupId>
+      <artifactId>pax-url-wrap</artifactId>
+      <version>${pax.url.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.configadmin</artifactId>
+      <version>1.8.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.scr</artifactId>
+      <version>1.8.2</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.h2database</groupId>
+      <artifactId>h2</artifactId>
+      <version>1.4.178</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <version>1.5.2</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-beanutils</groupId>
+      <artifactId>commons-beanutils-core</artifactId>
+      <version>1.8.3</version>
+    </dependency>
+  </dependencies>
+
+  <profiles>
+    <!--
+        copy the package such that IDEs may easily use it without
+        setting the system property
+    -->
+    <profile>
+      <id>ide</id>
+      <build>
+        <plugins>
+          <plugin>
+            <artifactId>maven-antrun-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>bundle-file-create</id>
+                <phase>package</phase>
+                <goals>
+                  <goal>run</goal>
+                </goals>
+                <configuration>
+                  <target>
+                    <copy file="${project.build.directory}/${project.build.finalName}.jar" tofile="${project.build.directory}/bundle.jar" />
+                  </target>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+    <profile>
+      <id>coverage</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.jacoco</groupId>
+            <artifactId>jacoco-maven-plugin</artifactId>
+            <executions>
+              <!-- Default to setup argLine required by surefire -->
+              <execution>
+                <id>prepare-agent-surefire</id>
+                <phase>test-compile</phase>
+                <goals>
+                  <goal>prepare-agent</goal>
+                </goals>
+                <configuration>
+                  <propertyName>coverage.command</propertyName>
+                  <includes>
+                    <include>org.apache.sling.extensions.datasource.internal.*</include>
+                  </includes>
+                </configuration>
+              </execution>
+              <execution>
+                <id>report</id>
+                <phase>post-integration-test</phase>
+                <goals>
+                  <goal>report</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git a/src/main/java/org/apache/juli/logging/DirectJDKLog.java b/src/main/java/org/apache/juli/logging/DirectJDKLog.java
new file mode 100644
index 0000000..f49ac08
--- /dev/null
+++ b/src/main/java/org/apache/juli/logging/DirectJDKLog.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.juli.logging;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Overriding the DirectJDKLog impl to delegate the logging to Slf4j
+ */
+@SuppressWarnings("UnusedDeclaration")
+class DirectJDKLog implements Log {
+    private final Logger logger;
+
+    public DirectJDKLog(String name) {
+        this.logger = LoggerFactory.getLogger(name);
+    }
+
+    static Log getInstance(String name) {
+        return new DirectJDKLog( name );
+    }
+
+    public boolean isDebugEnabled() {
+        return logger.isDebugEnabled();
+    }
+
+    public boolean isErrorEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    public boolean isFatalEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    public boolean isInfoEnabled() {
+        return logger.isInfoEnabled();
+    }
+
+    public boolean isTraceEnabled() {
+        return logger.isTraceEnabled();
+    }
+
+    public boolean isWarnEnabled() {
+        return logger.isWarnEnabled();
+    }
+
+    public void trace(Object message) {
+        logger.trace(String.valueOf(message));
+    }
+
+    public void trace(Object message, Throwable t) {
+        logger.trace(String.valueOf(message), t);
+    }
+
+    public void debug(Object message) {
+        logger.debug(String.valueOf(message));
+    }
+
+    public void debug(Object message, Throwable t) {
+        logger.debug(String.valueOf(message), t);
+    }
+
+    public void info(Object message) {
+        logger.info(String.valueOf(message));
+    }
+
+    public void info(Object message, Throwable t) {
+        logger.info(String.valueOf(message), t);
+    }
+
+    public void warn(Object message) {
+        logger.warn(String.valueOf(message));
+    }
+
+    public void warn(Object message, Throwable t) {
+        logger.warn(String.valueOf(message), t);
+    }
+
+    public void error(Object message) {
+        logger.error(String.valueOf(message));
+    }
+
+    public void error(Object message, Throwable t) {
+        logger.error(String.valueOf(message), t);
+    }
+
+    public void fatal(Object message) {
+        logger.error(String.valueOf(message));
+    }
+
+    public void fatal(Object message, Throwable t) {
+        logger.error(String.valueOf(message), t);
+    }
+}
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
new file mode 100644
index 0000000..f653fcc
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/datasource/internal/DataSourceFactory.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.datasource.internal;
+
+import java.lang.management.ManagementFactory;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+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.Property;
+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.PoolConfiguration;
+import org.apache.tomcat.jdbc.pool.PoolProperties;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+        name = DataSourceFactory.NAME,
+        label = "%datasource.component.name",
+        description = "%datasource.component.description",
+        metatype = true,
+        configurationFactory = true,
+        policy = ConfigurationPolicy.REQUIRE
+)
+public class DataSourceFactory {
+    public static final String NAME = "org.apache.sling.extensions.datasource.DataSourceFactory";
+
+    @Property
+    static final String PROP_DATASOURCE_NAME = "datasource.name";
+
+    @Property
+    static final String PROP_DRIVERCLASSNAME = "driverClassName";
+
+    @Property
+    static final String PROP_URL = "url";
+
+    @Property
+    static final String PROP_USERNAME = "username";
+
+    @Property(passwordValue = "")
+    static final String PROP_PASSWORD = "password";
+
+    @Property(intValue = PoolProperties.DEFAULT_MAX_ACTIVE)
+    static final String PROP_MAXACTIVE = "maxActive";
+
+    @Property(value = {}, cardinality = 1024)
+    static final String PROP_DATASOURCE_SVC_PROPS = "datasource.svc.properties";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference
+    private DriverRegistry driverRegistry;
+
+    private String name;
+
+    private ObjectName jmxName;
+
+    private ServiceRegistration dsRegistration;
+
+    private DataSource dataSource;
+
+    @Activate
+    protected void activate(BundleContext bundleContext, Map<String,?> config) throws Exception {
+        Properties props = new Properties();
+        name = PropertiesUtil.toString(config.get(PROP_DATASOURCE_NAME), null);
+
+        checkArgument(name != null, "DataSource name must be specified via [%s] property", PROP_DATASOURCE_NAME);
+
+        //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());
+        }
+
+        props.setProperty(org.apache.tomcat.jdbc.pool.DataSourceFactory.OBJECT_NAME, name);
+
+        for(String propName : DummyDataSourceFactory.getPropertyNames()){
+            String value = PropertiesUtil.toString(config.get(propName), null);
+            if(value != null){
+                props.setProperty(propName, value);
+            }
+        }
+
+        dataSource = createDataSource(props, bundleContext);
+
+        registerDataSource(bundleContext);
+        registerJmx();
+
+        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);
+
+        //Specify the DataSource such that connection creation logic is handled
+        //by us where we take care of OSGi env
+        poolProperties.setDataSource(driverDataSource);
+
+        org.apache.tomcat.jdbc.pool.DataSource dataSource =
+                new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
+        //initialise the pool itself
+        ConnectionPool pool = dataSource.createPool();
+        driverDataSource.setJmxPool(pool.getJmxPool());
+
+        // Return the configured DataSource instance
+        return dataSource;
+    }
+
+    private void registerDataSource(BundleContext bundleContext) {
+        Dictionary<String,Object> svcProps = new Hashtable<String, Object>();
+        svcProps.put(PROP_DATASOURCE_NAME, 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);
+    }
+
+    private void registerJmx() throws MalformedObjectNameException {
+        if(dataSource instanceof DataSourceProxy){
+            org.apache.tomcat.jdbc.pool.jmx.ConnectionPool pool =
+                    ((DataSourceProxy) dataSource).getPool().getJmxPool();
+
+            if(pool == 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));
+            jmxName = new ObjectName("org.apache.sling", table);
+
+            try {
+                MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+                mbs.registerMBean(pool, jmxName);
+            }catch(Exception e){
+                log.warn("Error occurred while registering the JMX Bean for " +
+                        "connection pool with name {}",jmxName, e);
+            }
+        }
+    }
+
+    private void unregisterJmx(){
+        try {
+            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);
+        }
+    }
+
+    private String getDataSourceDetails() {
+        if(dataSource instanceof DataSourceProxy){
+            return ((DataSourceProxy) dataSource).getPoolProperties().toString();
+        }
+        return "<UNKNOWN>";
+    }
+
+
+
+    public static void checkArgument(boolean expression,
+                                     String errorMessageTemplate,
+                                     Object... errorMessageArgs) {
+        if (!expression) {
+            throw new IllegalArgumentException(
+                    String.format(errorMessageTemplate, errorMessageArgs));
+        }
+    }
+
+    /**
+     * Dummy impl to enable access to protected fields
+     */
+    private static class DummyDataSourceFactory extends org.apache.tomcat.jdbc.pool.DataSourceFactory {
+        static String[] getPropertyNames(){
+            return ALL_PROPERTIES;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/extensions/datasource/internal/DriverDataSource.java b/src/main/java/org/apache/sling/extensions/datasource/internal/DriverDataSource.java
new file mode 100644
index 0000000..e2e0b32
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/datasource/internal/DriverDataSource.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.datasource.internal;
+
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.apache.tomcat.jdbc.pool.ConnectionPool;
+import org.apache.tomcat.jdbc.pool.PoolConfiguration;
+import org.apache.tomcat.jdbc.pool.PoolUtilities;
+import org.osgi.framework.BundleContext;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DataSource implementation which only implements the Connection creation part. Tomcat
+ * JDBC currently does not support specifying the Drive instance directly. While running
+ * in OSGi env DriverRegistry maintains a list of seen driver instances.
+ *
+ * DriverDataSource make use of the DriverRegistry to lookup right Driver instance. This avoid
+ * the requirement of having the Driver OSGi bundle attaches as fragments to our bundle
+ */
+class DriverDataSource implements DataSource {
+    private final PoolConfiguration poolProperties;
+    private final DriverRegistry driverRegistry;
+    private final BundleContext bundleContext;
+    private final org.slf4j.Logger log = LoggerFactory.getLogger(getClass());
+    private org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmxPool;
+    private Driver driver;
+
+    public DriverDataSource(PoolConfiguration poolProperties, DriverRegistry driverRegistry,
+                            BundleContext bundleContext) {
+        this.poolProperties = poolProperties;
+        this.driverRegistry = driverRegistry;
+        this.bundleContext = bundleContext;
+    }
+
+    public Connection getConnection() throws SQLException {
+        return getConnection(null, null);
+    }
+
+    public Connection getConnection(String usr, String pwd) throws SQLException {
+        Properties properties = PoolUtilities.clone(poolProperties.getDbProperties());
+        if(usr == null){
+            usr = poolProperties.getUsername();
+        }
+        if(pwd == null){
+            pwd= poolProperties.getPassword();
+        }
+
+        if (usr != null) properties.setProperty(PoolUtilities.PROP_USER, usr);
+        if (pwd != null) properties.setProperty(PoolUtilities.PROP_PASSWORD, pwd);
+
+        String driverURL = poolProperties.getUrl();
+        Connection connection;
+        try {
+            connection = getDriver().connect(driverURL, properties);
+        } catch (Exception x) {
+            if (log.isDebugEnabled()) {
+                log.debug("Unable to connect to database.", x);
+            }
+            //Based on logic in org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver()
+            if (jmxPool!=null) {
+                jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_CONNECT,
+                        ConnectionPool.getStackTrace(x));
+            }
+            if (x instanceof SQLException) {
+                throw (SQLException)x;
+            } else {
+                SQLException ex = new SQLException(x.getMessage());
+                ex.initCause(x);
+                throw ex;
+            }
+        }
+        if (connection==null) {
+            throw new SQLException("Driver:"+driver+" returned null for URL:"+driverURL);
+        }
+
+        return connection;
+    }
+
+    public void setJmxPool(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool jmxPool) {
+        this.jmxPool = jmxPool;
+    }
+
+    //~-------------------------------------< DataSource >
+
+    public PrintWriter getLogWriter() throws SQLException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    public void setLogWriter(PrintWriter out) throws SQLException {
+
+    }
+
+    public void setLoginTimeout(int seconds) throws SQLException {
+
+    }
+
+    public int getLoginTimeout() throws SQLException {
+        return 0;
+    }
+
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    public <T> T unwrap(Class<T> iface) throws SQLException {
+        return null;
+    }
+
+    public boolean isWrapperFor(Class<?> iface) throws SQLException {
+        return false;
+    }
+
+    private Driver getDriver() throws SQLException {
+        if (driver != null) {
+            return driver;
+        }
+        final String url = poolProperties.getUrl();
+
+        Collection<Driver> drivers = driverRegistry.getDrivers();
+        if(!drivers.isEmpty()) {
+            log.debug("Looking for driver for [{}] against registered drivers", url);
+            driver = findMatchingDriver(drivers);
+        }
+
+        if(driver == null){
+            log.debug("Looking for driver for [{}] via provided className [{}]",
+                    url, poolProperties.getDriverClassName());
+            driver = loadDriverClass();
+        }
+
+        if(driver == null){
+            //This one is redundant as DriverManager would filter out drivers
+            //whose classes are not visible from our bundle classloader which
+            //means that this list would be empty in most cases
+            log.debug("Looking for driver from DriverManager");
+            driver = findMatchingDriver(Collections.list(DriverManager.getDrivers()));
+        }
+
+        if(driver == null){
+            String msg = String.format("Not able to find any matching driver for url [%s] " +
+                    "and driverClassName [%s]",url,poolProperties.getDriverClassName());
+            throw new SQLException(msg);
+        }
+
+        return driver;
+    }
+
+    private Driver loadDriverClass() throws SQLException {
+        try {
+              log.debug("Instantiating driver using class: {} [url={}]",
+                      poolProperties.getDriverClassName(),poolProperties.getUrl());
+                return (Driver) bundleContext.getBundle()
+                        .loadClass(poolProperties.getDriverClassName()).newInstance();
+        } catch (java.lang.Exception cn) {
+            log.debug("Unable to instantiate JDBC driver.", cn);
+            SQLException ex = new SQLException(cn.getMessage());
+            ex.initCause(cn);
+            throw ex;
+        }
+    }
+
+    private Driver findMatchingDriver(Collection<Driver> drivers) {
+        final String url = poolProperties.getUrl();
+        for (Driver driver : drivers) {
+            try {
+                if (driver.acceptsURL(url)) {
+                    return driver;
+                }
+            } catch (SQLException e) {
+                log.debug("Error occurred while matching driver against url {}", url, e);
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/org/apache/sling/extensions/datasource/internal/DriverRegistry.java b/src/main/java/org/apache/sling/extensions/datasource/internal/DriverRegistry.java
new file mode 100644
index 0000000..732433e
--- /dev/null
+++ b/src/main/java/org/apache/sling/extensions/datasource/internal/DriverRegistry.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.datasource.internal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.sql.Driver;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.BundleTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Service(value = DriverRegistry.class)
+public class DriverRegistry {
+    private static final String DRIVER_SERVICE = "META-INF/services/"
+            + Driver.class.getName();
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private BundleTracker<Collection<DriverInfo>> bundleTracker;
+
+    private ConcurrentMap<DriverInfo, Driver> driverInfos = new ConcurrentHashMap<DriverInfo, Driver>();
+
+    public Collection<Driver> getDrivers() {
+        return driverInfos.values();
+    }
+
+    @Activate
+    protected void activate(BundleContext bundleContext) {
+        bundleTracker = new BundleTracker<Collection<DriverInfo>>(bundleContext,
+                Bundle.ACTIVE, new DriverBundleTracker());
+        bundleTracker.open();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        if (bundleTracker != null) {
+            bundleTracker.close();
+        }
+    }
+
+    private void registerDrivers(Collection<DriverInfo> drivers) {
+        for (DriverInfo di : drivers) {
+            driverInfos.put(di, di.driver);
+            log.info("Registering {}", di);
+        }
+    }
+
+    private void deregisterDrivers(Collection<DriverInfo> drivers) {
+        for (DriverInfo di : drivers) {
+            driverInfos.remove(di);
+            log.info("Deregistering {}", di);
+        }
+    }
+
+    private Collection<DriverInfo> createDrivers(final Bundle bundle) {
+        URL url = bundle.getEntry(DRIVER_SERVICE);
+        InputStream ins = null;
+        final List<DriverInfo> extensions = new ArrayList<DriverInfo>();
+        try {
+            ins = url.openStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (!line.startsWith("#") && line.trim().length() > 0) {
+                    try {
+                        Class<?> clazz = bundle.loadClass(line);
+                        extensions.add(new DriverInfo(bundle, (Driver) clazz.newInstance()));
+                    } catch (Throwable t) {
+                        log.warn("Cannot register java.sql.Driver [{}] from bundle [{}]",
+                                new Object[]{line, bundle, t});
+                    }
+                }
+            }
+        } catch (IOException ioe) {
+            // ignore
+        } finally {
+            if (ins != null) {
+                try {
+                    ins.close();
+                } catch (IOException ignore) {
+                }
+            }
+        }
+
+        return extensions;
+    }
+
+    private class DriverBundleTracker implements BundleTrackerCustomizer<Collection<DriverInfo>> {
+        public Collection<DriverInfo> addingBundle(Bundle bundle, BundleEvent event) {
+            if (bundle.getEntry(DRIVER_SERVICE) != null) {
+                Collection<DriverInfo> drivers = createDrivers(bundle);
+                registerDrivers(drivers);
+                return drivers;
+            }
+            return null;
+        }
+
+        public void modifiedBundle(Bundle bundle, BundleEvent event, Collection<DriverInfo> object) {
+
+        }
+
+        public void removedBundle(Bundle bundle, BundleEvent event, Collection<DriverInfo> drivers) {
+            deregisterDrivers(drivers);
+        }
+    }
+
+    private static class DriverInfo {
+        final Driver driver;
+        final Bundle bundle;
+
+        DriverInfo(Bundle bundle, Driver driver) {
+            this.driver = driver;
+            this.bundle = bundle;
+        }
+
+        @SuppressWarnings("RedundantIfStatement")
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            DriverInfo that = (DriverInfo) o;
+
+            if (!(bundle == that.bundle)) return false;
+            if (!(driver == that.driver)) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = driver.hashCode();
+            result = 31 * result + bundle.hashCode();
+            return result;
+        }
+
+        public String toString() {
+            return String.format("java.sql.Driver [%s] from bundle [%s]", driver.getClass().getName(), bundle);
+        }
+    }
+}
diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties
new file mode 100644
index 0000000..e0e9e5c
--- /dev/null
+++ b/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,47 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+#
+
+# 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.name.name = Datasource name(*)
+datasource.name.description = Name of this data source (required)
+
+datasource.svc.properties.name = Additional Properties
+datasource.svc.properties.description = Properties that are added additionally to the underlying DataSource \
+  provider(name=value pairs). Refer to http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Common_Attributes \
+  for various property names and details.
+
+url.name=JDBC connection URI
+url.description=URI of the JDBC connection to use e.g. jdbc:mysql://localhost:3306/mysql
+
+driverClassName.name=JDBC driver class
+driverClassName.description=Java class name of the JDBC driver to use
+
+username.name=Username
+username.description=The connection username to be passed to our JDBC driver to establish a connection
+
+password.name=Password
+password.description=The connection password to be passed to our JDBC driver to establish a connection.
+
+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
diff --git a/src/test/java/org/apache/sling/extensions/datasource/DataSourceIT.java b/src/test/java/org/apache/sling/extensions/datasource/DataSourceIT.java
new file mode 100644
index 0000000..08720e2
--- /dev/null
+++ b/src/test/java/org/apache/sling/extensions/datasource/DataSourceIT.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.datasource;
+
+import java.sql.Connection;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.inject.Inject;
+import javax.sql.DataSource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.osgi.framework.Filter;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+import static org.apache.commons.beanutils.BeanUtils.getProperty;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+@RunWith(PaxExam.class)
+public class DataSourceIT extends DataSourceTestBase{
+
+    static {
+        //paxRunnerVmOption = DEBUG_VM_OPTION;
+    }
+
+
+    String PID = "org.apache.sling.extensions.datasource.DataSourceFactory";
+
+    @Inject
+    ConfigurationAdmin ca;
+
+    @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("maxActive",70);
+        config.update(p);
+
+        Filter filter = context.createFilter("(&(objectclass=javax.sql.DataSource)(datasource.name=test))");
+        ServiceTracker<DataSource, DataSource> st =
+                new ServiceTracker<DataSource, DataSource>(context, filter, null);
+        st.open();
+
+        DataSource ds = st.waitForService(10000);
+        assertNotNull(ds);
+
+        Connection conn = ds.getConnection();
+        assertNotNull(conn);
+
+        //Cannot access directly so access via reflection
+        assertEquals("70", getProperty(ds, "poolProperties.maxActive"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/extensions/datasource/DataSourceTestBase.java b/src/test/java/org/apache/sling/extensions/datasource/DataSourceTestBase.java
new file mode 100644
index 0000000..7415017
--- /dev/null
+++ b/src/test/java/org/apache/sling/extensions/datasource/DataSourceTestBase.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.extensions.datasource;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.osgi.framework.BundleContext;
+
+import static org.ops4j.pax.exam.CoreOptions.cleanCaches;
+import static org.ops4j.pax.exam.CoreOptions.composite;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.keepCaches;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemPackage;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.CoreOptions.systemTimeout;
+import static org.ops4j.pax.exam.CoreOptions.workingDirectory;
+import static org.ops4j.pax.exam.CoreOptions.wrappedBundle;
+import static org.ops4j.pax.exam.util.PathUtils.getBaseDir;
+
+public abstract class DataSourceTestBase {
+    @Inject
+    protected BundleContext context;
+
+    // the name of the system property providing the bundle file to be installed
+    // and tested
+    protected static final String BUNDLE_JAR_SYS_PROP = "project.bundle.file";
+
+    // the default bundle jar file name
+    protected static final String BUNDLE_JAR_DEFAULT = "target/bundle.jar";
+
+    // the name of the system property which captures the jococo coverage agent command
+    //if specified then agent would be specified otherwise ignored
+    protected static final String COVERAGE_COMMAND = "coverage.command";
+
+    // the JVM option to set to enable remote debugging
+    @SuppressWarnings("UnusedDeclaration")
+    protected static final String DEBUG_VM_OPTION = "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=31313";
+
+    // the actual JVM option set, extensions may implement a static
+    // initializer overwriting this value to have the configuration()
+    // method include it when starting the OSGi framework JVM
+    protected static String paxRunnerVmOption = null;
+
+    @Configuration
+    public Option[] config() throws IOException {
+        final String bundleFileName = System.getProperty(BUNDLE_JAR_SYS_PROP, BUNDLE_JAR_DEFAULT);
+        final File bundleFile = new File(bundleFileName);
+        if (!bundleFile.canRead()) {
+            throw new IllegalArgumentException("Cannot read from bundle file " + bundleFileName + " specified in the "
+                    + BUNDLE_JAR_SYS_PROP + " system property. Try building the project first " +
+                    "with 'mvn clean install -Pide -DskipTests'");
+        }
+        return options(
+                // the current project (the bundle under test)
+                CoreOptions.bundle(bundleFile.toURI().toString()),
+                mavenBundle("com.h2database", "h2").versionAsInProject(),
+                wrappedBundle(mavenBundle("commons-beanutils", "commons-beanutils-core").versionAsInProject()),
+                mavenBundle("org.slf4j", "slf4j-simple").versionAsInProject().noStart(),
+                mavenBundle("org.apache.felix", "org.apache.felix.scr").versionAsInProject(),
+                mavenBundle("org.apache.felix", "org.apache.felix.configadmin").versionAsInProject(),
+                junitBundles(),
+                systemProperty("pax.exam.osgi.unresolved.fail").value("fail"),
+                systemPackage("com.sun.tools.attach"),
+                cleanCaches(),
+                addCodeCoverageOption(),
+                addDebugOptions()
+        );
+    }
+
+    private static Option addCodeCoverageOption() {
+        String coverageCommand = System.getProperty(COVERAGE_COMMAND);
+        if (coverageCommand != null) {
+            return CoreOptions.vmOption(coverageCommand);
+        }
+        return null;
+    }
+
+    private static Option addDebugOptions() throws IOException {
+        if (paxRunnerVmOption != null) {
+            String workDir = FilenameUtils.concat(getBaseDir(), "target/pax");
+            File workDirFile = new File(workDir);
+            if (workDirFile.exists()) {
+                FileUtils.deleteDirectory(workDirFile);
+            }
+            return composite(CoreOptions.vmOption(paxRunnerVmOption), keepCaches(),
+                    systemTimeout(TimeUnit.MINUTES.toMillis(10)), workingDirectory(workDir));
+        }
+        return null;
+    }
+}