From 71078d763c3aa65dae739a1d90d5a6cc8457ead1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Mon, 1 Aug 2022 11:54:04 +0200 Subject: [PATCH] Support for the Data Service Specification Add a first basic implementation of the DataSourceFactory --- pom.xml | 38 ++++++- .../java/org/sqlite/osgi/SQLiteActivator.java | 23 ++++ .../sqlite/osgi/SQLiteDataSourceFactory.java | 99 +++++++++++++++++ src/test/java/org/sqlite/OSGiTest.java | 103 ++++++++++++++++++ 4 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/sqlite/osgi/SQLiteActivator.java create mode 100644 src/main/java/org/sqlite/osgi/SQLiteDataSourceFactory.java create mode 100644 src/test/java/org/sqlite/OSGiTest.java diff --git a/pom.xml b/pom.xml index f91cdff71..9e356e0b4 100644 --- a/pom.xml +++ b/pom.xml @@ -12,8 +12,11 @@ UTF-8 5.7.2 2.22.2 + 1.2.1 + 0.0.1 + 3.18.0 - + The Apache Software License, Version 2.0 @@ -149,8 +152,9 @@ org.xerial.sqlite-jdbc;singleton:=true - *;resolution:=optional + * + org.sqlite.osgi.SQLiteActivator @@ -375,5 +379,35 @@ 3.12.4 test + + + org.osgi + org.osgi.service.jdbc + 1.0.1 + provided + + + org.osgi + org.osgi.framework + 1.10.0 + provided + + + org.osgi + org.osgi.test.junit5 + ${osgi-test.version} + test + + + de.laeubisoft + osgi-test-framework + ${osgi-test-framework.version} + test + + + org.eclipse.platform + org.eclipse.osgi + ${equinoxVersion} + diff --git a/src/main/java/org/sqlite/osgi/SQLiteActivator.java b/src/main/java/org/sqlite/osgi/SQLiteActivator.java new file mode 100644 index 000000000..8962c35f4 --- /dev/null +++ b/src/main/java/org/sqlite/osgi/SQLiteActivator.java @@ -0,0 +1,23 @@ +package org.sqlite.osgi; + +import java.util.Hashtable; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.service.jdbc.DataSourceFactory; +import org.sqlite.JDBC; +import org.sqlite.SQLiteJDBCLoader; + +public class SQLiteActivator implements BundleActivator { + + @Override + public void start(BundleContext context) throws Exception { + Hashtable properties = new Hashtable<>(); + properties.put(DataSourceFactory.OSGI_JDBC_DRIVER_CLASS, JDBC.class.getName()); + properties.put(DataSourceFactory.OSGI_JDBC_DRIVER_NAME, "SQLite JDBC driver"); + properties.put(DataSourceFactory.OSGI_JDBC_DRIVER_VERSION, SQLiteJDBCLoader.getVersion()); + context.registerService(DataSourceFactory.class, new SQLiteDataSourceFactory(), properties); + } + + @Override + public void stop(BundleContext context) throws Exception {} +} diff --git a/src/main/java/org/sqlite/osgi/SQLiteDataSourceFactory.java b/src/main/java/org/sqlite/osgi/SQLiteDataSourceFactory.java new file mode 100644 index 000000000..63ca0f099 --- /dev/null +++ b/src/main/java/org/sqlite/osgi/SQLiteDataSourceFactory.java @@ -0,0 +1,99 @@ +package org.sqlite.osgi; + +import java.sql.Driver; +import java.sql.SQLException; +import java.util.Properties; +import java.util.function.Consumer; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.DataSource; +import javax.sql.XADataSource; +import org.osgi.service.jdbc.DataSourceFactory; +import org.sqlite.JDBC; +import org.sqlite.SQLiteConfig; +import org.sqlite.SQLiteDataSource; +import org.sqlite.javax.SQLiteConnectionPoolDataSource; + +public class SQLiteDataSourceFactory implements DataSourceFactory { + + @Override + public DataSource createDataSource(Properties props) throws SQLException { + SQLiteDataSource dataSource = new SQLiteDataSource(getConfig(props)); + setBasicDataSourceProperties(props, dataSource); + return dataSource; + } + + @Override + public ConnectionPoolDataSource createConnectionPoolDataSource(Properties props) + throws SQLException { + + SQLiteConnectionPoolDataSource poolDataSource = + new SQLiteConnectionPoolDataSource(getConfig(props)); + setBasicDataSourceProperties(props, poolDataSource); + return poolDataSource; + } + + @Override + public XADataSource createXADataSource(Properties props) throws SQLException { + throw new SQLException("XADataSource is not supported by SQLite"); + } + + @Override + public Driver createDriver(Properties props) throws SQLException { + return new JDBC(); + } + + /** + * Method to transfer a property to a setter method + * + * @param props + * @param key + * @param consumer + */ + private static void setStandardProperty( + Properties props, String key, Consumer consumer) { + String value = props.getProperty(key); + if (value != null) { + consumer.accept(value); + } + } + + /** + * Set basic properties common to {@link SQLiteDataSource}s + * + * @param props + * @param dataSource + */ + private static void setBasicDataSourceProperties( + Properties props, SQLiteDataSource dataSource) { + if (props != null) { + setStandardProperty( + props, DataSourceFactory.JDBC_DATABASE_NAME, dataSource::setDatabaseName); + setStandardProperty(props, DataSourceFactory.JDBC_URL, dataSource::setUrl); + } + } + + /** + * converts user supplied properties into an internal {@link SQLiteConfig} object + * + * @param userProperties the user properties, might be null + * @return a {@link SQLiteConfig} config object reflecting the given user properties + */ + private static SQLiteConfig getConfig(Properties userProperties) { + SQLiteConfig config; + if (userProperties == null) { + config = new SQLiteConfig(); + } else { + Properties properties = new Properties(userProperties); + setStandardProperty( + userProperties, + DataSourceFactory.JDBC_USER, + v -> properties.setProperty("user", v)); + setStandardProperty( + userProperties, + DataSourceFactory.JDBC_PASSWORD, + v -> properties.setProperty("pass", v)); + config = new SQLiteConfig(properties); + } + return config; + } +} diff --git a/src/test/java/org/sqlite/OSGiTest.java b/src/test/java/org/sqlite/OSGiTest.java new file mode 100644 index 000000000..40be5fd25 --- /dev/null +++ b/src/test/java/org/sqlite/OSGiTest.java @@ -0,0 +1,103 @@ +package org.sqlite; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import de.laeubisoft.osgi.junit5.framework.annotations.EmbeddedFramework; +import de.laeubisoft.osgi.junit5.framework.annotations.WithBundle; +import de.laeubisoft.osgi.junit5.framework.extension.FrameworkExtension; +import de.laeubisoft.osgi.junit5.framework.services.FrameworkEvents; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.DataSource; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.osgi.framework.launch.Framework; +import org.osgi.service.jdbc.DataSourceFactory; +import org.osgi.test.common.annotation.InjectService; +import org.osgi.test.common.service.ServiceAware; +import org.osgi.test.junit5.service.ServiceExtension; +import org.sqlite.javax.SQLiteConnectionPoolDataSource; + +@WithBundle(value = "org.xerial.sqlite-jdbc", start = true, isolated = true) +@WithBundle(value = "org.osgi.service.jdbc") +@ExtendWith(ServiceExtension.class) +@ExtendWith(FrameworkExtension.class) +public class OSGiTest { + + @BeforeAll + public static void beforeTest( + @EmbeddedFramework Framework framework, + @InjectService FrameworkEvents frameworkEvents) { + FrameworkExtension.printBundles(framework, System.out::println); + FrameworkExtension.printComponents(framework, System.out::println); + frameworkEvents.assertErrorFree(); + } + + @InjectService(filter = "(osgi.jdbc.driver.class=org.sqlite.JDBC)") + ServiceAware datasourceFactory; + + @BeforeEach + public void checkService() { + assertEquals( + 1, + datasourceFactory.size(), + "There should be exactly one DataSourceFactory for SQLite!"); + } + + @Test + public void testCreateDriver() throws SQLException { + Driver driver = getFactory().createDriver(null); + assertClass(JDBC.class, driver); + } + + @Test + public void testCreateDataSource() throws SQLException { + DataSource dataSource = getFactory().createDataSource(null); + assertClass(SQLiteDataSource.class, dataSource); + } + + @Test + public void testCreateConnectionPoolDataSource() throws SQLException { + ConnectionPoolDataSource dataSource = getFactory().createConnectionPoolDataSource(null); + assertClass(SQLiteConnectionPoolDataSource.class, dataSource); + } + + @Test + public void testCreateXADataSource() throws SQLException { + DataSourceFactory service = getFactory(); + assertThrows(SQLException.class, () -> service.createXADataSource(null)); + } + + @Test + public void testCreateConnection() throws SQLException { + + Properties props = new Properties(); + props.setProperty(DataSourceFactory.JDBC_URL, "jdbc:sqlite:"); + DataSource dataSource = getFactory().createDataSource(props); + try (Connection connection = dataSource.getConnection()) { + Statement stmt = connection.createStatement(); + stmt.executeUpdate("create table sample(id, name)"); + stmt.executeUpdate("insert into sample values(1, \"leo\")"); + stmt.executeUpdate("insert into sample values(2, \"yui\")"); + } + } + + private DataSourceFactory getFactory() { + DataSourceFactory service = datasourceFactory.getService(); + assertNotNull(service); + return service; + } + + private static void assertClass(Class clazz, Object obj) { + assertNotNull(obj); + assertEquals(clazz.getName(), obj.getClass().getName()); + } +}