From 384937594d5e6a14514137070040ae58be2357bd Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 15 Aug 2023 17:01:01 +0800 Subject: [PATCH] Implement persistence layer using Elide (#4) --- docs/docs/development.md | 6 +- pom.xml | 37 ++++- .../application/ApplicationConfig.java | 4 +- .../astraios/application/BinderFactory.java | 138 +++++++++++++++++- .../astraios/application/ResourceConfig.java | 30 ++-- .../astraios/web/endpoints/DataServlet.java | 57 -------- .../astraios/web/filters/CorsFilter.java | 41 ------ .../astraios/DataServletITSpec.groovy | 86 ----------- .../astraios/JettyServerFactorySpec.groovy | 71 --------- .../web/endpoints/DataServletSpec.groovy | 51 ------- .../web/filters/CorsFilterSpec.groovy | 44 ------ .../astraios/JettyServerFactory.java | 94 ------------ .../astraios/resource/TestEndpoint.java | 54 ------- 13 files changed, 198 insertions(+), 515 deletions(-) delete mode 100644 src/main/java/com/paiondata/astraios/web/endpoints/DataServlet.java delete mode 100644 src/main/java/com/paiondata/astraios/web/filters/CorsFilter.java delete mode 100644 src/test/groovy/com/paiondata/astraios/DataServletITSpec.groovy delete mode 100755 src/test/groovy/com/paiondata/astraios/JettyServerFactorySpec.groovy delete mode 100644 src/test/groovy/com/paiondata/astraios/web/endpoints/DataServletSpec.groovy delete mode 100644 src/test/groovy/com/paiondata/astraios/web/filters/CorsFilterSpec.groovy delete mode 100755 src/test/java/com/paiondata/astraios/JettyServerFactory.java delete mode 100755 src/test/java/com/paiondata/astraios/resource/TestEndpoint.java diff --git a/docs/docs/development.md b/docs/docs/development.md index 20792c6d..b5abc2e7 100644 --- a/docs/docs/development.md +++ b/docs/docs/development.md @@ -128,7 +128,7 @@ configs. In short, the Standalone Jetty container will be setup with export JETTY_HOME=/path/to/jetty-home-11.0.15 mkdir -p /path/to/jetty-base cd /path/to/jetty-base -java -jar $JETTY_HOME/start.jar --add-module=annotations,server,http,deploy +java -jar $JETTY_HOME/start.jar --add-module=annotations,server,http,deploy,servlet,webapp,resources,jsp,websocket ``` where `/path/to/` is the _absolute_ path to the directory containing the `jetty-home-11.0.15` directory @@ -142,6 +142,10 @@ Lastly, drop the [WAR file](#packaging) into **/path/to/jetty-base/webapps** dir mv /path/to/war-file /path/to/jetty-base/webapps/ROOT.war ``` +### Setting Environment Variables + +- **MODEL_PACKAGE_NAME**: Model package in CLASSPATH + ### Running Astraios ```bash diff --git a/pom.xml b/pom.xml index 723a5337..e68f0ac4 100644 --- a/pom.xml +++ b/pom.xml @@ -206,6 +206,35 @@ 6.25.2 + + + com.yahoo.elide + elide-core + 7.0.0-pr6 + + + com.yahoo.elide + elide-graphql + + + + + com.yahoo.elide + elide-standalone + 7.0.0-pr6 + + + com.yahoo.elide + elide-graphql + + + + + mysql + mysql-connector-java + 5.1.49 + + org.spockframework @@ -233,18 +262,20 @@ org.eclipse.jetty jetty-server - test org.eclipse.jetty jetty-webapp - test org.eclipse.jetty jetty-servlet - test + + org.eclipse.jetty + jetty-slf4j-impl + + io.rest-assured rest-assured diff --git a/src/main/java/com/paiondata/astraios/application/ApplicationConfig.java b/src/main/java/com/paiondata/astraios/application/ApplicationConfig.java index cff93a03..29552027 100644 --- a/src/main/java/com/paiondata/astraios/application/ApplicationConfig.java +++ b/src/main/java/com/paiondata/astraios/application/ApplicationConfig.java @@ -44,6 +44,6 @@ @Config.Sources({"system:env", "system:properties"}) public interface ApplicationConfig extends Config { - @Key("EXAMPLE_CONFIG_KEY_NAME") - String exampleConfigKey(); + @Key("MODEL_PACKAGE_NAME") + String modelPackageName(); } diff --git a/src/main/java/com/paiondata/astraios/application/BinderFactory.java b/src/main/java/com/paiondata/astraios/application/BinderFactory.java index 7ab75c8e..7778c200 100644 --- a/src/main/java/com/paiondata/astraios/application/BinderFactory.java +++ b/src/main/java/com/paiondata/astraios/application/BinderFactory.java @@ -15,13 +15,42 @@ */ package com.paiondata.astraios.application; +import static com.yahoo.elide.standalone.Util.combineModelEntities; + +import com.yahoo.elide.Elide; +import com.yahoo.elide.ElideSettings; +import com.yahoo.elide.ElideSettingsBuilder; +import com.yahoo.elide.core.TransactionRegistry; +import com.yahoo.elide.core.datastore.DataStore; +import com.yahoo.elide.core.dictionary.EntityDictionary; +import com.yahoo.elide.core.dictionary.Injector; +import com.yahoo.elide.core.utils.ClassScanner; +import com.yahoo.elide.core.utils.DefaultClassScanner; +import com.yahoo.elide.core.utils.coerce.CoerceUtil; +import com.yahoo.elide.datastores.jpa.JpaDataStore; +import com.yahoo.elide.datastores.jpa.PersistenceUnitInfoImpl; +import com.yahoo.elide.datastores.jpa.transaction.NonJtaTransaction; + +import org.aeonbits.owner.ConfigFactory; +import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.utilities.Binder; import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.hibernate.Session; +import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; +import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.spi.PersistenceUnitInfo; import jakarta.validation.constraints.NotNull; import net.jcip.annotations.Immutable; import net.jcip.annotations.ThreadSafe; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Properties; +import java.util.function.Consumer; + /** * A binder factory builds a custom binder for the Jersey application. *

@@ -37,15 +66,120 @@ public class BinderFactory { *

* This binder should bind all relevant resources for runtime dependency injection. * + * @param injector A standard HK2 service locator + * * @return a binder instance that will be registered by putting as a parameter to * {@link org.glassfish.jersey.server.ResourceConfig#register(Object)} */ @NotNull - public Binder buildBinder() { + public Binder buildBinder(final ServiceLocator injector) { return new AbstractBinder() { + + private static final Consumer TXCANCEL = em -> em.unwrap(Session.class).cancelQuery(); + + private static final ApplicationConfig CONFIG = ConfigFactory.create(ApplicationConfig.class); + + private final ClassScanner classScanner = new DefaultClassScanner(); + @Override protected void configure() { - // intentionally left blank + final ElideSettings elideSettings = buildElideSettings(); + + bind(buildElide(elideSettings)).to(Elide.class).named("elide"); + bind(elideSettings).to(ElideSettings.class); + bind(elideSettings.getDictionary()).to(EntityDictionary.class); + bind(elideSettings.getDataStore()).to(DataStore.class).named("elideDataStore"); + } + + private Elide buildElide(final ElideSettings elideSettings) { + return new Elide( + elideSettings, + new TransactionRegistry(), + elideSettings.getDictionary().getScanner(), + false + ); + } + + private ElideSettings buildElideSettings() { + return new ElideSettingsBuilder(buildDataStore(buildEntityManagerFactory())) + .withEntityDictionary(buildEntityDictionary(injector)) + .build(); + } + + private DataStore buildDataStore(final EntityManagerFactory entityManagerFactory) { + return new JpaDataStore( + entityManagerFactory::createEntityManager, + em -> new NonJtaTransaction(em, TXCANCEL), + entityManagerFactory::getMetamodel); + } + + private EntityManagerFactory buildEntityManagerFactory() { + final String modelPackageName = CONFIG.modelPackageName(); + final ClassLoader classLoader = null; + + final PersistenceUnitInfo persistenceUnitInfo = new PersistenceUnitInfoImpl( + "astraios", + combineModelEntities(classScanner, modelPackageName, false), + getDefaultDbConfigs(), + classLoader + ); + + return new EntityManagerFactoryBuilderImpl( + new PersistenceUnitInfoDescriptor(persistenceUnitInfo), + new HashMap<>(), + classLoader + ).build(); + } + + @SuppressWarnings("MultipleStringLiterals") + private static Properties getDefaultDbConfigs() { + final Properties dbProperties = new Properties(); + + dbProperties.put("hibernate.show_sql", "true"); + dbProperties.put("hibernate.hbm2ddl.auto", "create"); + dbProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect"); + dbProperties.put("hibernate.current_session_context_class", "thread"); + dbProperties.put("hibernate.jdbc.use_scrollable_resultset", "true"); + + // Collection Proxy & JDBC Batching + dbProperties.put("hibernate.jdbc.batch_size", "50"); + dbProperties.put("hibernate.jdbc.fetch_size", "50"); + dbProperties.put("hibernate.default_batch_fetch_size", "100"); + + // Hikari Connection Pool Settings + dbProperties.putIfAbsent("hibernate.connection.provider_class", + "com.zaxxer.hikari.hibernate.HikariConnectionProvider"); + dbProperties.putIfAbsent("hibernate.hikari.connectionTimeout", "20000"); + dbProperties.putIfAbsent("hibernate.hikari.maximumPoolSize", "30"); + dbProperties.putIfAbsent("hibernate.hikari.idleTimeout", "30000"); + + dbProperties.put("jakarta.persistence.jdbc.driver", "com.mysql.jdbc.Driver"); + dbProperties.put("jakarta.persistence.jdbc.url", "jdbc:mysql://localhost/elide?serverTimezone=UTC"); + dbProperties.put("jakarta.persistence.jdbc.user", "root"); + dbProperties.put("jakarta.persistence.jdbc.password", "root"); + + return dbProperties; + } + + private EntityDictionary buildEntityDictionary(final ServiceLocator injector) { + return new EntityDictionary( + new HashMap<>(), + new HashMap<>(), + new Injector() { + @Override + public void inject(final Object entity) { + injector.inject(entity); + } + + @Override + public T instantiate(final Class cls) { + return injector.create(cls); + } + }, + CoerceUtil::lookup, + new HashSet<>(), + classScanner + ); } }; } diff --git a/src/main/java/com/paiondata/astraios/application/ResourceConfig.java b/src/main/java/com/paiondata/astraios/application/ResourceConfig.java index a8898ed0..975e9b12 100644 --- a/src/main/java/com/paiondata/astraios/application/ResourceConfig.java +++ b/src/main/java/com/paiondata/astraios/application/ResourceConfig.java @@ -15,13 +15,15 @@ */ package com.paiondata.astraios.application; -import com.paiondata.astraios.web.filters.CorsFilter; +import com.yahoo.elide.Elide; +import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.utilities.Binder; -import org.glassfish.jersey.media.multipart.MultiPartFeature; import jakarta.inject.Inject; +import jakarta.servlet.ServletContext; import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Context; import net.jcip.annotations.Immutable; import net.jcip.annotations.ThreadSafe; @@ -30,21 +32,31 @@ */ @Immutable @ThreadSafe -@ApplicationPath("v1") +@ApplicationPath("/v1/data/") public class ResourceConfig extends org.glassfish.jersey.server.ResourceConfig { - private static final String ENDPOINT_PACKAGE = "com.paiondata.astraios.web.endpoints"; + private static final String ENDPOINT_PACKAGE = "com.yahoo.elide.jsonapi.resources"; /** * DI Constructor. + * + * @param injector A standard HK2 service locator + * @param servletContext Currently unused */ @Inject - public ResourceConfig() { - final Binder binder = new BinderFactory().buildBinder(); + public ResourceConfig(final ServiceLocator injector, @Context final ServletContext servletContext) { + final Binder binder = new BinderFactory().buildBinder(injector); - packages(ENDPOINT_PACKAGE); - register(new CorsFilter()); register(binder); - register(MultiPartFeature.class); + + register(new org.glassfish.hk2.utilities.binding.AbstractBinder() { + @Override + protected void configure() { + final Elide elide = injector.getService(Elide.class, "elide"); + elide.doScans(); + } + }); + + packages(ENDPOINT_PACKAGE); } } diff --git a/src/main/java/com/paiondata/astraios/web/endpoints/DataServlet.java b/src/main/java/com/paiondata/astraios/web/endpoints/DataServlet.java deleted file mode 100644 index 9933323d..00000000 --- a/src/main/java/com/paiondata/astraios/web/endpoints/DataServlet.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Paion Data - * - * Licensed 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 com.paiondata.astraios.web.endpoints; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import net.jcip.annotations.Immutable; -import net.jcip.annotations.ThreadSafe; - -/** - * Endpoint that contains a basic sanity-check. - */ -@Singleton -@Immutable -@ThreadSafe -@Path("/data") -@Produces(MediaType.APPLICATION_JSON) -public class DataServlet { - - private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); - - /** - * Constructor for dependency injection. - */ - @Inject - public DataServlet() { - // intentionally left blank - } - - @GET - @Path("/healthcheck") - public Response healthcheck() { - return Response - .status(Response.Status.OK) - .build(); - } -} diff --git a/src/main/java/com/paiondata/astraios/web/filters/CorsFilter.java b/src/main/java/com/paiondata/astraios/web/filters/CorsFilter.java deleted file mode 100644 index 23a3f7c5..00000000 --- a/src/main/java/com/paiondata/astraios/web/filters/CorsFilter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Paion Data - * - * Licensed 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 com.paiondata.astraios.web.filters; - -import jakarta.validation.constraints.NotNull; -import jakarta.ws.rs.container.ContainerRequestContext; -import jakarta.ws.rs.container.ContainerResponseContext; -import jakarta.ws.rs.container.ContainerResponseFilter; - -public class CorsFilter implements ContainerResponseFilter { - - @Override - public void filter( - @NotNull final ContainerRequestContext request, - @NotNull final ContainerResponseContext response - ) { - response.getHeaders().add("Access-Control-Allow-Origin", "*"); - response.getHeaders().add( - "Access-Control-Allow-Headers", - "CSRF-Token, X-Requested-By, Authorization, Content-Type" - ); - response.getHeaders().add("Access-Control-Allow-Credentials", "true"); - response.getHeaders().add( - "Access-Control-Allow-Methods", - "GET, POST, PUT, DELETE, OPTIONS, HEAD" - ); - } -} diff --git a/src/test/groovy/com/paiondata/astraios/DataServletITSpec.groovy b/src/test/groovy/com/paiondata/astraios/DataServletITSpec.groovy deleted file mode 100644 index 660220dd..00000000 --- a/src/test/groovy/com/paiondata/astraios/DataServletITSpec.groovy +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Paion Data - * - * Licensed 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 com.paiondata.astraios - -import org.testcontainers.containers.GenericContainer -import org.testcontainers.images.PullPolicy -import org.testcontainers.images.builder.ImageFromDockerfile -import org.testcontainers.spock.Testcontainers - -import io.restassured.RestAssured -import spock.lang.IgnoreIf -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Subject -import spock.lang.Unroll - -import java.nio.file.Paths - -/** - * Integration tests for WS running in Dockerfile. - * - * It uses testcontainers to orchestrate lifecycle of the test container through @Testcontainers annotation - * - * see https://www.testcontainers.org/quickstart/spock_quickstart/ - * see https://www.testcontainers.org/test_framework_integration/spock/#testcontainers-class-annotation - */ -@Testcontainers -@IgnoreIf({ isLocal() }) -class DataServletITSpec extends Specification { - - static final int SUCCESS = 0 - static final List LOCAL_ENVS = ["Mac OS X", "windows"] - static final String CHECK_DOCKER_INSTALLED_COMMAND = "docker -v" - static final String DOCKERFILE_ABS_PATH = String.format("%s/Dockerfile", System.getProperty("user.dir")) - - @Deprecated - @SuppressWarnings('GroovyUnusedCatchParameter') - private static boolean dockerNotInstalled() { - try { - return Runtime.getRuntime().exec(CHECK_DOCKER_INSTALLED_COMMAND).waitFor() != SUCCESS - } catch (Exception exception) { - return true // I hate this - } - } - - private static boolean isLocal() { - return System.properties['os.name'] as String in LOCAL_ENVS - } - - @Shared - @Subject - GenericContainer container = new GenericContainer<>( - new ImageFromDockerfile().withDockerfile(Paths.get(DOCKERFILE_ABS_PATH)) - ) - .withExposedPorts(8080) - .withImagePullPolicy(PullPolicy.defaultPolicy()) - - def setupSpec() { - RestAssured.baseURI = "http://" + container.host - RestAssured.port = container.firstMappedPort - RestAssured.basePath = "/v1" - } - - @Unroll - def "Dockerized WS responds to healthcheck request 200 SUCCESS"() { - expect: - RestAssured.given() - .when() - .get("/data/healthcheck") - .then() - .statusCode(200) - } -} diff --git a/src/test/groovy/com/paiondata/astraios/JettyServerFactorySpec.groovy b/src/test/groovy/com/paiondata/astraios/JettyServerFactorySpec.groovy deleted file mode 100755 index a2f93e40..00000000 --- a/src/test/groovy/com/paiondata/astraios/JettyServerFactorySpec.groovy +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright Paion Data - * - * Licensed 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 com.paiondata.astraios - -import org.eclipse.jetty.server.Server -import org.glassfish.jersey.server.ResourceConfig - -import io.restassured.RestAssured -import jakarta.inject.Inject -import jakarta.ws.rs.ApplicationPath -import spock.lang.Specification - -class JettyServerFactorySpec extends Specification { - - static final int PORT = 8235 - static final String ENDPOINT_RESOURCE_PACKAGE = "com.paiondata.astraios.resource" - - /** - * DI constructor. - *

- * CAUTION: the {@code @ApplicationPath("v1")} is not taking effects. See {@link JettyServerFactory} for more - * details. - */ - @ApplicationPath("v1") - class TestResourceConfig extends ResourceConfig { - - @Inject - TestResourceConfig() { - packages(ENDPOINT_RESOURCE_PACKAGE) - } - } - - def setupSpec() { - RestAssured.baseURI = "http://localhost" - RestAssured.port = PORT - RestAssured.basePath = "/v1" - } - - def "Factory produces Jsersey-Jetty applications"() { - setup: - Server server = JettyServerFactory.newInstance(PORT, "/v1/*", new TestResourceConfig()) - server.start() - - expect: - RestAssured - .when() - .get("/v1/example/test") - .then() - .statusCode(200) - - RestAssured - .when() - .get("/v1/example/test").asString() == "SUCCESS" - - cleanup: - server.stop() - } -} diff --git a/src/test/groovy/com/paiondata/astraios/web/endpoints/DataServletSpec.groovy b/src/test/groovy/com/paiondata/astraios/web/endpoints/DataServletSpec.groovy deleted file mode 100644 index 041ec000..00000000 --- a/src/test/groovy/com/paiondata/astraios/web/endpoints/DataServletSpec.groovy +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Paion Data - * - * Licensed 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 com.paiondata.astraios.web.endpoints - -import com.paiondata.astraios.JettyServerFactory -import com.paiondata.astraios.application.ResourceConfig - -import org.eclipse.jetty.server.Server - -import io.restassured.RestAssured -import spock.lang.Specification - -class DataServletSpec extends Specification { - - static final int PORT = 8080 - - def setupSpec() { - RestAssured.baseURI = "http://localhost" - RestAssured.port = PORT - RestAssured.basePath = "/v1" - } - - def "Healthchecking endpoints returns 200"() { - setup: - Server server = JettyServerFactory.newInstance(PORT, "/v1/*", new ResourceConfig()) - server.start() - - expect: - RestAssured.given() - .when() - .get("/data/healthcheck") - .then() - .statusCode(200) - - cleanup: - server.stop() - } -} diff --git a/src/test/groovy/com/paiondata/astraios/web/filters/CorsFilterSpec.groovy b/src/test/groovy/com/paiondata/astraios/web/filters/CorsFilterSpec.groovy deleted file mode 100644 index e199e77a..00000000 --- a/src/test/groovy/com/paiondata/astraios/web/filters/CorsFilterSpec.groovy +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Paion Data - * - * Licensed 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 com.paiondata.astraios.web.filters - -import jakarta.ws.rs.container.ContainerRequestContext -import jakarta.ws.rs.container.ContainerResponseContext -import jakarta.ws.rs.core.MultivaluedHashMap -import jakarta.ws.rs.core.MultivaluedMap -import spock.lang.Specification - -class CorsFilterSpec extends Specification { - - def "Cross-origin header gets attached"() { - given: - ContainerResponseContext response = Mock(ContainerResponseContext) - MultivaluedMap headers = Mock(MultivaluedMap) - response.getHeaders() >>> [ - headers, - new MultivaluedHashMap<>([:]), - new MultivaluedHashMap<>([:]), - new MultivaluedHashMap<>([:]) - ] - - when: - new CorsFilter().filter(Mock(ContainerRequestContext), response) - - then: - 1 * headers.add("Access-Control-Allow-Origin", "*") - } -} - diff --git a/src/test/java/com/paiondata/astraios/JettyServerFactory.java b/src/test/java/com/paiondata/astraios/JettyServerFactory.java deleted file mode 100755 index 6bc04a57..00000000 --- a/src/test/java/com/paiondata/astraios/JettyServerFactory.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright Paion Data - * - * Licensed 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 com.paiondata.astraios; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.servlet.ServletContainer; - -import net.jcip.annotations.Immutable; -import net.jcip.annotations.ThreadSafe; - -import java.util.Objects; - -/** - * {@link JettyServerFactory} is provides embedded Jersey-Jetty instances for testing purposes. - *

- * Note that {@link JettyServerFactory} is designed only for testing purposes. Any production uses are not assumed. - */ -@Immutable -@ThreadSafe -public final class JettyServerFactory { - - /** - * Constructor. - *

- * Suppress default constructor for noninstantiability. - * - * @throws AssertionError when called - */ - private JettyServerFactory() { - throw new AssertionError(); - } - - /** - * Returns a embedded Jersey-Jetty server for local testing purposes. - * - * @param port The port number serving all testing requests on the embedded Jetty - * @param pathSpec The common path of all API's, e.g. "/v1/*" - * @param resourceConfig A Jersey subclass of JAX-RS {@link jakarta.ws.rs.core.Application}. Due to a - * bug) in Jersey, {@code @ApplicationPath} - * annotated on {@code resourceConfig} class is ignored in embedded Jetty. For example - * - *

-     * {@code
-     * @ApplicationPath("v1")
-     * class TestResourceConfig extends ResourceConfig {
-     *
-     *     @Inject
-     *     TestResourceConfig() {
-     *         packages(ENDPOINT_RESOURCE_PACKAGE)
-     *     }
-     * }
-     * }
-     * 
- * - * The {@code @ApplicationPath("v1")} annotation is - * not taking any effects. We must somehow prefix - * "v1" either at endpoint resource (such as {@code @Path("/v1/...")}) or completely remove "v1" in test request - * path. Which option to choose makes no difference. - * - * @return the embedded Jetty server for local testing purposes - * - * @throws NullPointerException if {@code pathSpec} or {@code resourceConfig} is {@code null} - */ - public static Server newInstance(final int port, final String pathSpec, final ResourceConfig resourceConfig) { - Objects.requireNonNull(pathSpec, "pathSpec"); - Objects.requireNonNull(resourceConfig, "resourceConfig"); - - final Server server = new Server(port); - - final ServletContainer servletContainer = new ServletContainer(resourceConfig); - final ServletHolder servletHolder = new ServletHolder(servletContainer); - final ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); - servletContextHandler.addServlet(servletHolder, pathSpec); - server.setHandler(servletContextHandler); - - return server; - } -} diff --git a/src/test/java/com/paiondata/astraios/resource/TestEndpoint.java b/src/test/java/com/paiondata/astraios/resource/TestEndpoint.java deleted file mode 100755 index 02510098..00000000 --- a/src/test/java/com/paiondata/astraios/resource/TestEndpoint.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Paion Data - * - * Licensed 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 com.paiondata.astraios.resource; - -import com.paiondata.astraios.JettyServerFactory; - -import org.glassfish.jersey.server.ResourceConfig; - -import jakarta.inject.Singleton; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Response; -import net.jcip.annotations.Immutable; -import net.jcip.annotations.ThreadSafe; - -/** - * A JAX-RS resource class used for testing {@link JettyServerFactory}. - * - * see {@link JettyServerFactory#newInstance(int, String, ResourceConfig)} for why we - * need to prefix @Path with "/v1" - */ -@Singleton -@Immutable -@ThreadSafe -@Path("/v1/example") -public class TestEndpoint { - - /** - * A sanity check endpoint that simply returns a 200 response. - * - * @return a simple success response - */ - @GET - @Path("/test") - public Response test() { - return Response - .status(Response.Status.OK) - .entity("SUCCESS") - .build(); - } -}