From 289825d23bba4ef99ead835bdb5cd6c5ea02781a Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Mon, 3 Sep 2018 22:59:23 +1000 Subject: [PATCH] Initial attempt at agroal integration This works in JVM mode, but fails due to an NPE in analysis on substrate --- agroal/deployment/pom.xml | 31 +++ .../shamrock/agroal/AgroalProcessor.java | 55 ++++++ .../jboss/shamrock/agroal/AgroalSetup.java | 11 ++ ...rg.jboss.shamrock.deployment.ShamrockSetup | 1 + agroal/pom.xml | 18 ++ agroal/runtime/pom.xml | 61 ++++++ .../agroal/runtime/DataSourceProducer.java | 184 ++++++++++++++++++ .../agroal/runtime/DataSourceTemplate.java | 18 ++ .../graal/AgroalDataSourceReplacement.java | 24 +++ .../deployment/BuildTimeGenerator.java | 14 +- .../shamrock/deployment/ProcessorContext.java | 4 + .../shamrock/deployment/RuntimePriority.java | 1 + .../deployment/buildconfig/BuildConfig.java | 4 + .../graal/MappedByteBufferReplacement.java | 16 ++ examples/strict/pom.xml | 16 ++ .../datasource/DatasourceResource.java | 38 ++++ .../datasource/HSQLDriverReplacement.java | 115 +++++++++++ .../resources/META-INF/shamrock-build.yaml | 3 + .../example/test/DatasourceITCase.java | 10 + .../example/test/DatasourceTestCase.java | 56 ++++++ pom.xml | 35 ++++ .../transactions/TransactionsProcessor.java | 27 ++- 22 files changed, 737 insertions(+), 5 deletions(-) create mode 100644 agroal/deployment/pom.xml create mode 100644 agroal/deployment/src/main/java/org/jboss/shamrock/agroal/AgroalProcessor.java create mode 100644 agroal/deployment/src/main/java/org/jboss/shamrock/agroal/AgroalSetup.java create mode 100644 agroal/deployment/src/main/resources/META-INF/services/org.jboss.shamrock.deployment.ShamrockSetup create mode 100644 agroal/pom.xml create mode 100644 agroal/runtime/pom.xml create mode 100644 agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/DataSourceProducer.java create mode 100644 agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/DataSourceTemplate.java create mode 100644 agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/graal/AgroalDataSourceReplacement.java create mode 100644 core/runtime/src/main/java/org/jboss/shamrock/runtime/graal/MappedByteBufferReplacement.java create mode 100644 examples/strict/src/main/java/org/jboss/shamrock/example/datasource/DatasourceResource.java create mode 100644 examples/strict/src/main/java/org/jboss/shamrock/example/datasource/HSQLDriverReplacement.java create mode 100644 examples/strict/src/main/resources/META-INF/shamrock-build.yaml create mode 100644 examples/strict/src/test/java/org/jboss/shamrock/example/test/DatasourceITCase.java create mode 100644 examples/strict/src/test/java/org/jboss/shamrock/example/test/DatasourceTestCase.java diff --git a/agroal/deployment/pom.xml b/agroal/deployment/pom.xml new file mode 100644 index 000000000..9e0b7235a --- /dev/null +++ b/agroal/deployment/pom.xml @@ -0,0 +1,31 @@ + + + + shamrock-agroal + org.jboss.shamrock + 1.0.0.Alpha1-SNAPSHOT + ../ + + 4.0.0 + + shamrock-agroal-deployment + + + + org.jboss.shamrock + shamrock-core-deployment + + + org.jboss.shamrock + shamrock-agroal-runtime + + + org.jboss.shamrock + shamrock-transactions-deployment + + + + + \ No newline at end of file diff --git a/agroal/deployment/src/main/java/org/jboss/shamrock/agroal/AgroalProcessor.java b/agroal/deployment/src/main/java/org/jboss/shamrock/agroal/AgroalProcessor.java new file mode 100644 index 000000000..5acb75939 --- /dev/null +++ b/agroal/deployment/src/main/java/org/jboss/shamrock/agroal/AgroalProcessor.java @@ -0,0 +1,55 @@ +package org.jboss.shamrock.agroal; + +import java.util.function.Function; + +import javax.inject.Inject; + +import org.jboss.shamrock.agroal.runtime.DataSourceProducer; +import org.jboss.shamrock.agroal.runtime.DataSourceTemplate; +import org.jboss.shamrock.deployment.ArchiveContext; +import org.jboss.shamrock.deployment.BeanDeployment; +import org.jboss.shamrock.deployment.ProcessorContext; +import org.jboss.shamrock.deployment.ResourceProcessor; +import org.jboss.shamrock.deployment.RuntimePriority; +import org.jboss.shamrock.deployment.buildconfig.BuildConfig; +import org.jboss.shamrock.deployment.codegen.BytecodeRecorder; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +class AgroalProcessor implements ResourceProcessor { + + @Inject + private BeanDeployment beanDeployment; + + @Override + public void process(ArchiveContext archiveContext, ProcessorContext processorContext) throws Exception { + BuildConfig config = archiveContext.getBuildConfig(); + BuildConfig.ConfigNode ds = config.getApplicationConfig().get("datasource"); + if (ds.isNull()) { + return; + } + String driver = ds.get("driver").asString(); + String url = ds.get("url").asString(); + if (driver == null) { + throw new RuntimeException("Driver is required"); + } + if (url == null) { + throw new RuntimeException("Driver is required"); + } + String userName = ds.get("username").asString(); + String password = ds.get("password").asString(); + + processorContext.addReflectiveClass(false, false, driver); + beanDeployment.addAdditionalBean(DataSourceProducer.class); + try (BytecodeRecorder bc = processorContext.addDeploymentTask(RuntimePriority.DATASOURCE_DEPLOYMENT)) { + DataSourceTemplate template = bc.getRecordingProxy(DataSourceTemplate.class); + template.addDatasource(null, url, bc.classProxy(driver), userName, password); + } + } + + @Override + public int getPriority() { + return 1; + } +} diff --git a/agroal/deployment/src/main/java/org/jboss/shamrock/agroal/AgroalSetup.java b/agroal/deployment/src/main/java/org/jboss/shamrock/agroal/AgroalSetup.java new file mode 100644 index 000000000..a97f84a36 --- /dev/null +++ b/agroal/deployment/src/main/java/org/jboss/shamrock/agroal/AgroalSetup.java @@ -0,0 +1,11 @@ +package org.jboss.shamrock.agroal; + +import org.jboss.shamrock.deployment.SetupContext; +import org.jboss.shamrock.deployment.ShamrockSetup; + +public class AgroalSetup implements ShamrockSetup { + @Override + public void setup(SetupContext context) { + context.addResourceProcessor(new AgroalProcessor()); + } +} diff --git a/agroal/deployment/src/main/resources/META-INF/services/org.jboss.shamrock.deployment.ShamrockSetup b/agroal/deployment/src/main/resources/META-INF/services/org.jboss.shamrock.deployment.ShamrockSetup new file mode 100644 index 000000000..4c97b5438 --- /dev/null +++ b/agroal/deployment/src/main/resources/META-INF/services/org.jboss.shamrock.deployment.ShamrockSetup @@ -0,0 +1 @@ +org.jboss.shamrock.agroal.AgroalSetup diff --git a/agroal/pom.xml b/agroal/pom.xml new file mode 100644 index 000000000..e20b48a75 --- /dev/null +++ b/agroal/pom.xml @@ -0,0 +1,18 @@ + + + + shamrock-parent + org.jboss.shamrock + 1.0.0.Alpha1-SNAPSHOT + + 4.0.0 + + shamrock-agroal + pom + + deployment + runtime + + \ No newline at end of file diff --git a/agroal/runtime/pom.xml b/agroal/runtime/pom.xml new file mode 100644 index 000000000..c7a9f5646 --- /dev/null +++ b/agroal/runtime/pom.xml @@ -0,0 +1,61 @@ + + + + shamrock-agroal + org.jboss.shamrock + 1.0.0.Alpha1-SNAPSHOT + ../ + + 4.0.0 + + shamrock-agroal-runtime + + + + org.jboss.shamrock + shamrock-core-runtime + + + org.jboss.graalvm + graal-annotations + + + + org.jboss.narayana.jta + narayana-jta + + + + org.jboss.narayana.jts + narayana-jts-integration + + + + org.jboss.spec.javax.transaction + jboss-transaction-api_1.2_spec + + + + io.agroal + agroal-api + + + io.agroal + agroal-narayana + + + io.agroal + agroal-pool + + + + + + + maven-dependency-plugin + + + + \ No newline at end of file diff --git a/agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/DataSourceProducer.java b/agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/DataSourceProducer.java new file mode 100644 index 000000000..c934603da --- /dev/null +++ b/agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/DataSourceProducer.java @@ -0,0 +1,184 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2017, Red Hat, Inc., 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.shamrock.agroal.runtime; + +import java.sql.Driver; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.PreDestroy; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import javax.sql.DataSource; +import javax.sql.XADataSource; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; + +import io.agroal.api.AgroalDataSource; +import io.agroal.api.AgroalDataSourceListener; +import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier; +import io.agroal.api.security.NamePrincipal; +import io.agroal.api.security.SimplePassword; +import io.agroal.api.transaction.TransactionIntegration; +import io.agroal.narayana.NarayanaTransactionIntegration; + +@ApplicationScoped +public class DataSourceProducer { + + private static final Logger log = Logger.getLogger(DataSourceProducer.class.getName()); + + private Class driver; + private String dataSourceName; + private String url; + private String userName; + private String password; + private boolean jta; + private boolean connectable; + private boolean xa; + + private AgroalDataSource agroalDataSource; + + @Inject + private TransactionManager transactionManager; + + @Inject + private TransactionSynchronizationRegistry transactionSynchronizationRegistry; + + private AgroalDataSource dataSource; + + @Produces + @ApplicationScoped + public AgroalDataSource getDatasource() throws SQLException { + Class providerClass = driver; + if (xa) { + if (!XADataSource.class.isAssignableFrom(providerClass)) { + throw new RuntimeException("Driver is not an XA datasource and xa has been configured"); + } + } else { + if (providerClass != null && !DataSource.class.isAssignableFrom(providerClass) && !Driver.class.isAssignableFrom(providerClass)) { + throw new RuntimeException("Driver is an XA datasource and xa has been configured"); + } + } + AgroalDataSourceConfigurationSupplier dataSourceConfiguration = new AgroalDataSourceConfigurationSupplier(); + dataSourceConfiguration.connectionPoolConfiguration().connectionFactoryConfiguration().jdbcUrl(url); + dataSourceConfiguration.connectionPoolConfiguration().connectionFactoryConfiguration().connectionProviderClass(providerClass); + + if (jta || xa) { + TransactionIntegration txIntegration = new NarayanaTransactionIntegration(transactionManager, transactionSynchronizationRegistry, null, connectable); + dataSourceConfiguration.connectionPoolConfiguration().transactionIntegration(txIntegration); + } + // use the name / password from the callbacks + if (userName != null) { + dataSourceConfiguration.connectionPoolConfiguration().connectionFactoryConfiguration().principal(new NamePrincipal(userName)); + } + if (password != null) { + dataSourceConfiguration.connectionPoolConfiguration().connectionFactoryConfiguration().credential(new SimplePassword(password)); + } + + agroalDataSource = AgroalDataSource.from(dataSourceConfiguration); + log.log(Level.INFO, "Started data source " + url); + return agroalDataSource; + } + + @PreDestroy + public void stop() { + agroalDataSource.close(); + } + + public static Logger getLog() { + return log; + } + + public Class getDriver() { + return driver; + } + + public void setDriver(Class driver) { + this.driver = driver; + } + + public String getDataSourceName() { + return dataSourceName; + } + + public void setDataSourceName(String dataSourceName) { + this.dataSourceName = dataSourceName; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isJta() { + return jta; + } + + public void setJta(boolean jta) { + this.jta = jta; + } + + public boolean isConnectable() { + return connectable; + } + + public void setConnectable(boolean connectable) { + this.connectable = connectable; + } + + public boolean isXa() { + return xa; + } + + public void setXa(boolean xa) { + this.xa = xa; + } + + public AgroalDataSource getAgroalDataSource() { + return agroalDataSource; + } + + public void setAgroalDataSource(AgroalDataSource agroalDataSource) { + this.agroalDataSource = agroalDataSource; + } +} diff --git a/agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/DataSourceTemplate.java b/agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/DataSourceTemplate.java new file mode 100644 index 000000000..7439bc5cd --- /dev/null +++ b/agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/DataSourceTemplate.java @@ -0,0 +1,18 @@ +package org.jboss.shamrock.agroal.runtime; + +import org.jboss.shamrock.runtime.BeanContainer; +import org.jboss.shamrock.runtime.ContextObject; + +public class DataSourceTemplate { + + public void addDatasource(@ContextObject("bean.container") BeanContainer beanContainer, + String url, Class driver, + String userName, String password) { + DataSourceProducer producer = beanContainer.instance(DataSourceProducer.class); + producer.setDriver(driver); + producer.setUrl(url); + producer.setUserName(userName); + producer.setPassword(password); + } + +} diff --git a/agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/graal/AgroalDataSourceReplacement.java b/agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/graal/AgroalDataSourceReplacement.java new file mode 100644 index 000000000..98774a29a --- /dev/null +++ b/agroal/runtime/src/main/java/org/jboss/shamrock/agroal/runtime/graal/AgroalDataSourceReplacement.java @@ -0,0 +1,24 @@ +package org.jboss.shamrock.agroal.runtime.graal; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import io.agroal.api.AgroalDataSource; +import io.agroal.api.AgroalDataSourceListener; +import io.agroal.api.configuration.AgroalDataSourceConfiguration; +import io.agroal.pool.DataSource; + +@TargetClass(AgroalDataSource.class) +final class AgroalDataSourceReplacement { + + @Substitute + static AgroalDataSource from(AgroalDataSourceConfiguration configuration, AgroalDataSourceListener... listeners) throws SQLException { + return new DataSource(configuration, listeners); + + } + +} diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java index baa1b9f55..1c93c9474 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/BuildTimeGenerator.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; @@ -164,6 +165,7 @@ public class BuildTimeGenerator { private final Map reflectiveClasses = new LinkedHashMap<>(); private final Set resources = new HashSet<>(); private final Set resourceBundles = new HashSet<>(); + private final List> beforeAnalysisCallback = new ArrayList<>(); @Override public BytecodeRecorder addStaticInitTask(int priority) { @@ -246,6 +248,11 @@ public class BuildTimeGenerator { resourceBundles.add(bundle); } + @Override + public void addBeforeAnalysis(Consumer callback) { + beforeAnalysisCallback.add(callback); + } + void writeMainClass() throws IOException { Collections.sort(tasks); @@ -298,11 +305,16 @@ public class BuildTimeGenerator { ClassCreator file = new ClassCreator(ClassOutput.gizmoAdaptor(output, true), GRAAL_AUTOFEATURE, null, Object.class.getName(), "org/graalvm/nativeimage/Feature"); file.addAnnotation("com/oracle/svm/core/annotate/AutomaticFeature"); - + MethodCreator afterReg = file.getMethodCreator("afterRegistration", void.class, "org.graalvm.nativeimage.Feature$AfterRegistrationAccess"); MethodCreator beforeAn = file.getMethodCreator("beforeAnalysis", "V", "org/graalvm/nativeimage/Feature$BeforeAnalysisAccess"); //TODO: at some point we are going to need to break this up, as if it get too big it will hit the method size limit + for(Consumer i : beforeAnalysisCallback) { + i.accept(afterReg); + } + afterReg.returnValue(null); + for (String i : resources) { beforeAn.invokeStaticMethod(ofMethod(ResourceHelper.class, "registerResources", void.class, String.class), beforeAn.load(i)); } diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java index c48dfdc97..c028d001e 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/ProcessorContext.java @@ -1,10 +1,12 @@ package org.jboss.shamrock.deployment; import java.io.IOException; +import java.util.function.Consumer; import java.util.function.Function; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; +import org.jboss.protean.gizmo.MethodCreator; import org.jboss.shamrock.deployment.codegen.BytecodeRecorder; import org.objectweb.asm.ClassVisitor; @@ -87,4 +89,6 @@ public interface ProcessorContext { void addResourceBundle(String bundle); + + void addBeforeAnalysis(Consumer callback); } diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/RuntimePriority.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/RuntimePriority.java index 356f59949..8b7775381 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/RuntimePriority.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/RuntimePriority.java @@ -17,4 +17,5 @@ public class RuntimePriority { public static final int UNDERTOW_START = 500; public static final int BEAN_VALIDATION_DEPLOYMENT = 600; public static final int TRANSACTIONS_DEPLOYMENT = 700; + public static final int DATASOURCE_DEPLOYMENT = 700; } diff --git a/core/deployment/src/main/java/org/jboss/shamrock/deployment/buildconfig/BuildConfig.java b/core/deployment/src/main/java/org/jboss/shamrock/deployment/buildconfig/BuildConfig.java index b798a9567..395a4812f 100644 --- a/core/deployment/src/main/java/org/jboss/shamrock/deployment/buildconfig/BuildConfig.java +++ b/core/deployment/src/main/java/org/jboss/shamrock/deployment/buildconfig/BuildConfig.java @@ -154,5 +154,9 @@ public class BuildConfig { public Object getUnderlying() { return node; } + + public boolean isNull() { + return node == null; + } } } diff --git a/core/runtime/src/main/java/org/jboss/shamrock/runtime/graal/MappedByteBufferReplacement.java b/core/runtime/src/main/java/org/jboss/shamrock/runtime/graal/MappedByteBufferReplacement.java new file mode 100644 index 000000000..4cfff40ad --- /dev/null +++ b/core/runtime/src/main/java/org/jboss/shamrock/runtime/graal/MappedByteBufferReplacement.java @@ -0,0 +1,16 @@ +package org.jboss.shamrock.runtime.graal; + +import java.io.FileDescriptor; +import java.nio.MappedByteBuffer; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(MappedByteBuffer.class) +final class MappedByteBufferReplacement { + + @Substitute + private void force0(FileDescriptor fd, long address, long length) { + + } +} diff --git a/examples/strict/pom.xml b/examples/strict/pom.xml index a35b16536..22ac5cb4a 100644 --- a/examples/strict/pom.xml +++ b/examples/strict/pom.xml @@ -69,6 +69,17 @@ shamrock-transactions-deployment provided + + + org.jboss.graalvm + graal-annotations + @@ -82,6 +93,11 @@ shamrock-graal test + + org.hsqldb + hsqldb + 2.4.0 + diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/datasource/DatasourceResource.java b/examples/strict/src/main/java/org/jboss/shamrock/example/datasource/DatasourceResource.java new file mode 100644 index 000000000..d4de0fceb --- /dev/null +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/datasource/DatasourceResource.java @@ -0,0 +1,38 @@ +package org.jboss.shamrock.example.datasource; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; + +import javax.inject.Inject; +import javax.sql.DataSource; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/datasource") +public class DatasourceResource { +// +// @Inject +// private DataSource dataSource; +// +// @GET +// public String simpleTest() throws Exception { +// try (Connection con = dataSource.getConnection()) { +// try (Statement statement = con.createStatement()) { +// statement.execute("create table a (b int)"); +// } +// try (Statement statement = con.createStatement()) { +// statement.execute("insert into a values (10)"); +// } +// try (Statement statement = con.createStatement()) { +// try (ResultSet rs = statement.executeQuery("select b from a")) { +// if(rs.next()) { +// return rs.getString(1); +// } +// return "FAILED"; +// } +// } +// } +// } + +} diff --git a/examples/strict/src/main/java/org/jboss/shamrock/example/datasource/HSQLDriverReplacement.java b/examples/strict/src/main/java/org/jboss/shamrock/example/datasource/HSQLDriverReplacement.java new file mode 100644 index 000000000..e0b67e366 --- /dev/null +++ b/examples/strict/src/main/java/org/jboss/shamrock/example/datasource/HSQLDriverReplacement.java @@ -0,0 +1,115 @@ +package org.jboss.shamrock.example.datasource; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +import org.hsqldb.DatabaseURL; +import org.hsqldb.error.ErrorCode; +import org.hsqldb.jdbc.JDBCConnection; +import org.hsqldb.jdbc.JDBCDriver; +import org.hsqldb.jdbc.JDBCUtil; +import org.hsqldb.persist.HsqlProperties; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(JDBCDriver.class) +final class HSQLDriverReplacement { + + + @Substitute + public static Connection getConnection(String url, + Properties info) throws SQLException { + + final HsqlProperties props = DatabaseURL.parseURL(url, true, false); + + if (props == null) { + + // supposed to be an HSQLDB driver url but has errors + throw JDBCUtil.invalidArgument(); + } else if (props.isEmpty()) { + + // is not an HSQLDB driver url + return null; + } + + long timeout = 0; + + if (info != null) { + timeout = HsqlProperties.getIntegerProperty(info, "loginTimeout", 0); + } + + props.addProperties(info); + + if (timeout == 0) { + timeout = DriverManager.getLoginTimeout(); + } + + // @todo: maybe impose some sort of sane restriction + // on network connections regardless of user + // specification? + if (timeout == 0) { + + // no timeout restriction + return new JDBCConnection(props); + } + + String connType = props.getProperty("connection_type"); + + if (DatabaseURL.isInProcessDatabaseType(connType)) { + return new JDBCConnection(props); + } + + // @todo: Better: ThreadPool? HsqlTimer with callback? + final JDBCConnection[] conn = new JDBCConnection[1]; + final SQLException[] ex = new SQLException[1]; + Thread t = new Thread() { + + public void run() { + + try { + conn[0] = new JDBCConnection(props); + } catch (SQLException se) { + ex[0] = se; + } + } + }; + + t.start(); + + try { + t.join(1000 * timeout); + } catch (InterruptedException ie) { + } + + try { + + // PRE: + // deprecated, but should be ok, since neither + // the HSQLClientConnection or the HTTPClientConnection + // constructor will ever hold monitors on objects in + // an inconsistent state, such that damaged objects + // become visible to other threads with the + // potential of arbitrary behavior. + //t.stop(); + } catch (Exception e) { + } finally { + try { + t.setContextClassLoader(null); + } catch (Throwable th) { + } + } + + if (ex[0] != null) { + throw ex[0]; + } + + if (conn[0] != null) { + return conn[0]; + } + + throw JDBCUtil.sqlException(ErrorCode.X_08501); + } +} diff --git a/examples/strict/src/main/resources/META-INF/shamrock-build.yaml b/examples/strict/src/main/resources/META-INF/shamrock-build.yaml new file mode 100644 index 000000000..f7d7fc758 --- /dev/null +++ b/examples/strict/src/main/resources/META-INF/shamrock-build.yaml @@ -0,0 +1,3 @@ +datasource: + url: "jdbc:hsqldb:http://localhost:7676/test" + driver: "org.hsqldb.jdbc.JDBCDriver" \ No newline at end of file diff --git a/examples/strict/src/test/java/org/jboss/shamrock/example/test/DatasourceITCase.java b/examples/strict/src/test/java/org/jboss/shamrock/example/test/DatasourceITCase.java new file mode 100644 index 000000000..43392c70b --- /dev/null +++ b/examples/strict/src/test/java/org/jboss/shamrock/example/test/DatasourceITCase.java @@ -0,0 +1,10 @@ +package org.jboss.shamrock.example.test; + +import org.jboss.shamrock.junit.GraalTest; +import org.junit.Ignore; +import org.junit.runner.RunWith; + +@RunWith(GraalTest.class) +@Ignore +public class DatasourceITCase extends DatasourceTestCase { +} \ No newline at end of file diff --git a/examples/strict/src/test/java/org/jboss/shamrock/example/test/DatasourceTestCase.java b/examples/strict/src/test/java/org/jboss/shamrock/example/test/DatasourceTestCase.java new file mode 100644 index 000000000..897d5708b --- /dev/null +++ b/examples/strict/src/test/java/org/jboss/shamrock/example/test/DatasourceTestCase.java @@ -0,0 +1,56 @@ +package org.jboss.shamrock.example.test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.hsqldb.server.WebServer; +import org.jboss.shamrock.junit.ShamrockTest; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(ShamrockTest.class) +@Ignore +public class DatasourceTestCase { + + static WebServer ws; + + @BeforeClass + public static void setup() throws Exception { + Path dir = Files.createTempDirectory("shamrock-test"); + ws = new WebServer(); + ws.setPort(7676); + ws.setAddress("localhost"); + ws.setDatabaseName(0, "test"); + ws.setDatabasePath(0, dir.toAbsolutePath().toString() + File.separator + "tempdb"); + ws.start(); + } + + @AfterClass + public static void tearDown() { + ws.stop(); + } + + @Test + public void testDataSource() throws Exception { + URL uri = new URL("http://localhost:8080/rest/datasource"); + URLConnection connection = uri.openConnection(); + InputStream in = connection.getInputStream(); + byte[] buf = new byte[100]; + int r; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + while ((r = in.read(buf)) > 0) { + out.write(buf, 0, r); + } + Assert.assertEquals("10", new String(out.toByteArray())); + } + +} diff --git a/pom.xml b/pom.xml index e701779b0..5b487744f 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,8 @@ 6.0.7.Final 5.8.0.Final 1.1.1.Final + 1.1 + 7.6.0.Final @@ -71,6 +73,7 @@ ext/arc bean-validation transactions + agroal @@ -121,6 +124,16 @@ + + org.jboss.shamrock + shamrock-agroal-deployment + ${project.version} + + + org.jboss.shamrock + shamrock-agroal-runtime + ${project.version} + org.jboss.shamrock shamrock-bean-validation-deployment @@ -297,6 +310,23 @@ jackson-databind ${jackson.version} + + io.agroal + agroal-api + ${agroal.version} + + + + io.agroal + agroal-narayana + ${agroal.version} + + + + io.agroal + agroal-pool + ${agroal.version} + io.undertow undertow-servlet @@ -494,6 +524,11 @@ jboss-jaxrs-api_2.1_spec ${jboss-jaxrs-api_2.1_spec.version} + + org.jboss + jboss-transaction-spi + ${jboss-transaction-spi.version} + org.jboss.weld.se weld-se-core diff --git a/transactions/deployment/src/main/java/org/jboss/shamrock/transactions/TransactionsProcessor.java b/transactions/deployment/src/main/java/org/jboss/shamrock/transactions/TransactionsProcessor.java index 7fcf776bf..a9a60aada 100644 --- a/transactions/deployment/src/main/java/org/jboss/shamrock/transactions/TransactionsProcessor.java +++ b/transactions/deployment/src/main/java/org/jboss/shamrock/transactions/TransactionsProcessor.java @@ -1,7 +1,14 @@ package org.jboss.shamrock.transactions; +import static org.jboss.protean.gizmo.MethodDescriptor.ofMethod; + +import java.util.Properties; +import java.util.function.Consumer; + import javax.inject.Inject; +import org.jboss.protean.gizmo.MethodCreator; +import org.jboss.protean.gizmo.ResultHandle; import org.jboss.shamrock.deployment.ArchiveContext; import org.jboss.shamrock.deployment.BeanDeployment; import org.jboss.shamrock.deployment.ProcessorContext; @@ -11,7 +18,9 @@ import org.jboss.shamrock.deployment.codegen.BytecodeRecorder; import org.jboss.shamrock.transactions.runtime.TransactionProducers; import org.jboss.shamrock.transactions.runtime.TransactionTemplate; -import com.arjuna.ats.arjuna.coordinator.CheckedActionFactory; +import com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean; +import com.arjuna.ats.arjuna.common.recoveryPropertyManager; +import com.arjuna.ats.arjuna.recovery.RecoveryManager; import com.arjuna.ats.internal.arjuna.coordinator.CheckedActionFactoryImple; import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple; import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple; @@ -34,12 +43,22 @@ class TransactionsProcessor implements ResourceProcessor { TransactionSynchronizationRegistryImple.class.getName()); //we want to force Arjuna to init at static init time - try (BytecodeRecorder bc = processorContext.addDeploymentTask(RuntimePriority.TRANSACTIONS_DEPLOYMENT)) { + try (BytecodeRecorder bc = processorContext.addStaticInitTask(RuntimePriority.TRANSACTIONS_DEPLOYMENT)) { TransactionTemplate tt = bc.getRecordingProxy(TransactionTemplate.class); - tt.setDefaultProperties(PropertiesFactory.getDefaultProperties()); - tt.forceInit(); + Properties defaultProperties = PropertiesFactory.getDefaultProperties(); + tt.setDefaultProperties(defaultProperties); } + processorContext.addBeforeAnalysis(new Consumer() { + @Override + public void accept(MethodCreator methodCreator) { + methodCreator.invokeStaticMethod(ofMethod(RecoveryManager.class, "delayRecoveryManagerThread", void.class)); + ResultHandle result = methodCreator.invokeStaticMethod(ofMethod(recoveryPropertyManager.class, "getRecoveryEnvironmentBean", RecoveryEnvironmentBean.class)); + methodCreator.invokeVirtualMethod(ofMethod(RecoveryEnvironmentBean.class, "setExpiryScanInterval", void.class, int.class), result, methodCreator.load(0)); + + } + }); + } @Override