diff --git a/.circleci/config.yml b/.circleci/config.yml
index 73f2141..004f789 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,29 +1,63 @@
-version: 2
+version: 2.1
+
+orbs:
+ # Using the gradle orb for better caching and setup
+ gradle: circleci/gradle@2.2.0
+
jobs:
build:
working_directory: ~/alfanse/embers
parallelism: 1
docker:
- - image: circleci/openjdk:11-jdk
+ - image: cimg/openjdk:17.0.10
steps:
- - checkout
- - run:
- working_directory: ~/alfanse/embers
- # for diagnostics, show me the version, includes JVM, and Gradle.
- command: ./gradlew --version
- # Build
- - run: make build
- - store_test_results:
- path: embers-services/build/test-results
- - store_test_results:
- path: embers-acceptance-tests/build/test-results
- - store_test_results:
- path: embers-spring/build/test-results
- - store_artifacts:
- name: store embers-acceptance-tests reports
- path: embers-acceptance-tests/build/yatspec
- destination: yatspec
- - store_artifacts:
- name: store jacoco reports
- path: build/reports/jacoco
- destination: jacoco
+ - checkout
+
+ # Restore Gradle dependencies cache
+ - restore_cache:
+ keys:
+ - v1-dependencies-{{ checksum "build.gradle" }}
+ - v1-dependencies-
+
+ # Show versions for diagnostics
+ - run:
+ name: Show versions
+ command: |
+ java -version
+ ./gradlew --version
+
+ # Build with Gradle
+ - run:
+ name: Build and test
+ command: ./gradlew build --no-daemon --stacktrace
+ environment:
+ # Configure Gradle to use more memory
+ GRADLE_OPTS: '-Dorg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8'
+
+ # Save Gradle dependencies cache
+ - save_cache:
+ paths:
+ - ~/.gradle
+ key: v1-dependencies-{{ checksum "build.gradle" }}
+
+ # Store test results for CircleCI test insights
+ - store_test_results:
+ path: embers-services/build/test-results
+ - store_test_results:
+ path: embers-acceptance-tests/build/test-results
+ - store_test_results:
+ path: embers-spring/build/test-results
+
+ # Store artifacts for later inspection
+ - store_artifacts:
+ name: Store acceptance test reports
+ path: embers-acceptance-tests/build/yatspec
+ destination: yatspec
+ - store_artifacts:
+ name: Store JaCoCo reports
+ path: build/reports/jacoco
+ destination: jacoco
+ - store_artifacts:
+ name: Store test results
+ path: build/reports/tests
+ destination: test-reports
diff --git a/build.gradle b/build.gradle
index c09f047..5500897 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@ buildscript {
}
}
dependencies {
- classpath "com.adarshr:gradle-test-logger-plugin:1.7.0"
+ classpath "com.adarshr:gradle-test-logger-plugin:4.0.0"
}
}
@@ -19,7 +19,8 @@ allprojects {
apply plugin: 'jacoco'
apply plugin: 'com.adarshr.test-logger'
- sourceCompatibility = 1.8
+ sourceCompatibility = 17
+ targetCompatibility = 17
repositories {
mavenCentral()
@@ -35,81 +36,36 @@ allprojects {
}
task codeCoverageReport(type: JacocoReport) {
-//swiped from http://csiebler.github.io/blog/2014/02/09/multi-project-code-coverage-using-gradle-and-jacoco/
+ //swiped from http://csiebler.github.io/blog/2014/02/09/multi-project-code-coverage-using-gradle-and-jacoco/
- // Gather execution data from all subprojects
- // (change this if you e.g. want to calculate unit test/integration test coverage separately)
- executionData fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec")
+ // Enable the report to be generated
+ dependsOn subprojects*.test
- // Add all relevant sourcesets from the subprojects
- subprojects.each {
- sourceSets it.sourceSets.main
- }
+ // Gather execution data from all subprojects
+ executionData fileTree(project.rootDir).include("**/build/jacoco/*.exec")
+
+ // Configure source and class directories from all subprojects
+ sourceDirectories.setFrom(files(subprojects.sourceSets.main.allSource.srcDirs))
+ classDirectories.setFrom(files(subprojects.sourceSets.main.output))
+
+ // Enable XML report for CI tools if needed
reports {
- html.enabled true
- html.destination file("${buildDir}/reports/jacoco")
- println "code coverage reports: " + html.destination
- }
-}
+ html.required = true
+ xml.required = false
+ csv.required = false
+
+ html.outputLocation = layout.buildDirectory.dir('reports/jacoco')
+ println "Code coverage reports will be generated at: ${html.outputLocation.get()}"
+// xml.outputLocation = layout.buildDirectory.file('reports/jacoco/jacocoTestReport.xml')
-// always run the tests before generating the report
-codeCoverageReport.dependsOn {
- subprojects*.test
+ }
}
-check.dependsOn jacocoTestReport
-
-subprojects {
-
- def jaxb = '2.3.0'
- def junitJupiter = '5.7.1'
-
- extensions.add("libs", [
- core: [
- //the only libraries the production jar depends on
- 'javax.ws.rs:javax.ws.rs-api:2.1',
- 'javax.inject:javax.inject:1',
- 'javax.activation:activation:1.1.1',
- "javax.xml.bind:jaxb-api:$jaxb",
- "com.sun.xml.bind:jaxb-core:$jaxb",
- "com.sun.xml.bind:jaxb-impl:$jaxb",
- 'org.jdbi:jdbi:2.78'
- ],
- unit_tests: [
- "org.junit.jupiter:junit-jupiter:$junitJupiter",
- 'org.easytesting:fest-assert-core:2.0M10',
- 'org.mockito:mockito-core:3.8.0',
- 'org.mockito:mockito-junit-jupiter:3.8.0'
- ],
- acceptance_tests: [
-// the bdd framework, makes pretty html reports from Junit tests
- 'com.github.nickmcdowall:yatspec:2021.1.1'
- ],
- jersey: [
-// implements javax.ws.rs
- 'org.glassfish.jersey.core:jersey-server:2.2',
- 'org.glassfish.jersey.containers:jersey-container-servlet:2.2'
- ],
- jaxson: [
-// handles json serialisation
- 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.5.2'
- ],
- gson: [
- //serialise java to json
- 'com.google.code.gson:gson:2.8.2'
- ],
- json_assert: [
- 'org.skyscreamer:jsonassert:1.2.3'
- ],
- jetty: [
-// beloved server
- 'org.eclipse.jetty:jetty-server:9.3.0.M1',
- 'org.eclipse.jetty:jetty-servlet:9.3.0.M1'
- ],
- database: [
-// beloved in-memory DB for testing.
- 'org.hsqldb:hsqldb:2.3.2'
- ]
- ])
+// Configure the test task to generate execution data
+tasks.withType(Test).configureEach {
+ finalizedBy codeCoverageReport
+ doLast {
+ logger.lifecycle("Test execution completed. Generating JaCoCo report...")
+ }
}
diff --git a/embers-acceptance-tests/build.gradle b/embers-acceptance-tests/build.gradle
index 6af6fb5..3b21313 100644
--- a/embers-acceptance-tests/build.gradle
+++ b/embers-acceptance-tests/build.gradle
@@ -12,13 +12,19 @@ test {
test.mustRunAfter ":embers-services:build"
dependencies {
- compile project(":embers-services")
- compile libs.acceptance_tests
- compile libs.database
+ implementation project(":embers-services")
+ implementation libs.bundles.core
+ implementation libs.gson
+ implementation libs.bundles.acceptance.tests
+ implementation libs.bundles.database
- testCompile libs.unit_tests
- testCompile libs.acceptance_tests
- testCompile libs.jersey
- testCompile libs.jaxson
- testCompile libs.jetty
+ testImplementation libs.bundles.unit.tests
+ testImplementation libs.bundles.acceptance.tests
+ testImplementation libs.bundles.jakarta.web.server
+ testImplementation libs.bundles.jersey
+ testImplementation libs.bundles.jetty
+ testImplementation libs.jaxson
+
+ // Platform BOMs for dependency management
+ testImplementation platform('org.eclipse.jetty:jetty-bom:12.0.7')
}
diff --git a/embers-acceptance-tests/src/main/java/adf/embers/tools/EmbersDatabase.java b/embers-acceptance-tests/src/main/java/adf/embers/tools/EmbersDatabase.java
index 0efa14c..d1da7b3 100644
--- a/embers-acceptance-tests/src/main/java/adf/embers/tools/EmbersDatabase.java
+++ b/embers-acceptance-tests/src/main/java/adf/embers/tools/EmbersDatabase.java
@@ -29,6 +29,7 @@ public EmbersDatabase(String jdbcUrl) {
this.jdbcUrl = jdbcUrl;
}
+
public void startInMemoryDatabase() throws Exception {
System.out.println("Starting the Embers database");
DriverManager.registerDriver(jdbcDriver.driverInstance);
diff --git a/embers-acceptance-tests/src/test/java/adf/embers/tools/EmbersJettyServer.java b/embers-acceptance-tests/src/test/java/adf/embers/tools/EmbersJettyServer.java
index 23347a8..297a49d 100644
--- a/embers-acceptance-tests/src/test/java/adf/embers/tools/EmbersJettyServer.java
+++ b/embers-acceptance-tests/src/test/java/adf/embers/tools/EmbersJettyServer.java
@@ -3,15 +3,22 @@
import adf.embers.configuration.EmbersHandlerConfiguration;
import adf.embers.configuration.EmbersProcessorConfiguration;
import adf.embers.configuration.EmbersRepositoryConfiguration;
+
+import jakarta.servlet.Servlet;
+import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.glassfish.hk2.api.ServiceLocator;
+import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
+import org.glassfish.jersey.inject.hk2.Hk2InjectionManagerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
-import javax.servlet.Servlet;
import javax.sql.DataSource;
+import java.util.logging.Level;
+import java.util.logging.Logger;
public class EmbersJettyServer {
@@ -22,24 +29,71 @@ public EmbersJettyServer(int port) {
}
public void startHttpServer(DataSource dataSource) throws Exception {
- System.out.println("Starting the Embers Server");
-
- EmbersRepositoryConfiguration embersRepositoryConfiguration = new EmbersRepositoryConfiguration(dataSource);
- EmbersProcessorConfiguration embersProcessorConfiguration = new EmbersProcessorConfiguration(embersRepositoryConfiguration);
- EmbersHandlerConfiguration embersConfiguration = new EmbersHandlerConfiguration(embersProcessorConfiguration);
-
- Servlet jerseyServlet = createJerseyServletWithEmbersHandlers(embersConfiguration);
-
- server.setHandler(createEmbersHandler(jerseyServlet));
- server.start();
+ System.out.println("Starting the Embers Server on port: " + server.getURI());
+
+ // Disable Weld logging to avoid unnecessary warnings
+ Logger.getLogger("org.jboss.weld").setLevel(Level.SEVERE);
+
+ // Set HK2 as the injection manager
+ System.setProperty("jersey.config.server.disableAutoDiscovery", "true");
+ System.setProperty("jersey.config.server.disableMetainfServicesLookup", "true");
+ System.setProperty("jersey.config.server.disableMoxyJson", "true");
+
+ try {
+ // Create HK2 service locator
+ ServiceLocator locator = ServiceLocatorUtilities.createAndPopulateServiceLocator("EmbersServiceLocator");
+
+ // Manually register services
+ EmbersRepositoryConfiguration embersRepositoryConfiguration = new EmbersRepositoryConfiguration(dataSource);
+ ServiceLocatorUtilities.addOneConstant(locator, embersRepositoryConfiguration);
+
+ EmbersProcessorConfiguration embersProcessorConfiguration = new EmbersProcessorConfiguration(embersRepositoryConfiguration);
+ ServiceLocatorUtilities.addOneConstant(locator, embersProcessorConfiguration);
+
+ EmbersHandlerConfiguration embersConfiguration = new EmbersHandlerConfiguration(embersProcessorConfiguration);
+ ServiceLocatorUtilities.addOneConstant(locator, embersConfiguration);
+
+ // Create Jersey resource config with HK2
+ ResourceConfig resourceConfig = new ResourceConfig();
+ resourceConfig.property("jersey.config.server.provider.classnames",
+ "org.glassfish.jersey.media.multipart.MultiPartFeature");
+
+ // Register resources
+ resourceConfig.register(embersConfiguration.getQueryHandler());
+ resourceConfig.register(embersConfiguration.getAdminQueryHandler());
+ resourceConfig.register(embersConfiguration.getQueryResultCacheHandler());
+
+ // Create servlet container with HK2
+ ServletContainer servletContainer = new ServletContainer(resourceConfig);
+
+ // Set up the handler
+ ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ handler.setContextPath("/" + EmbersServer.CONTEXT_PATH_ROOT);
+ ServletHolder servletHolder = new ServletHolder("jersey-servlet", servletContainer);
+ handler.addServlet(servletHolder, "/*");
+
+ System.out.println("Created handler: " + handler);
+ server.setHandler(handler);
+
+ System.out.println("Starting Jetty server...");
+ server.start();
+ System.out.println("Started the Embers Server at: http://localhost:" + server.getURI().getPort() + "/" + EmbersServer.CONTEXT_PATH_ROOT);
+ } catch (Exception e) {
+ System.err.println("Error starting Embers server: " + e.getMessage());
+ e.printStackTrace();
+ throw e;
+ }
}
public void stopHttpServer() {
System.out.println("Stopping the Embers server");
try {
server.stop();
+ System.out.println("Stopped the Embers server");
} catch (Exception e) {
- System.err.println("Exception stopping jetty server: "+e.getMessage());
+ System.err.println("Exception stopping jetty server: " + e.getMessage() + "/n");
+ } finally {
+ LifeCycle.stop(server);
}
}
@@ -53,10 +107,12 @@ private Servlet createJerseyServletWithEmbersHandlers(EmbersHandlerConfiguration
private Handler createEmbersHandler(Servlet embersServlet) {
ServletContextHandler handler = new ServletContextHandler();
- handler.addServlet(new ServletHolder(embersServlet), "/");
- //setting context path separately works
handler.setContextPath("/" + EmbersServer.CONTEXT_PATH_ROOT);
-
+
+ // Add the servlet
+ ServletHolder servletHolder = new ServletHolder("embers", embersServlet);
+ handler.addServlet(servletHolder, "/*");
+
return handler;
}
}
diff --git a/embers-acceptance-tests/src/test/java/adf/embers/tools/EmbersServer.java b/embers-acceptance-tests/src/test/java/adf/embers/tools/EmbersServer.java
index f8dbdc6..2082330 100644
--- a/embers-acceptance-tests/src/test/java/adf/embers/tools/EmbersServer.java
+++ b/embers-acceptance-tests/src/test/java/adf/embers/tools/EmbersServer.java
@@ -18,8 +18,24 @@ public class EmbersServer {
private EmbersJettyServer embersJettyServer;
public void before() throws Throwable {
- startDatabase();
- startJettyServer(embersDatabase.getDataSource());
+ System.out.println("Starting Embers server...");
+ try {
+ startDatabase();
+ System.out.println("Database started successfully");
+
+ System.out.println("Starting Jetty server with data source: " + embersDatabase.getDataSource());
+ startJettyServer(embersDatabase.getDataSource());
+
+ // Add a small delay to ensure server is fully started
+ Thread.sleep(1000);
+ System.out.println("Embers server started successfully on port " + PORT);
+ System.out.println("Query path: " + embersQueryPath());
+ System.out.println("Admin path: " + embersAdminPath());
+ } catch (Throwable t) {
+ System.err.println("Error starting Embers server: " + t.getMessage());
+ t.printStackTrace();
+ throw t;
+ }
}
@SuppressWarnings("unused") //Keeping it as a might be needed to fix re-using server bugs
diff --git a/embers-acceptance-tests/src/test/resources/META-INF/beans.xml b/embers-acceptance-tests/src/test/resources/META-INF/beans.xml
new file mode 100644
index 0000000..36ce1f4
--- /dev/null
+++ b/embers-acceptance-tests/src/test/resources/META-INF/beans.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/embers-request-collection/POST Admin - create query.bru b/embers-request-collection/POST Admin - create query.bru
new file mode 100644
index 0000000..efc41d6
--- /dev/null
+++ b/embers-request-collection/POST Admin - create query.bru
@@ -0,0 +1,24 @@
+meta {
+ name: POST Admin - create query
+ type: http
+ seq: 1
+}
+
+post {
+ url: http://localhost:8001/embers/admin
+ body: json
+ auth: inherit
+}
+
+headers {
+ Accept-Charset: UTF-8
+ Content-Type: application/json
+}
+
+body:json {
+ {
+ "name":"new Query",
+ "sql":"SELECT CURRENT_DATE AS today, CURRENT_TIME AS now FROM (VALUES(0))",
+ "description":"this query returns the date,time"
+ }
+}
diff --git a/embers-request-collection/bruno.json b/embers-request-collection/bruno.json
new file mode 100644
index 0000000..2f085b7
--- /dev/null
+++ b/embers-request-collection/bruno.json
@@ -0,0 +1,9 @@
+{
+ "version": "1",
+ "name": "embers-request-collection",
+ "type": "collection",
+ "ignore": [
+ "node_modules",
+ ".git"
+ ]
+}
\ No newline at end of file
diff --git a/embers-services/build.gradle b/embers-services/build.gradle
index 59d4c4a..29a2cbb 100644
--- a/embers-services/build.gradle
+++ b/embers-services/build.gradle
@@ -1,14 +1,15 @@
description = "Build jar for the embers sql reporting services."
dependencies {
- compile libs.core
- compile libs.gson
- testCompile libs.unit_tests
- testCompile libs.jersey
- testCompile libs.json_assert
+ implementation libs.bundles.core
+ implementation libs.gson
+ testImplementation libs.bundles.unit.tests
+ testImplementation libs.bundles.jersey
+ testImplementation libs.json.assert
}
test {
+ jvmArgs '--add-opens', 'java.base/java.util=ALL-UNNAMED'
useJUnitPlatform()
}
diff --git a/embers-services/src/main/java/adf/embers/admin/AdminQueryHandler.java b/embers-services/src/main/java/adf/embers/admin/AdminQueryHandler.java
index 25144d4..b8a1e96 100644
--- a/embers-services/src/main/java/adf/embers/admin/AdminQueryHandler.java
+++ b/embers-services/src/main/java/adf/embers/admin/AdminQueryHandler.java
@@ -3,10 +3,10 @@
import adf.embers.query.persistence.Query;
import adf.embers.query.persistence.QueryDao;
-import javax.inject.Inject;
-import javax.ws.rs.*;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import static adf.embers.statics.UrlTools.decodeString;
diff --git a/embers-services/src/main/java/adf/embers/cache/Caching.java b/embers-services/src/main/java/adf/embers/cache/Caching.java
index acc843f..19d2221 100644
--- a/embers-services/src/main/java/adf/embers/cache/Caching.java
+++ b/embers-services/src/main/java/adf/embers/cache/Caching.java
@@ -1,6 +1,6 @@
package adf.embers.cache;
-import javax.inject.Qualifier;
+import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
diff --git a/embers-services/src/main/java/adf/embers/cache/QueryResultCacheHandler.java b/embers-services/src/main/java/adf/embers/cache/QueryResultCacheHandler.java
index 755b43b..ad07b24 100644
--- a/embers-services/src/main/java/adf/embers/cache/QueryResultCacheHandler.java
+++ b/embers-services/src/main/java/adf/embers/cache/QueryResultCacheHandler.java
@@ -3,12 +3,12 @@
import adf.embers.query.QueryProcessor;
import adf.embers.query.QueryResult;
-import javax.inject.Inject;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import static adf.embers.statics.UrlTools.decodeString;
diff --git a/embers-services/src/main/java/adf/embers/cache/QueryResultCacheProcessor.java b/embers-services/src/main/java/adf/embers/cache/QueryResultCacheProcessor.java
index 3c90067..8e8d1a8 100644
--- a/embers-services/src/main/java/adf/embers/cache/QueryResultCacheProcessor.java
+++ b/embers-services/src/main/java/adf/embers/cache/QueryResultCacheProcessor.java
@@ -11,6 +11,7 @@
import adf.embers.query.persistence.Query;
import adf.embers.query.persistence.QueryDao;
+import jakarta.inject.Inject;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -21,6 +22,7 @@ public class QueryResultCacheProcessor implements QueryProcessor {
private QueryDao queryDao;
private QueryExecutor queryExecutor;
+ @Inject
public QueryResultCacheProcessor(QueryResultCacheDao queryResultCacheDao, QueryDao queryDao, QueryExecutor queryExecutor) {
this.queryResultCacheDao = queryResultCacheDao;
this.queryDao = queryDao;
diff --git a/embers-services/src/main/java/adf/embers/configuration/EmbersRepositoryConfiguration.java b/embers-services/src/main/java/adf/embers/configuration/EmbersRepositoryConfiguration.java
index 66dfe4d..282a742 100644
--- a/embers-services/src/main/java/adf/embers/configuration/EmbersRepositoryConfiguration.java
+++ b/embers-services/src/main/java/adf/embers/configuration/EmbersRepositoryConfiguration.java
@@ -12,7 +12,7 @@
public class EmbersRepositoryConfiguration {
private final DBI dbi;
- private DataSource dataSource;
+ private final DataSource dataSource;
public EmbersRepositoryConfiguration(DataSource dataSource) {
this.dataSource = dataSource;
diff --git a/embers-services/src/main/java/adf/embers/query/QueryHandler.java b/embers-services/src/main/java/adf/embers/query/QueryHandler.java
index 54808ef..5867a78 100644
--- a/embers-services/src/main/java/adf/embers/query/QueryHandler.java
+++ b/embers-services/src/main/java/adf/embers/query/QueryHandler.java
@@ -1,11 +1,11 @@
package adf.embers.query;
-import javax.inject.Inject;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
import java.net.HttpURLConnection;
import static adf.embers.statics.UrlTools.decodeString;
diff --git a/embers-services/src/test/java/adf/embers/admin/AdminQueryHandlerTest.java b/embers-services/src/test/java/adf/embers/admin/AdminQueryHandlerTest.java
index 0cbdbec..a1b8c6f 100644
--- a/embers-services/src/test/java/adf/embers/admin/AdminQueryHandlerTest.java
+++ b/embers-services/src/test/java/adf/embers/admin/AdminQueryHandlerTest.java
@@ -2,11 +2,10 @@
import adf.embers.query.persistence.Query;
import adf.embers.query.persistence.QueryDao;
+import jakarta.ws.rs.core.Response;
import org.fest.assertions.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
-
-import javax.ws.rs.core.Response;
import java.net.HttpURLConnection;
import static org.fest.assertions.api.Assertions.assertThat;
diff --git a/embers-services/src/test/java/adf/embers/cache/QueryResultCacheHandlerTest.java b/embers-services/src/test/java/adf/embers/cache/QueryResultCacheHandlerTest.java
index 56b2d1b..cdf0bc9 100644
--- a/embers-services/src/test/java/adf/embers/cache/QueryResultCacheHandlerTest.java
+++ b/embers-services/src/test/java/adf/embers/cache/QueryResultCacheHandlerTest.java
@@ -3,14 +3,13 @@
import adf.embers.query.QueryProcessor;
import adf.embers.query.QueryRequest;
import adf.embers.query.QueryResult;
+import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import javax.ws.rs.core.Response;
-
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
diff --git a/embers-spring/build.gradle b/embers-spring/build.gradle
index 4f8e4ea..91edc96 100644
--- a/embers-spring/build.gradle
+++ b/embers-spring/build.gradle
@@ -1,22 +1,30 @@
plugins {
- id 'org.springframework.boot' version '2.1.8.RELEASE'
- id 'io.spring.dependency-management' version '1.0.8.RELEASE'
+ id 'org.springframework.boot' version '3.2.0'
+ id 'io.spring.dependency-management' version '1.1.4'
+ id 'java'
}
description = """Example project showing how to mix embers into a spring-boot application."""
dependencies {
-
implementation project(":embers-services")
implementation 'org.springframework.boot:spring-boot-starter-jersey'
- implementation "org.springframework.boot:spring-boot-starter-web"
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'org.springframework.boot:spring-boot-starter-jdbc'
+
+ // Jackson for JSON processing
+ implementation 'com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider'
+
+ // Jakarta EE API dependencies - using versions from version catalog
+ implementation libs.jakarta.ws.rs
+ implementation libs.jakarta.annotation.api
+ // Testing
testImplementation project(":embers-acceptance-tests")
- testImplementation libs.database
- testImplementation libs.unit_tests
- testImplementation 'io.rest-assured:rest-assured:3.3.0'
- testImplementation "org.springframework.boot:spring-boot-starter-test"
-
+ testImplementation libs.bundles.database
+ testImplementation libs.bundles.unit.tests
+ testImplementation 'io.rest-assured:rest-assured:5.3.0' // For Jakarta EE 9+ compatibility
+ testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
diff --git a/embers-spring/src/main/java/adf/embers/examples/spring/EmbersSpringConfiguration.java b/embers-spring/src/main/java/adf/embers/examples/spring/EmbersSpringConfiguration.java
index ba47104..4bf28cf 100644
--- a/embers-spring/src/main/java/adf/embers/examples/spring/EmbersSpringConfiguration.java
+++ b/embers-spring/src/main/java/adf/embers/examples/spring/EmbersSpringConfiguration.java
@@ -4,9 +4,8 @@
import adf.embers.configuration.EmbersProcessorConfiguration;
import adf.embers.configuration.EmbersRepositoryConfiguration;
import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.servlet.ServletContainer;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -15,39 +14,59 @@
@Configuration
public class EmbersSpringConfiguration {
-
public static final String EMBERS = "embers";
- @Autowired
- DataSource dataSource;
+ @Bean
+ public EmbersRepositoryConfiguration embersRepositoryConfiguration(DataSource dataSource) {
+ return new EmbersRepositoryConfiguration(dataSource);
+ }
+
+ @Bean
+ public EmbersProcessorConfiguration embersProcessorConfiguration(EmbersRepositoryConfiguration embersRepositoryConfiguration) {
+ return new EmbersProcessorConfiguration(embersRepositoryConfiguration);
+ }
@Bean
- EmbersHandlerConfiguration embersHandlerConfiguration() {
- EmbersRepositoryConfiguration embersRepositoryConfiguration = new EmbersRepositoryConfiguration(dataSource);
- EmbersProcessorConfiguration embersProcessorConfiguration = new EmbersProcessorConfiguration(embersRepositoryConfiguration);
- return new EmbersHandlerConfiguration(embersProcessorConfiguration);
+ public EmbersHandlerConfiguration embersHandlerConfiguration(EmbersProcessorConfiguration embersProcessorConfiguration) {
+ return new EmbersHandlerConfiguration(
+ embersProcessorConfiguration.queryDao(),
+ embersProcessorConfiguration.queryProcessor(),
+ embersProcessorConfiguration.cachedQueryProcessor()
+ );
}
@Bean
- @Qualifier(EMBERS)
- public ResourceConfig resourceConfig(
- @Autowired EmbersHandlerConfiguration handlerConfiguration
- ) {
+ @org.springframework.beans.factory.annotation.Qualifier(EMBERS)
+ public ResourceConfig resourceConfig(EmbersHandlerConfiguration handlerConfiguration) {
ResourceConfig resourceConfig = new ResourceConfig();
+
+ // Register the handlers
resourceConfig.register(handlerConfiguration.getQueryHandler());
resourceConfig.register(handlerConfiguration.getAdminQueryHandler());
resourceConfig.register(handlerConfiguration.getQueryResultCacheHandler());
+
+ // Configure Jersey
+ resourceConfig.packages("adf.embers");
+ resourceConfig.property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);
+ resourceConfig.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
+
+ // Enable Jackson JSON processing
+ resourceConfig.register(org.glassfish.jersey.jackson.JacksonFeature.class);
+ resourceConfig.register(com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider.class);
+
return resourceConfig;
}
- /**
- * Registers the ResourceConfig to path /embers/*
- */
@Bean
- public ServletRegistrationBean servletRegistrationBean(
- @Autowired @Qualifier(EMBERS) ResourceConfig resourceConfig
- ) {
+ public ServletRegistrationBean servletRegistrationBean(
+ @org.springframework.beans.factory.annotation.Qualifier(EMBERS) ResourceConfig resourceConfig) {
+
+ // Create and configure the servlet
ServletContainer servletContainer = new ServletContainer(resourceConfig);
- return new ServletRegistrationBean(servletContainer, "/embers/*");
+ ServletRegistrationBean registration = new ServletRegistrationBean<>(
+ servletContainer, "/" + EMBERS + "/*");
+ registration.setName("jersey-servlet");
+ registration.setLoadOnStartup(1);
+ return registration;
}
}
diff --git a/embers-spring/src/test/java/adf/embers/examples/spring/ApplicationTest.java b/embers-spring/src/test/java/adf/embers/examples/spring/ApplicationTest.java
index 8991dc1..c09cab3 100644
--- a/embers-spring/src/test/java/adf/embers/examples/spring/ApplicationTest.java
+++ b/embers-spring/src/test/java/adf/embers/examples/spring/ApplicationTest.java
@@ -4,36 +4,47 @@
import adf.embers.cache.QueryResultCacheHandler;
import adf.embers.query.QueryHandler;
import io.restassured.RestAssured;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.test.context.ActiveProfiles;
-import java.io.IOException;
import java.net.URI;
+import java.util.logging.Logger;
import static org.assertj.core.api.Assertions.assertThat;
-@RunWith(SpringRunner.class)
@SpringBootTest(
- classes = {SpringDataSourceConfiguration.class, Application.class},
- webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
- properties = {"server.port=8080"})
+ classes = {Application.class, SpringDataSourceConfiguration.class},
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ActiveProfiles("test")
public class ApplicationTest {
+ private static final Logger log = Logger.getLogger(ApplicationTest.class.getName());
+
+ @LocalServerPort
+ private int port;
+
+ @BeforeEach
+ public void setUp() {
+ RestAssured.port = port;
+ log.info("Using port: " + port);
+ }
@Test
- public void query_accessible() throws IOException, InterruptedException {
+ public void query_accessible() {
String responseBody =
- RestAssured.when()
- .get(embersUri(QueryHandler.PATH + "/unknownQuery")).
- then().statusCode(404)
- .extract().response().body().asString();
+ RestAssured.when()
+ .get(embersUri(QueryHandler.PATH + "/unknownQuery"))
+ .then()
+ .statusCode(404)
+ .extract().response().body().asString();
assertThat(responseBody).isEqualTo("Query not found: unknownQuery");
}
@Test
- public void admin_accessible() throws IOException, InterruptedException {
+ public void admin_accessible() {
String responseBody = RestAssured.when()
.delete(embersUri(AdminQueryHandler.PATH + "/unknownQuery"))
.then().statusCode(200)
@@ -43,7 +54,7 @@ public void admin_accessible() throws IOException, InterruptedException {
}
@Test
- public void cached_accessible() throws IOException, InterruptedException {
+ public void cached_accessible() {
String responseBody = RestAssured.when()
.get(embersUri(QueryResultCacheHandler.PATH + "/unknownQuery"))
.then().statusCode(404)
@@ -52,8 +63,11 @@ public void cached_accessible() throws IOException, InterruptedException {
assertThat(responseBody).isEqualTo("Query not found: unknownQuery");
}
- private URI embersUri(String endpoint) {
- return URI.create("http://localhost:8080/embers" + endpoint);
+ private URI embersUri(String path) {
+ String uri = String.format("http://localhost:%d/embers%s",
+ port, path.startsWith("/") ? path : "/" + path);
+ log.info("Calling URI: " + uri);
+ return URI.create(uri);
}
-}
\ No newline at end of file
+}
diff --git a/embers-spring/src/test/java/adf/embers/examples/spring/SpringDataSourceConfiguration.java b/embers-spring/src/test/java/adf/embers/examples/spring/SpringDataSourceConfiguration.java
index 6a89d13..c2e864e 100644
--- a/embers-spring/src/test/java/adf/embers/examples/spring/SpringDataSourceConfiguration.java
+++ b/embers-spring/src/test/java/adf/embers/examples/spring/SpringDataSourceConfiguration.java
@@ -1,26 +1,59 @@
package adf.embers.examples.spring;
import adf.embers.tools.EmbersDatabase;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
import javax.sql.DataSource;
+/**
+ * Configuration for setting up the test data source using H2 in-memory database.
+ * This configuration is only active when the 'test' profile is active.
+ */
@Configuration
-public class SpringDataSourceConfiguration {
+@Profile("test")
+public class SpringDataSourceConfiguration implements DisposableBean {
+
+ private static final Logger log = LoggerFactory.getLogger(SpringDataSourceConfiguration.class);
private final EmbersDatabase embersDatabase;
- public SpringDataSourceConfiguration() throws Exception {
- embersDatabase = new EmbersDatabase(EmbersDatabase.JDBC_URL);
- embersDatabase.startInMemoryDatabase();
- embersDatabase.createTableQueries();
- embersDatabase.createTableQueriesStatistics();
- embersDatabase.createTableQueryResultCache();
+ public SpringDataSourceConfiguration() {
+ this.embersDatabase = new EmbersDatabase(EmbersDatabase.JDBC_URL);
+ }
+
+ /**
+ * Initialize the in-memory database and create necessary tables.
+ */
+ @PostConstruct
+ public void initialize() throws Exception {
+ this.embersDatabase.startInMemoryDatabase();
+ this.embersDatabase.createTableQueries();
+ this.embersDatabase.createTableQueriesStatistics();
+ this.embersDatabase.createTableQueryResultCache();
}
+ /**
+ * @return Configured DataSource
+ */
@Bean
public DataSource dataSource() {
- return embersDatabase.getDataSource();
+ return this.embersDatabase.getDataSource();
+ }
+
+ @Override
+ @PreDestroy
+ public void destroy() {
+ try {
+ this.embersDatabase.shutdownInMemoryDatabase();
+ } catch (Exception e) {
+ log.warn("Error while shutting down in-memory H2 database", e);
+ }
}
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..690f581
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,95 @@
+[versions]
+jaxb = "2.3.0"
+mockito = "3.8.0"
+
+[libraries]
+
+# Core Libraries
+gson = { module = "com.google.code.gson:gson", version = "2.8.2" }
+
+# Jakarta EE
+json_assert = { module = "org.skyscreamer:jsonassert", version = "1.2.3" }
+
+jakarta_annotation_api = { module = "jakarta.annotation:jakarta.annotation-api", version = "3.0.0" }
+jakarta_ws_rs = { module = "jakarta.ws.rs:jakarta.ws.rs-api", version = "3.1.0" }
+jakarta_servlet = { module = "jakarta.servlet:jakarta.servlet-api", version = "6.0.0" }
+jakarta_inject = { module = "jakarta.inject:jakarta.inject-api", version = "2.0.1" }
+javax_xml_bind = { module = "javax.xml.bind:jaxb-api", version.ref = "jaxb" }
+jaxb_core = { module = "com.sun.xml.bind:jaxb-core", version.ref = "jaxb" }
+jaxb_impl = { module = "com.sun.xml.bind:jaxb-impl", version.ref = "jaxb" }
+jdbi = { module = "org.jdbi:jdbi", version = "2.78" }
+
+jaxson = { module = "com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider", version = "2.16.1" }
+
+jersey_server = { module = "org.glassfish.jersey.core:jersey-server", version = "3.1.3" }
+jersey_container = { module = "org.glassfish.jersey.containers:jersey-container-servlet", version = "3.1.3" }
+jersey_media_json = { module = "org.glassfish.jersey.media:jersey-media-json-jackson", version = "3.1.3" }
+
+jetty_server = { module = "org.eclipse.jetty.ee10:jetty-ee10-servlet", version = "12.0.7" }
+jetty_http = { module = "org.eclipse.jetty:jetty-http", version = "12.0.7" }
+jetty_io = { module = "org.eclipse.jetty:jetty-io", version = "12.0.7" }
+jetty_util = { module = "org.eclipse.jetty:jetty-util", version = "12.0.7" }
+
+hsqldb = { module = "org.hsqldb:hsqldb", version = "2.7.4" }
+
+junit_jupiter = { module = "org.junit.jupiter:junit-jupiter", version = "5.7.1" }
+fest_assert = { module = "org.easytesting:fest-assert-core", version = "2.0M10" }
+mockito_core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
+mockito_junit_jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }
+
+yatspec = { module = "com.github.nickmcdowall:yatspec", version = "2021.1.1" }
+
+# Jakarta EE 10 dependencies
+jakarta_websocket_api = { module = "jakarta.websocket:jakarta.websocket-api", version = "2.1.1" }
+jakarta_websocket_client_api = { module = "jakarta.websocket:jakarta.websocket-client-api", version = "2.1.1" }
+# Using an older version of JSTL that's more stable with our setup
+jstl = { module = "org.glassfish.web:jakarta.servlet.jsp.jstl", version = "2.0.0" }
+
+# Jersey and HK2 integration
+jersey_container_servlet = { module = "org.glassfish.jersey.containers:jersey-container-servlet", version = "3.1.3" }
+jersey_hk2 = { module = "org.glassfish.jersey.inject:jersey-hk2", version = "3.1.3" }
+
+# HK2 (Hundred-Kilobyte Kernel) dependencies
+hk2_api = { module = "org.glassfish.hk2:hk2-api", version = "3.0.5" }
+hk2_locator = { module = "org.glassfish.hk2:hk2-locator", version = "3.0.5" }
+hk2_utils = { module = "org.glassfish.hk2:hk2-utils", version = "3.0.5" }
+hk2_runlevel = { module = "org.glassfish.hk2:hk2-runlevel", version = "3.0.5" }
+
+[bundles]
+core = [
+ # the only libraries the production jar depends on
+ "jakarta_ws_rs", "jakarta_servlet", "jakarta_inject", "javax_xml_bind", "jaxb_core", "jaxb_impl", "jdbi"
+]
+
+unit_tests = [
+ "junit_jupiter", "fest_assert", "mockito_core", "mockito_junit_jupiter"
+]
+
+acceptance_tests = [
+ # The bdd framework, makes pretty html reports from Junit tests
+ "yatspec"
+]
+
+jakarta_web_server = [
+ "jakarta_websocket_api", "jakarta_websocket_client_api", "jstl"
+]
+
+jersey = [
+ # implements jakarta.ws.rs with HK2
+ "jersey_server", "jersey_container_servlet", "jersey_media_json", "jersey_hk2",
+
+ # HK2 (Hundred-Kilobyte Kernel) dependencies
+ "hk2_api", "hk2_locator", "hk2_utils", "hk2_runlevel"
+]
+
+jetty = [
+ # beloved server
+ "jetty_server", "jetty_http", "jetty_io", "jetty_util"
+]
+
+database = [
+ # beloved in-memory DB for testing.
+ "hsqldb"
+]
+
+[plugins]
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2da474f..dbb84f5 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https://services.gradle.org/distributions/gradle-5.3.1-bin.zip
+distributionUrl=https://services.gradle.org/distributions/gradle-8.0-bin.zip