From 5503af5f02eaf7f163ac6189eda7df2aa69c5cd8 Mon Sep 17 00:00:00 2001 From: apasternak Date: Wed, 20 Nov 2024 15:53:25 +0100 Subject: [PATCH 1/7] Memory leak fix --- .../ObservationRequestEventListener.java | 11 ++-- ...ctObservationRequestEventListenerTest.java | 52 ++++++++++++++++++- 2 files changed, 57 insertions(+), 6 deletions(-) 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 index 96c463cddc..6fc0f0166e 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 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 @@ -65,7 +65,7 @@ public void onEvent(RequestEvent event) { switch (event.getType()) { case ON_EXCEPTION: - if (!isNotFoundException(event)) { + if (!isClientError(event) || observations.get(containerRequest) != null) { break; } startObservation(event); @@ -102,13 +102,14 @@ private void startObservation(RequestEvent event) { observations.put(event.getContainerRequest(), new ObservationScopeAndContext(scope, jerseyContext)); } - private boolean isNotFoundException(RequestEvent event) { + private boolean isClientError(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("javax.ws.rs.NotFoundException"); + String className = t.getClass().getSuperclass().getCanonicalName(); + return className.equals("jakarta.ws.rs.ClientErrorException") + || className.equals("javax.ws.rs.ClientErrorException"); } private static class ObservationScopeAndContext { 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 index 597e6a61a7..5e6a06e888 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 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 @@ -20,6 +20,7 @@ import java.util.logging.Logger; import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; @@ -38,6 +39,7 @@ 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.mapper.ResourceGoneExceptionMapper; import org.glassfish.jersey.micrometer.server.resources.TestResource; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; @@ -85,6 +87,7 @@ protected Application configure() { final ResourceConfig config = new ResourceConfig(); config.register(listener); config.register(TestResource.class); + config.register(ResourceGoneExceptionMapper.class); return config; } @@ -131,6 +134,53 @@ void resourcesAreTimed() { .hasTag("outcome", "SUCCESS") .hasTag("status", "200") .hasTag("uri", "/sub-resource/sub-hello/{name}"); + assertThat(observationRegistry.getCurrentObservation()).isNull(); + } + + @Test + void errorResourcesAreTimed() { + 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) { + } + try { + target("produces-text-plain").request(MediaType.APPLICATION_JSON).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); + + assertThat(registry.get(METRIC_NAME) + .tags(tagsFrom("UNKNOWN", "406", "CLIENT_ERROR", "NotAcceptableException")) + .timer() + .count()).isEqualTo(1); + + assertThat(observationRegistry.getCurrentObservation()).isNull(); } private static Iterable tagsFrom(String uri, String status, String outcome, String exception) { From b2c7ba6d388cb9722f39073d7e82aa818fec49d5 Mon Sep 17 00:00:00 2001 From: jansupol <15908245+jansupol@users.noreply.github.com> Date: Mon, 2 Dec 2024 08:19:50 +0100 Subject: [PATCH 2/7] Fix possible concurrent issue with SSL & default connector (#5794) * Fix possible concurrent issue with SSL & default connector Signed-off-by: jansupol --------- Signed-off-by: jansupol --- .../client/HttpUrlConnectorProvider.java | 17 +-- .../client/internal/HttpUrlConnector.java | 6 +- tests/e2e-tls/pom.xml | 18 +++ .../ConcurrentHttpsUrlConnectionTest.java | 119 ++++++++++++++++++ .../tests/e2e/tls/connector/HttpsServer.java | 87 +++++++++++++ .../tests/e2e/tls/connector/keystore.jks | Bin 0 -> 2748 bytes 6 files changed, 231 insertions(+), 16 deletions(-) create mode 100644 tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/ConcurrentHttpsUrlConnectionTest.java create mode 100644 tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/HttpsServer.java create mode 100644 tests/e2e-tls/src/test/resources/org/glassfish/jersey/tests/e2e/tls/connector/keystore.jks 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 66925cf551..1036105e7c 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 @@ -21,9 +21,6 @@ import java.net.Proxy; import java.net.URL; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; import javax.ws.rs.client.Client; @@ -290,16 +287,12 @@ public interface ConnectionFactory { * @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); - } + return (proxy == null) ? getConnection(url) : (HttpURLConnection) url.openConnection(proxy); } } private static class DefaultConnectionFactory implements ConnectionFactory { - private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); - @Override public HttpURLConnection getConnection(final URL url) throws IOException { return connect(url, null); @@ -311,13 +304,7 @@ public HttpURLConnection getConnection(URL url, Proxy proxy) throws IOException } private HttpURLConnection connect(URL url, Proxy proxy) throws IOException { - Lock lock = locks.computeIfAbsent(url, u -> new ReentrantLock()); - lock.lock(); - try { - return (proxy == null) ? (HttpURLConnection) url.openConnection() : (HttpURLConnection) url.openConnection(proxy); - } finally { - lock.unlock(); - } + return (proxy == null) ? (HttpURLConnection) url.openConnection() : (HttpURLConnection) url.openConnection(proxy); } } 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 350ad2b2a7..3433e2c8e0 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 @@ -84,7 +84,7 @@ public class HttpUrlConnector implements Connector { private static final String ALLOW_RESTRICTED_HEADERS_SYSTEM_PROPERTY = "sun.net.http.allowRestrictedHeaders"; // Avoid multi-thread uses of HttpsURLConnection.getDefaultSSLSocketFactory() because it does not implement a // proper lazy-initialization. See https://github.com/jersey/jersey/issues/3293 - private static final Value DEFAULT_SSL_SOCKET_FACTORY = + private static final LazyValue DEFAULT_SSL_SOCKET_FACTORY = Values.lazy((Value) () -> HttpsURLConnection.getDefaultSSLSocketFactory()); // The list of restricted headers is extracted from sun.net.www.protocol.http.HttpURLConnection private static final String[] restrictedHeaders = { @@ -387,6 +387,10 @@ private ClientResponse _apply(final ClientRequest request) throws IOException { sniUri = request.getUri(); } + if (!DEFAULT_SSL_SOCKET_FACTORY.isInitialized() && "HTTPS".equalsIgnoreCase(sniUri.getScheme())) { + DEFAULT_SSL_SOCKET_FACTORY.get(); + } + proxy.ifPresent(clientProxy -> ClientProxy.setBasicAuthorizationHeader(request.getHeaders(), proxy.get())); uc = this.connectionFactory.getConnection(sniUri.toURL(), proxy.isPresent() ? proxy.get().proxy() : null); uc.setDoInput(true); diff --git a/tests/e2e-tls/pom.xml b/tests/e2e-tls/pom.xml index 3797bda9cf..737baa7134 100644 --- a/tests/e2e-tls/pom.xml +++ b/tests/e2e-tls/pom.xml @@ -164,6 +164,24 @@ + + JDK_17- + + [1.8,17) + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/ConcurrentHttpsUrlConnectionTest* + + + + + diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/ConcurrentHttpsUrlConnectionTest.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/ConcurrentHttpsUrlConnectionTest.java new file mode 100644 index 0000000000..ecf2a6ee1b --- /dev/null +++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/ConcurrentHttpsUrlConnectionTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available 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.connector; + +import org.junit.jupiter.api.Test; + +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Client; +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.MediaType; + +import java.io.InputStream; +import java.net.URL; +import java.security.KeyStore; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManagerFactory; + +/** + * Jersey client seems to be not thread-safe: + * When the first GET request is in progress, + * all parallel requests from other Jersey client instances fail + * with SSLHandshakeException: PKIX path building failed. + *

+ * Once the first GET request is completed, + * all subsequent requests work without error. + *

+ * BUG 5749 + */ +public class ConcurrentHttpsUrlConnectionTest { + private static int THREAD_NUMBER = 5; + + private static volatile int responseCounter = 0; + + private static SSLContext createContext() throws Exception { + URL url = ConcurrentHttpsUrlConnectionTest.class.getResource("keystore.jks"); + KeyStore keyStore = KeyStore.getInstance("JKS"); + try (InputStream is = url.openStream()) { + keyStore.load(is, "password".toCharArray()); + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(keyStore, "password".toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); + tmf.init(keyStore); + SSLContext context = SSLContext.getInstance("TLS"); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return context; + } + + @Test + public void testSSLConnections() throws Exception { + if (THREAD_NUMBER == 1) { + System.out.println("\nThis is the working case (THREAD_NUMBER==1). Set THREAD_NUMBER > 1 to reproduce the error! \n"); + } + + final HttpsServer server = new HttpsServer(createContext()); + Executors.newFixedThreadPool(1).submit(server); + + // set THREAD_NUMBER > 1 to reproduce an issue + ExecutorService executorService2clients = Executors.newFixedThreadPool(THREAD_NUMBER); + + final ClientBuilder builder = ClientBuilder.newBuilder().sslContext(createContext()) + .hostnameVerifier(new HostnameVerifier() { + public boolean verify(String arg0, SSLSession arg1) { + return true; + } + }); + + AtomicInteger counter = new AtomicInteger(0); + + for (int i = 0; i < THREAD_NUMBER; i++) { + executorService2clients.submit(new Runnable() { + @Override + public void run() { + try { + Client client = builder.build(); + String ret = client.target("https://127.0.0.1:" + server.getPort() + "/" + new Random().nextInt()) + .request(MediaType.TEXT_HTML) + .get(new GenericType() { + }); + System.out.print(++responseCounter + ". Server returned: " + ret); + } catch (Exception e) { + //get an exception here, if jersey lib is buggy and THREAD_NUMBER > 1: + //jakarta.ws.rs.ProcessingException: javax.net.ssl.SSLHandshakeException: PKIX path building failed: + e.printStackTrace(); + } finally { + System.out.println(counter.incrementAndGet()); + } + } + }); + } + + while (counter.get() != THREAD_NUMBER) { + Thread.sleep(100L); + } + server.stop(); + } +} diff --git a/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/HttpsServer.java b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/HttpsServer.java new file mode 100644 index 0000000000..31214f1174 --- /dev/null +++ b/tests/e2e-tls/src/test/java/org/glassfish/jersey/tests/e2e/tls/connector/HttpsServer.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available 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.connector; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSocket; + +class HttpsServer implements Runnable { + private final SSLServerSocket sslServerSocket; + private boolean closed = false; + + public HttpsServer(SSLContext context) throws Exception { + sslServerSocket = (SSLServerSocket) context.getServerSocketFactory().createServerSocket(0); + } + + public int getPort() { + return sslServerSocket.getLocalPort(); + } + + @Override + public void run() { + System.out.printf("Server started on port %d%n", getPort()); + while (!closed) { + SSLSocket s; + try { + s = (SSLSocket) sslServerSocket.accept(); + } catch (IOException e2) { + s = null; + } + final SSLSocket socket = s; + new Thread(new Runnable() { + public void run() { + try { + if (socket != null) { + InputStream is = new BufferedInputStream(socket.getInputStream()); + byte[] data = new byte[2048]; + int len = is.read(data); + if (len <= 0) { + throw new IOException("no data received"); + } + //System.out.printf("Server received: %s\n", new String(data, 0, len)); + PrintWriter writer = new PrintWriter(socket.getOutputStream()); + writer.println("HTTP/1.1 200 OK"); + writer.println("Content-Type: text/html"); + writer.println(); + writer.println("Hello from server!"); + writer.flush(); + writer.close(); + socket.close(); + } + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }).start(); + } + } + + void stop() { + try { + closed = true; + sslServerSocket.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/tests/e2e-tls/src/test/resources/org/glassfish/jersey/tests/e2e/tls/connector/keystore.jks b/tests/e2e-tls/src/test/resources/org/glassfish/jersey/tests/e2e/tls/connector/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..130aa714826af1f436af885e065b50a1788138e2 GIT binary patch literal 2748 zcma)8c{mh`7M~e2wuWmf%(d?fnX#vsF=dHRl%*_B7pP6lW6mg$u|A zB$GI;!6Z)0AFvsM1gZTm3Q`ItL5hCBc|R(P^~8T%P);C-OoE_)faegmemWpr2z!Li zuN8`bg9T=>Q=ZMI{a*z4Wn0ZdXOaV{xiTP-JP!ycfq=5I|NA0{0|G#ZvU1#vGXc7@ zfPqS2p2CAzpFFK=9FMO^BH&&Fl3)^exOlT}>@s@Do2mj~ewqJr78{ix{r7xBnDg3)hNukmNa#?nm~ z7e1(Kjc~DFVlg2k3z7Gib{3xu-gsscYF#5MkL8w#7s-t|AtztY3yye};@H5Sdyh+A z;iLUb+2$HT=4=;O?=EhfZSk`_(bGHgh`PUZkSuvtpys_4A<8Enun``aiw~UK0k?R* zCY5<^idmdQg=X+K?_al?a}*vBeb+JNX4k@l3>=#0I39i#Oj#inY(=;CI!ugHbF7U- z#3&4@$4jX5>S)GzZ1 zf7}Nmp857}-ARJ6bq7Pe-kVvbgElc=aL_HOSWy)^}=ERZR5WTn^ffDy|r@tYlvfyGF-Ro)kNcKlKKGUf$T5*;8~m$ywq9gy-UGegQSs%v&WMlEVhL}RHan8t zXnJSKpGO}?~EKOGTbmG*(~cCV*h`_GDZ z(eYz{5LTZzPc?NIOJ2+wksg~|pvxpfXp6@)?0(D<# zb#ZTJ);rGF!X!n~X^-D1z(q=G2cTP@lYw;=!@S#tv2xNoq?M4{`p{T!YI9-eZo@z> z7rr}Z4a2b9R zbZ`@a_pt5-OF@>1deylZgXPGAOs$EL#EV@{2X!}y3E%eCWLf>63?Y*s8B3I4`_+9^ zkxdzKMWh>kHIePcqYc+DUd7I1sR$=fd385c%_rNhpH7Wq&flxrvr?(9rm0)pjA9!{ z=pQT}J2yH-&6Yv2?q5D>j#Q1&#)%1gMLF&TO_E1{Q<@$nDAp`;mNlEQpo{2-1#W>; zVfwN;ANN&wrHqx(8WsD!XNry5Tb7Y=S8B}0nu=!^29wREs+J`WMXN))9q20J0<=>k z6HBEBlv2~ux`v=PUQewuW^#*f+bfss$3fVKJ^l42e03UgieJt@`P#j<|J;do5v?X0 z9@niqQ1(pEY>=(8cqZtsHg#|`&Zbr6ouz;>gT5@*AR~yzQ169U>2%<;0<1Nn4s<$w zIZ$4;40gFe`Wry6Abkphie8=^dhGl(HI<8eNob7XLZ0A>YU!DFyBKLdT1VDsA*HUo zRw&Z3h@ds9*wMBn2tJNQ$Y!ILvlXliLAdxW+yWCB%9>|FyRNvrw14T^+v38H3!j#c z8KD-+-UTQLX}OjW`5f2kYbNdHlh*3ayM|2?b1^j2;+b`^Z2b=0XDU%+6gm}>R4rzU zPdQco=+oykwZG5@-1-EMu;3at)#RIv8xKh}1&3<$M{JdiM;I*Kusm5f)1UT+pm}I1 zXO#^o%udfMy_^(7=~!~evUk*x)NaZw~NK%>aWqxN@cW3wy7^W4>!gYWwuh^vM(8V3#9s@X-S zy=UVg6HR-R)%c@{w%56g`ZT!4a9`U8vzn6cP4t7X8-HAh;s|g^m}QpMdL8h1romI& z{8N&&cUSv&y_JNu57jqpaC0}kC~m|tPQrKFnv?=S`ZJuSzL908K%vvk23?-DqVG-E z&WQNY;Yt2M10*N&*1YMOVE^^5uY4?)?UU98BAsm0$i@AX)3C=`%ElMD&*R$)uNB(I z-T!o|CbpKV?F(#;m^Jd(yWa=yHluZ06F9L}@0IZ? z$^$j6Bt4E(xCo{%sEC!D%-Ee{OY1wA67JeBR%otzlv8s8(EsA=i;M`C(dgGbfLV_h zLuUJLi(m)Na!ch@uJjDc@p1Do+4IA5hswzCUgE-P{C*_D18(6sP;rO1HwRfUiiVzcr>-HIfe}7C#i;B+}ZR0wAK3P!)v> z%Fn-t#X221#oU_or%eauYT*=fv*IsbD3TA|%|2XQ6Tj*sWc}J>TmRA7{4gV{OkP}x zX$cPxr{CddPlb(Xk?WkBaK`g(LiaddH^NtL9VT;n``%VIqdigZjOn6SRu!zb(jhOr z#AF4RtyrzVcG&Mh;Sa_H;NI{t%T|a5ymLc>%U4TY;n{`EvUff*Og+X6-<1oWLu~BH zK*ir{Zt-ETekpMob5Iv=S9=oyO*9eesmM1%*Vzf@jPH%j=RSDhIvvC6**6RSH8Y&1+3Lfve)~_Cr2J0!GVdEOu zM@nI^gb=eHeln`bO)=9Qrm5lz$M~cOHJet(SJErZj7uTNib% zfySM+w)8RakKJb_yDqC^-afPoaD6nB3e|eA>e*YfkzmiIsI4EUurftQmyiEV87w33 zItEFZ76dT<#7}GHd6`UHqN$^;c19<=6yOmi9>)*&6xSGGG&}3Iei(&&9q%lv+6AXi zpkc$Vo3=Z!5tPMU`-PYRbmC|%<$ULVIwQ_Q+ik?cuiWWfrzuRZ9n#cS zpWV2OFh?K}Tt7cL5D)?YOSm)$EPUed;T}6_^okdm;(WycDIq?s#`4Xkzh|Tc3Iq#e bwnZ9qSY3#Ld?i<+V&;)Ma&Kb(iJ*T0av%r= literal 0 HcmV?d00001 From 6c8a20941e5b28abe56d41e283cfd211cba83065 Mon Sep 17 00:00:00 2001 From: jansupol Date: Fri, 29 Nov 2024 14:03:26 +0100 Subject: [PATCH 3/7] Fix memory leak when client does not use HK2 Signed-off-by: jansupol --- .../innate/inject/NonInjectionManager.java | 253 +++++++++++------- .../inject/NonInjectionRequestScope.java | 27 +- tests/e2e-inject/non-inject/pom.xml | 57 ++++ .../noninject/DisposableSuplierTest.java | 98 +++++++ .../noninject/InstanceListSizeTest.java | 195 ++++++++++++++ .../e2e/inject/noninject/PreDestroyTest.java | 96 +++++++ tests/e2e-inject/pom.xml | 1 + 7 files changed, 628 insertions(+), 99 deletions(-) create mode 100644 tests/e2e-inject/non-inject/pom.xml create mode 100644 tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/DisposableSuplierTest.java create mode 100644 tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/InstanceListSizeTest.java create mode 100644 tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/PreDestroyTest.java 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 index cc3b8eacad..a378ef0212 100644 --- 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 @@ -17,6 +17,7 @@ package org.glassfish.jersey.client.innate.inject; import org.glassfish.jersey.client.internal.LocalizationMessages; +import org.glassfish.jersey.internal.inject.AbstractBinder; import org.glassfish.jersey.internal.inject.Binder; import org.glassfish.jersey.internal.inject.Binding; import org.glassfish.jersey.internal.inject.ClassBinding; @@ -30,6 +31,7 @@ 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.Ref; import org.glassfish.jersey.internal.util.collection.Value; import org.glassfish.jersey.internal.util.collection.Values; import org.glassfish.jersey.process.internal.RequestScope; @@ -52,10 +54,12 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; import java.lang.reflect.Type; -import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -74,8 +78,6 @@ public final class NonInjectionManager implements InjectionManager { 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(); @@ -88,10 +90,11 @@ public final class NonInjectionManager implements InjectionManager { * @param the type for which the instance is created, either Class, or ParametrizedType (for instance * Provider<SomeClass>). */ - private class TypedInstances { + private class TypedInstances { private final MultivaluedMap> singletonInstances = new MultivaluedHashMap<>(); private ThreadLocal>> threadInstances = new ThreadLocal<>(); - private final List threadPredestroyables = Collections.synchronizedList(new LinkedList<>()); + private ThreadLocal> disposableSupplierObjects = + ThreadLocal.withInitial(() -> new MultivaluedHashMap<>()); private List> _getSingletons(TYPE clazz) { List> si; @@ -102,7 +105,7 @@ private List> _getSingletons(TYPE clazz) { } @SuppressWarnings("unchecked") - T _addSingleton(TYPE clazz, T instance, Binding binding, Annotation[] qualifiers) { + T _addSingleton(TYPE clazz, T instance, Binding binding, Annotation[] qualifiers, boolean destroy) { synchronized (singletonInstances) { // check existing singleton with a qualifier already created by another thread io a meantime List> values = singletonInstances.get(clazz); @@ -115,19 +118,19 @@ T _addSingleton(TYPE clazz, T instance, Binding binding, Annotation[] return (T) qualified.get(0).instance; } } - singletonInstances.add(clazz, new InstanceContext<>(instance, binding, qualifiers)); - threadPredestroyables.add(instance); + InstanceContext instanceContext = new InstanceContext<>(instance, binding, qualifiers, !destroy); + singletonInstances.add(clazz, instanceContext); return instance; } } @SuppressWarnings("unchecked") T addSingleton(TYPE clazz, T t, Binding binding, Annotation[] instanceQualifiers) { - T t2 = _addSingleton(clazz, t, binding, instanceQualifiers); + T t2 = _addSingleton(clazz, t, binding, instanceQualifiers, true); if (t2 == t) { for (Type contract : binding.getContracts()) { if (!clazz.equals(contract) && isClass(contract)) { - _addSingleton((TYPE) contract, t, binding, instanceQualifiers); + _addSingleton((TYPE) contract, t, binding, instanceQualifiers, false); } } } @@ -143,21 +146,22 @@ private List> _getThreadInstances(TYPE clazz) { return list; } - private void _addThreadInstance(TYPE clazz, T instance, Binding binding, Annotation[] qualifiers) { + private void _addThreadInstance( + TYPE clazz, T instance, Binding binding, Annotation[] qualifiers, boolean destroy) { MultivaluedMap> map = threadInstances.get(); if (map == null) { map = new MultivaluedHashMap<>(); threadInstances.set(map); } - map.add(clazz, new InstanceContext<>(instance, binding, qualifiers)); - threadPredestroyables.add(instance); + InstanceContext instanceContext = new InstanceContext<>(instance, binding, qualifiers, !destroy); + map.add(clazz, instanceContext); } void addThreadInstance(TYPE clazz, T t, Binding binding, Annotation[] instanceQualifiers) { - _addThreadInstance(clazz, t, binding, instanceQualifiers); + _addThreadInstance(clazz, t, binding, instanceQualifiers, true); for (Type contract : binding.getContracts()) { if (!clazz.equals(contract) && isClass(contract)) { - _addThreadInstance((TYPE) contract, t, binding, instanceQualifiers); + _addThreadInstance((TYPE) contract, t, binding, instanceQualifiers, false); } } } @@ -175,28 +179,78 @@ List> getContexts(TYPE clazz, Annotation[] 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; + return InstanceContext.merge(si, ti); } T getInstance(TYPE clazz, Annotation[] annotations) { List i = getInstances(clazz, annotations); if (i != null) { checkUnique(i); - return i.get(0); + return instanceOrSupply(clazz, i.get(0)); } return null; } + private T instanceOrSupply(TYPE clazz, T t) { + if (!Class.class.isInstance(clazz) || ((Class) clazz).isInstance(t)) { + return t; + } else if (Supplier.class.isInstance(t)) { + return (T) registerDisposableSupplierAndGet((Supplier) t, this); + } else if (Provider.class.isInstance(t)) { + return (T) ((Provider) t).get(); + } else { + return t; + } + } + void dispose() { singletonInstances.forEach((clazz, instances) -> instances.forEach(instance -> preDestroy(instance.getInstance()))); - threadPredestroyables.forEach(NonInjectionManager.this::preDestroy); + disposeThreadInstances(true); /* The java.lang.ThreadLocal$ThreadLocalMap$Entry[] keeps references to this NonInjectionManager */ threadInstances = null; + disposableSupplierObjects = null; + } + + void disposeThreadInstances(boolean allThreadInstances) { + MultivaluedMap> ti = threadInstances.get(); + if (ti == null) { + return; + } + Set>>> tiSet = ti.entrySet(); + Iterator>>> tiSetIt = tiSet.iterator(); + while (tiSetIt.hasNext()) { + Map.Entry>> entry = tiSetIt.next(); + Iterator> listIt = entry.getValue().iterator(); + while (listIt.hasNext()) { + InstanceContext instanceContext = listIt.next(); + if (allThreadInstances || instanceContext.getBinding().getScope() != PerThread.class) { + listIt.remove(); + if (DisposableSupplier.class.isInstance(instanceContext.getInstance())) { + MultivaluedMap disposeMap = disposableSupplierObjects.get(); + Iterator>> disposeMapIt = disposeMap.entrySet().iterator(); + while (disposeMapIt.hasNext()) { + Map.Entry> disposeMapEntry = disposeMapIt.next(); + if (disposeMapEntry.getKey() == /* identity */ instanceContext.getInstance()) { + Iterator disposeMapEntryIt = disposeMapEntry.getValue().iterator(); + while (disposeMapEntryIt.hasNext()) { + Object disposeInstance = disposeMapEntryIt.next(); + ((DisposableSupplier) instanceContext.getInstance()).dispose(disposeInstance); + disposeMapEntryIt.remove(); + } + } + if (disposeMapEntry.getValue().isEmpty()) { + disposeMapIt.remove(); + } + } + } + instanceContext.destroy(NonInjectionManager.this); + } + if (entry.getValue().isEmpty()) { + tiSetIt.remove(); + } + } + } + disposableSupplierObjects.remove(); } } @@ -207,9 +261,19 @@ private class Types extends TypedInstances { } public NonInjectionManager() { + Binding binding = new AbstractBinder() { + @Override + protected void configure() { + bind(NonInjectionRequestScope.class).to(RequestScope.class).in(Singleton.class); + } + }.getBindings().iterator().next(); + RequestScope scope = new NonInjectionRequestScope(this); + instances.addSingleton(RequestScope.class, scope, binding, null); + types.addSingleton(RequestScope.class, scope, binding, null); } public NonInjectionManager(boolean warning) { + this(); if (warning) { logger.warning(LocalizationMessages.NONINJECT_FALLBACK()); } else { @@ -219,20 +283,21 @@ public NonInjectionManager(boolean warning) { @Override public void completeRegistration() { - instances._addSingleton(InjectionManager.class, this, new InjectionManagerBinding(), null); + instances._addSingleton(InjectionManager.class, this, new InjectionManagerBinding(), null, false); } @Override public void shutdown() { shutdown = true; - - disposableSupplierObjects.forEach((supplier, objects) -> objects.forEach(supplier::dispose)); - disposableSupplierObjects.clear(); - instances.dispose(); types.dispose(); } + void disposeRequestScopedInstances() { + instances.disposeThreadInstances(false); + types.disposeThreadInstances(false); + } + @Override public boolean isShutdown() { return shutdown; @@ -411,12 +476,7 @@ public T create(Class 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()); - } + throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED()); } ClassBindings classBindings = classBindings(createMe); @@ -431,12 +491,7 @@ public T createAndInitialize(Class 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()); - } + throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED()); } ClassBindings classBindings = classBindings(createMe); @@ -535,7 +590,9 @@ public void inject(Object injectMe, String classAnalyzer) { @Override public void preDestroy(Object preDestroyMe) { - Method preDestroy = getAnnotatedMethod(preDestroyMe, PreDestroy.class); + Method preDestroy = Method.class.isInstance(preDestroyMe) + ? (Method) preDestroyMe + : getAnnotatedMethod(preDestroyMe, PreDestroy.class); if (preDestroy != null) { ensureAccessible(preDestroy); try { @@ -567,20 +624,27 @@ private static Method getAnnotatedMethod(Object object, Class T createSupplierProxyIfNeeded(Boolean createProxy, Class iface, Supplier supplier) { + private T createSupplierProxyIfNeeded( + Boolean createProxy, Class iface, Supplier> supplier, TypedInstances typedInstances) { if (createProxy != null && createProxy && iface.isInterface()) { T proxy = (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[]{iface}, new InvocationHandler() { - final SingleRegisterSupplier singleSupplierRegister = new SingleRegisterSupplier<>(supplier); + final Set instances = new HashSet<>(); + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - T t = singleSupplierRegister.get(); + Supplier supplierT = supplier.get(); + T t = supplierT.get(); + if (DisposableSupplier.class.isInstance(supplierT) && !instances.contains(t)) { + MultivaluedMap map = typedInstances.disposableSupplierObjects.get(); + map.add((DisposableSupplier) supplierT, t); + } Object ret = method.invoke(t, args); return ret; } }); return proxy; } else { - return registerDisposableSupplierAndGet(supplier); + return registerDisposableSupplierAndGet(supplier.get(), typedInstances); } } @@ -592,8 +656,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl private class SingleRegisterSupplier { private final LazyValue once; - private SingleRegisterSupplier(Supplier supplier) { - once = Values.lazy((Value) () -> registerDisposableSupplierAndGet(supplier)); + private SingleRegisterSupplier(Supplier supplier, TypedInstances instances) { + once = Values.lazy((Value) () -> registerDisposableSupplierAndGet(supplier, instances)); } T get() { @@ -601,10 +665,10 @@ T get() { } } - private T registerDisposableSupplierAndGet(Supplier supplier) { + private T registerDisposableSupplierAndGet(Supplier supplier, TypedInstances typedInstances) { T instance = supplier.get(); if (DisposableSupplier.class.isInstance(supplier)) { - disposableSupplierObjects.add((DisposableSupplier) supplier, instance); + typedInstances.disposableSupplierObjects.get().add((DisposableSupplier) supplier, instance); } return instance; } @@ -678,7 +742,7 @@ private TypeBindings typeBindings(Type type) { * @param The expected return type for the TYPE. * @param The Type for which a {@link Binding} has been created. */ - private abstract class XBindings { + private abstract class XBindings { protected final List> instanceBindings = new LinkedList<>(); protected final List> supplierInstanceBindings = new LinkedList<>(); @@ -759,12 +823,8 @@ private X _getInstance(InstanceBinding instanceBinding) { 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); - } + X t = registerDisposableSupplierAndGet(supplier, instances); + t = addInstance(type, t, binding); return t; } @@ -828,20 +888,17 @@ List allInstances() { 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) { + protected T _addSingletonInstance(TYPE type, T instance, Binding binding) { return instances.addSingleton(type, instance, binding, instancesQualifiers); } - protected void _addThreadInstance(Object instance, Binding binding) { - instances.addThreadInstance(type, instance, binding, instancesQualifiers); + protected T addInstance(TYPE type, T instance, Binding binding) { + if (Singleton.class.equals(binding.getScope())) { + instance = instances.addSingleton(type, instance, binding, instancesQualifiers); + } else if (_isPerThread(binding.getScope())) { + instances.addThreadInstance(type, instance, binding, instancesQualifiers); + } + return instance; } } @@ -901,28 +958,27 @@ private ServiceHolderImpl _serviceHolder(ClassBinding binding) { } 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); + Supplier> supplierSupplier = () -> { + 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()) || binding.getSupplierScope() == null) { + instances.addThreadInstance(binding.getSupplierClass(), supplier, binding, null); + } } - } + return supplier; + }; - 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); - } + T t = createSupplierProxyIfNeeded(binding.isProxiable(), (Class) type, supplierSupplier, instances); +// t = addInstance(type, t, binding); The supplier here creates instances that ought not to be registered as beans return t; } protected T _createAndStore(ClassBinding binding) { T result = justCreate(binding.getService()); - result = _addInstance(binding.getService(), result, binding); + result = addInstance(binding.getService(), result, binding); return result; } } @@ -935,19 +991,15 @@ private TypeBindings(Type type) { 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); - } + T t = registerDisposableSupplierAndGet(supplier, instances); + t = addInstance(type, t, binding); return t; } @Override protected T _createAndStore(ClassBinding binding) { T result = justCreate(binding.getService()); - result = _addInstance(type, result, binding); + result = addInstance(type, result, binding); return result; } @@ -968,7 +1020,7 @@ public Object get() { return NonInjectionManager.this.getInstance(actualTypeArgument); } } - }); + }, instances); @Override public Object get() { @@ -991,13 +1043,16 @@ private static class InstanceContext { private final T instance; private final Binding binding; private final Annotation[] createdWithQualifiers; + private boolean destroyed = false; - private InstanceContext(T instance, Binding binding, Annotation[] qualifiers) { + private InstanceContext(T instance, Binding binding, Annotation[] qualifiers, boolean destroyed) { this.instance = instance; this.binding = binding; this.createdWithQualifiers = qualifiers; + this.destroyed = destroyed; } + public Binding getBinding() { return binding; } @@ -1006,6 +1061,13 @@ public T getInstance() { return instance; } + public void destroy(NonInjectionManager nonInjectionManager) { + if (!destroyed) { + destroyed = true; + nonInjectionManager.preDestroy(instance); + } + } + @SuppressWarnings("unchecked") static List toInstances(List> instances, Annotation[] qualifiers) { return instances != null @@ -1024,6 +1086,15 @@ private static List> filterInstances(List> : null; } + private static List> merge(List> i1, List> i2) { + if (i1 == null) { + i1 = i2; + } else if (i2 != null) { + i1.addAll(i2); + } + return i1; + } + private boolean hasQualifiers(Annotation[] requested) { if (requested != null) { classLoop: 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 index 258780d53a..b8b23b2409 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -20,14 +20,20 @@ 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 { + + private final NonInjectionManager nonInjectionManager; + + public NonInjectionRequestScope(NonInjectionManager nonInjectionManager) { + this.nonInjectionManager = nonInjectionManager; + } + @Override public org.glassfish.jersey.process.internal.RequestContext createContext() { - return new Instance(); + return new Instance(nonInjectionManager); } /** @@ -35,6 +41,8 @@ public org.glassfish.jersey.process.internal.RequestContext createContext() { */ public static final class Instance implements org.glassfish.jersey.process.internal.RequestContext { + private final NonInjectionManager injectionManager; + private static final ExtendedLogger logger = new ExtendedLogger(Logger.getLogger(Instance.class.getName()), Level.FINEST); /* @@ -48,10 +56,11 @@ public static final class Instance implements org.glassfish.jersey.process.inter /** * Holds the number of snapshots of this scope. */ - private final AtomicInteger referenceCounter; + private int referenceCounter; - private Instance() { - this.referenceCounter = new AtomicInteger(1); + private Instance(NonInjectionManager injectionManager) { + this.injectionManager = injectionManager; + this.referenceCounter = 1; } /** @@ -65,7 +74,7 @@ private Instance() { @Override public NonInjectionRequestScope.Instance getReference() { // TODO: replace counter with a phantom reference + reference queue-based solution - referenceCounter.incrementAndGet(); + referenceCounter++; return this; } @@ -77,7 +86,9 @@ public NonInjectionRequestScope.Instance getReference() { */ @Override public void release() { - referenceCounter.decrementAndGet(); + if (0 == --referenceCounter) { + injectionManager.disposeRequestScopedInstances(); + } } @Override diff --git a/tests/e2e-inject/non-inject/pom.xml b/tests/e2e-inject/non-inject/pom.xml new file mode 100644 index 0000000000..0e53884884 --- /dev/null +++ b/tests/e2e-inject/non-inject/pom.xml @@ -0,0 +1,57 @@ + + + + + + e2e-inject + org.glassfish.jersey.tests + 2.46-SNAPSHOT + + 4.0.0 + + e2e-inject-noninject + + + + org.glassfish.jersey.incubator + jersey-injectless-client + 2.46-SNAPSHOT + + + org.glassfish.jersey.core + jersey-client + test + + + + org.junit.jupiter + junit-jupiter + test + + + org.hamcrest + hamcrest + test + + + + + diff --git a/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/DisposableSuplierTest.java b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/DisposableSuplierTest.java new file mode 100644 index 0000000000..6ccaa40d04 --- /dev/null +++ b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/DisposableSuplierTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available 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.inject.noninject; + +import org.glassfish.jersey.internal.inject.AbstractBinder; +import org.glassfish.jersey.internal.inject.DisposableSupplier; +import org.glassfish.jersey.process.internal.RequestScoped; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +public class DisposableSuplierTest { + private static final AtomicInteger disposeCounter = new AtomicInteger(0); + private static final String HOST = "http://somewhere.anywhere"; + + public interface ResponseObject { + Response getResponse(); + } + + private static class TestDisposableSupplier implements DisposableSupplier { + AtomicInteger counter = new AtomicInteger(300); + + @Override + public void dispose(ResponseObject instance) { + disposeCounter.incrementAndGet(); + } + + @Override + public ResponseObject get() { + return new ResponseObject() { + + @Override + public Response getResponse() { + return Response.ok().build(); + } + }; + } + } + + private static class DisposableSupplierInjectingFilter implements ClientRequestFilter { + private final ResponseObject responseSupplier; + + @Inject + private DisposableSupplierInjectingFilter(ResponseObject responseSupplier) { + this.responseSupplier = responseSupplier; + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + requestContext.abortWith(responseSupplier.getResponse()); + } + } + + @Test + public void testDisposeCount() { + disposeCounter.set(0); + int CNT = 4; + Client client = ClientBuilder.newClient() + .register(new AbstractBinder() { + @Override + protected void configure() { + bindFactory(TestDisposableSupplier.class).to(ResponseObject.class) + .proxy(true).proxyForSameScope(false).in(RequestScoped.class); + } + }).register(DisposableSupplierInjectingFilter.class); + + for (int i = 0; i != CNT; i++) { + try (Response response = client.target(HOST).request().get()) { + MatcherAssert.assertThat(response.getStatus(), Matchers.is(200)); + } + } + + MatcherAssert.assertThat(disposeCounter.get(), Matchers.is(CNT)); + } +} diff --git a/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/InstanceListSizeTest.java b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/InstanceListSizeTest.java new file mode 100644 index 0000000000..e526007de6 --- /dev/null +++ b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/InstanceListSizeTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available 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.inject.noninject; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.client.innate.inject.NonInjectionManager; +import org.glassfish.jersey.internal.inject.InjectionManager; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class InstanceListSizeTest { + + private static final String TEST_HEADER = "TEST"; + private static final String HOST = "https://anywhere.any"; + + @Test + public void leakTest() throws ExecutionException, InterruptedException { + int CNT = 100; + Client client = ClientBuilder.newClient(); + client.register(InjectionManagerGrowChecker.class); + Response response = client.target(HOST).request().header(TEST_HEADER, "0").get(); + int status = response.getStatus(); + response.close(); + //Create instance in NonInjectionManager$TypedInstances.threadPredestroyables + + for (int i = 0; i <= CNT; i++) { + final String header = String.valueOf(i + 1); + try (Response r = client.target(HOST).request() + .header(TEST_HEADER, header) + .async() + .post(Entity.text("text")).get()) { + int stat = r.getStatus(); + MatcherAssert.assertThat( + "NonInjectionManager#Types#disposableSupplierObjects is increasing", stat, Matchers.is(202)); + } + } + //Create 10 instance in NonInjectionManager$TypedInstances.threadPredestroyables + + for (int i = 0; i <= CNT; i++) { + final String header = String.valueOf(i + CNT + 2); + final Object text = CompletableFuture.supplyAsync(() -> { + Response test = client.target(HOST).request() + .header("TEST", header) + .post(Entity.text("text")); + int stat = test.getStatus(); + test.close(); + MatcherAssert.assertThat( + "NonInjectionManager#Types#disposableSupplierObjects is increasing", stat, Matchers.is(202)); + + return null; + }).join(); + } + //Create 10 instance in NonInjectionManager$TypedInstances.threadPredestroyables + + response = client.target(HOST).request().header(TEST_HEADER, 2 * CNT + 3).get(); + status = response.getStatus(); + MatcherAssert.assertThat(status, Matchers.is(202)); + response.close(); + } + + private static class InjectionManagerGrowChecker implements ClientRequestFilter { + private boolean first = true; + private int disposableSize = 0; + private int threadInstancesSize = 0; + private HttpHeaders headers; + private int headerCnt = 0; + + @Inject + public InjectionManagerGrowChecker(HttpHeaders headers) { + this.headers = headers; + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + Response.Status status = Response.Status.ACCEPTED; + if (headerCnt++ != Integer.parseInt(headers.getHeaderString("TEST"))) { + status = Response.Status.BAD_REQUEST; + } + + NonInjectionManager nonInjectionManager = getInjectionManager(requestContext); + Object types = getDeclaredField(nonInjectionManager, "types"); + Object instances = getDeclaredField(nonInjectionManager, "instances"); + if (first) { + first = false; + disposableSize = getThreadInstances(types, "disposableSupplierObjects") + + getThreadInstances(instances, "disposableSupplierObjects"); + threadInstancesSize = getThreadInstances(types, "threadInstances") + + getThreadInstances(instances, "threadInstances"); + } else { + int newPredestroyableSize = getThreadInstances(types, "disposableSupplierObjects") + + getThreadInstances(instances, "disposableSupplierObjects"); + if (newPredestroyableSize > disposableSize + 1 /* a new service to get disposed */) { + status = Response.Status.EXPECTATION_FAILED; + } + int newThreadInstances = getThreadInstances(types, "threadInstances") + + getThreadInstances(instances, "threadInstances"); + if (newThreadInstances > threadInstancesSize) { + status = Response.Status.PRECONDITION_FAILED; + } + } + + requestContext.abortWith(Response.status(status).build()); + } + } + + private static NonInjectionManager getInjectionManager(ClientRequestContext context) { + ClientRequest request = ((ClientRequest) context); + try { + Method clientConfigMethod = ClientRequest.class.getDeclaredMethod("getClientConfig"); + clientConfigMethod.setAccessible(true); + ClientConfig clientConfig = (ClientConfig) clientConfigMethod.invoke(request); + + Method runtimeMethod = ClientConfig.class.getDeclaredMethod("getRuntime"); + runtimeMethod.setAccessible(true); + Object clientRuntime = runtimeMethod.invoke(clientConfig); + Class clientRuntimeClass = clientRuntime.getClass(); + + Method injectionManagerMethod = clientRuntimeClass.getDeclaredMethod("getInjectionManager"); + injectionManagerMethod.setAccessible(true); + InjectionManager injectionManager = (InjectionManager) injectionManagerMethod.invoke(clientRuntime); + return (NonInjectionManager) injectionManager; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static Object getDeclaredField(NonInjectionManager nonInjectionManager, String name) { + try { + Field typesField = NonInjectionManager.class.getDeclaredField(name); + typesField.setAccessible(true); + return typesField.get(nonInjectionManager); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static int getThreadInstances(Object typedInstances, String threadLocalName) { + try { + Field threadLocalField = + typedInstances.getClass().getSuperclass().getDeclaredField(threadLocalName); + threadLocalField.setAccessible(true); + ThreadLocal> threadLocal = + (ThreadLocal>) threadLocalField.get(typedInstances); + MultivaluedMap map = threadLocal.get(); + if (map == null) { + return 0; + } else { + int cnt = 0; + Set>> set = map.entrySet(); + for (Map.Entry> entry : map.entrySet()) { + cnt += entry.getValue().size(); + } + return cnt; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + +} diff --git a/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/PreDestroyTest.java b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/PreDestroyTest.java new file mode 100644 index 0000000000..752fd243c2 --- /dev/null +++ b/tests/e2e-inject/non-inject/src/test/org/glassfish/jersey/tests/e2e/inject/noninject/PreDestroyTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available 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.inject.noninject; + +import org.glassfish.jersey.internal.inject.AbstractBinder; +import org.glassfish.jersey.internal.inject.DisposableSupplier; +import org.glassfish.jersey.process.internal.RequestScoped; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +public class PreDestroyTest { + private static final AtomicInteger disposeCounter = new AtomicInteger(0); + private static final String HOST = "http://somewhere.anywhere"; + + public interface ResponseObject { + Response getResponse(); + } + + public static class ResponseObjectImpl implements ResponseObject { + + public ResponseObjectImpl() { + + } + + @PreDestroy + public void preDestroy() { + disposeCounter.incrementAndGet(); + } + + @Override + public Response getResponse() { + return Response.ok().build(); + } + } + + private static class PreDestroyInjectingFilter implements ClientRequestFilter { + private final ResponseObject responseSupplier; + + @Inject + private PreDestroyInjectingFilter(ResponseObject responseSupplier) { + this.responseSupplier = responseSupplier; + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + requestContext.abortWith(responseSupplier.getResponse()); + } + } + + @Test + public void testPreDestroyCount() { + disposeCounter.set(0); + int CNT = 4; + Client client = ClientBuilder.newClient() + .register(new AbstractBinder() { + @Override + protected void configure() { + bind(ResponseObjectImpl.class).to(ResponseObject.class) + .proxy(true).proxyForSameScope(false).in(RequestScoped.class); + } + }).register(PreDestroyInjectingFilter.class); + + for (int i = 0; i != CNT; i++) { + try (Response response = client.target(HOST).request().get()) { + MatcherAssert.assertThat(response.getStatus(), Matchers.is(200)); + } + } + + MatcherAssert.assertThat(disposeCounter.get(), Matchers.is(1)); + } +} diff --git a/tests/e2e-inject/pom.xml b/tests/e2e-inject/pom.xml index 81acfcbcd2..8c67861390 100644 --- a/tests/e2e-inject/pom.xml +++ b/tests/e2e-inject/pom.xml @@ -36,5 +36,6 @@ cdi2-se cdi-inject-weld hk2 + non-inject From c59a1bce2725f8881a45ce2ab819a47c4672da50 Mon Sep 17 00:00:00 2001 From: jansupol Date: Fri, 13 Dec 2024 10:47:12 +0100 Subject: [PATCH 4/7] Build & run with JDK 24 Signed-off-by: jansupol --- core-common/pom.xml | 20 ++++++++++++------ ...nalPropertiesConfigurationFactoryTest.java | 18 ++++++++++++---- core-server/pom.xml | 3 +++ .../inject/ParamConverterDateTest.java | 8 +++---- examples/groovy/pom.xml | 6 ++++-- examples/osgi-helloworld-webapp/pom.xml | 21 ++++++++++++------- examples/pom.xml | 12 ++++++++++- incubator/pom.xml | 13 +++++++++++- pom.xml | 4 ++-- .../microprofile/rest-client/pom.xml | 9 ++++++++ tests/pom.xml | 10 ++++++++- 11 files changed, 95 insertions(+), 29 deletions(-) diff --git a/core-common/pom.xml b/core-common/pom.xml index 424315b1be..27d61f34b7 100644 --- a/core-common/pom.xml +++ b/core-common/pom.xml @@ -848,6 +848,9 @@ securityOff + + [24,) + @@ -856,12 +859,17 @@ org.apache.maven.plugins maven-surefire-plugin - - - **/SecurityManagerConfiguredTest.java - **/ReflectionHelperTest.java - - + + + default-test + + + **/SecurityManagerConfiguredTest.java + **/ReflectionHelperTest.java + + + + diff --git a/core-common/src/test/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactoryTest.java b/core-common/src/test/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactoryTest.java index 6f1f7b163e..ff3e9ef9bd 100644 --- a/core-common/src/test/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactoryTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -30,11 +30,14 @@ public class ExternalPropertiesConfigurationFactoryTest { + private static boolean isSecurityManager; + /** * Predefine some properties to be read from config */ @BeforeAll public static void setUp() { + isSecurityManager = System.getSecurityManager() != null; System.setProperty(CommonProperties.ALLOW_SYSTEM_PROPERTIES_PROVIDER, Boolean.TRUE.toString()); System.setProperty("jersey.config.server.provider.scanning.recursive", "PASSED"); @@ -53,7 +56,11 @@ public static void tearDown() { public void readSystemPropertiesTest() { final Object result = readExternalPropertiesMap().get("jersey.config.server.provider.scanning.recursive"); - Assertions.assertNull(result); + if (isSecurityManager) { + Assertions.assertNull(result); + } else { + Assertions.assertEquals("PASSED", result); + } Assertions.assertEquals(Boolean.TRUE, getConfig().isProperty(CommonProperties.JSON_PROCESSING_FEATURE_DISABLE)); Assertions.assertEquals(Boolean.TRUE, @@ -81,8 +88,11 @@ 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"); - Assertions.assertNull(result); - Assertions.assertNull(readExternalPropertiesMap().get("org.jersey.microprofile.config.added")); + final Object resultAdded = readExternalPropertiesMap().get("org.jersey.microprofile.config.added"); + if (isSecurityManager) { + Assertions.assertNull(result); + Assertions.assertNull(resultAdded); + } } } diff --git a/core-server/pom.xml b/core-server/pom.xml index b3373059ac..3e0a8fd2a2 100644 --- a/core-server/pom.xml +++ b/core-server/pom.xml @@ -270,6 +270,9 @@ securityOff + + [24,) + diff --git a/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterDateTest.java b/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterDateTest.java index fd841f1a7b..d6200c92e3 100644 --- a/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterDateTest.java +++ b/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/ParamConverterDateTest.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 @@ -40,6 +40,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class ParamConverterDateTest extends AbstractTest { + private final String format = "EEE MMM dd HH:mm:ss Z yyyy"; + private final SimpleDateFormat formatter = new SimpleDateFormat(format, new Locale("US")); @Path("/") public static class DateResource { @@ -55,7 +57,7 @@ public String doGet(@QueryParam("d") final Date d) { public void testDateResource() throws ExecutionException, InterruptedException { initiateWebApplication(getBinder(), ParamConverterDateTest.DateResource.class); final ContainerResponse responseContext = getResponseContext(UriBuilder.fromPath("/") - .queryParam("d", new Date()).build().toString()); + .queryParam("d", formatter.format(new Date())).build().toString()); assertEquals(200, responseContext.getStatus()); } @@ -80,8 +82,6 @@ public T fromString(final String value) { ); } try { - final String format = "EEE MMM dd HH:mm:ss Z yyyy"; - final SimpleDateFormat formatter = new SimpleDateFormat(format, new Locale("US")); return rawType.cast(formatter.parse(value)); } catch (final ParseException ex) { throw new ExtractorException(ex); diff --git a/examples/groovy/pom.xml b/examples/groovy/pom.xml index 320bf243d6..84fd62f0ed 100644 --- a/examples/groovy/pom.xml +++ b/examples/groovy/pom.xml @@ -83,7 +83,7 @@ org.codehaus.gmavenplus gmavenplus-plugin - 3.0.0 + 4.0.1 1 @@ -99,10 +99,12 @@ removeTestStubs groovydoc + + 11 + - org.apache.maven.plugins maven-antrun-plugin diff --git a/examples/osgi-helloworld-webapp/pom.xml b/examples/osgi-helloworld-webapp/pom.xml index 5c46db93ea..2079a95a72 100644 --- a/examples/osgi-helloworld-webapp/pom.xml +++ b/examples/osgi-helloworld-webapp/pom.xml @@ -25,15 +25,20 @@ jersey-examples-osgi-helloworld-webapp pom - - war-bundle - functional-test - lib-bundle - additional-bundle - alternate-version-bundle - - + + securityOn + + [1.8,24) + + + war-bundle + functional-test + lib-bundle + additional-bundle + alternate-version-bundle + + pre-release diff --git a/examples/pom.xml b/examples/pom.xml index 33243b2c08..ac6b963ede 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -70,7 +70,6 @@ freemarker-webapp - groovy helloworld helloworld-benchmark helloworld-cdi2-se @@ -281,5 +280,16 @@ + + + GROOVY-EXAMPLE + + [11,) + + + groovy + + + diff --git a/incubator/pom.xml b/incubator/pom.xml index 86d124c376..ada684372b 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -39,7 +39,6 @@ cdi-inject-weld declarative-linking gae-integration - html-json injectless-client kryo open-tracing @@ -53,4 +52,16 @@ test + + + + HTML-JSON-FOR-PRE-JDK24 + + [1.8, 24) + + + html-json + + + diff --git a/pom.xml b/pom.xml index bd4efe38b3..79644cecf6 100644 --- a/pom.xml +++ b/pom.xml @@ -2183,7 +2183,7 @@ 3.3.1 3.6.0 3.3.1 - 3.3.1 + 3.5.2 3.4.0 2.11.0 1.1.0 @@ -2209,7 +2209,7 @@ 1.7 2.3.33 2.0.29 - 4.0.22 + 5.0.0-alpha-11 2.11.0 33.3.0-jre 3.0 diff --git a/tests/integration/microprofile/rest-client/pom.xml b/tests/integration/microprofile/rest-client/pom.xml index 3f374e96cb..535e3b999c 100644 --- a/tests/integration/microprofile/rest-client/pom.xml +++ b/tests/integration/microprofile/rest-client/pom.xml @@ -218,6 +218,15 @@ + + securityOff + + [24,) + + + + + diff --git a/tests/pom.xml b/tests/pom.xml index 9122b05cb6..1ea4733d04 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -47,7 +47,6 @@ e2e-testng e2e-tls integration - jmockit mem-leaks osgi stress @@ -117,5 +116,14 @@ release-test + + JMOCKIT-MODULE-FOR-PRE-JDK24 + + [1.8,24) + + + jmockit + + From c312e20672d9c07e6b036640874bc89b7d78f8ec Mon Sep 17 00:00:00 2001 From: jansupol <15908245+jansupol@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:00:41 +0100 Subject: [PATCH 5/7] Allow to configure Jackson's JaxRSFeature on Jersey DefaultJacksonJaxbJsonProvider (#5816) Signed-off-by: jansupol --- .../jersey/jackson/JacksonFeature.java | 20 +++- .../jackson/JaxRSFeatureObjectMapper.java | 67 +++++++++++++ .../internal/AbstractObjectMapper.java | 29 ++++++ .../DefaultJacksonJaxbJsonProvider.java | 19 +++- .../jackson/internal/JaxrsFeatureBag.java | 58 +++++++++++ .../jackson/internal/JaxRSFeatureTest.java | 96 +++++++++++++++++++ 6 files changed, 284 insertions(+), 5 deletions(-) create mode 100644 media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JaxRSFeatureObjectMapper.java create mode 100644 media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/AbstractObjectMapper.java create mode 100644 media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/JaxrsFeatureBag.java create mode 100644 media/json-jackson/src/test/java/org/glassfish/jersey/jackson/internal/JaxRSFeatureTest.java diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java index e69801e03f..c152784301 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023 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 @@ -29,8 +29,10 @@ import org.glassfish.jersey.jackson.internal.DefaultJacksonJaxbJsonProvider; import org.glassfish.jersey.jackson.internal.FilteringJacksonJaxbJsonProvider; import org.glassfish.jersey.jackson.internal.JacksonFilteringFeature; +import org.glassfish.jersey.jackson.internal.JaxrsFeatureBag; import org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.JsonMappingExceptionMapper; import org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.JsonParseExceptionMapper; +import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature; import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider; import org.glassfish.jersey.message.MessageProperties; import org.glassfish.jersey.message.filtering.EntityFilteringFeature; @@ -41,7 +43,7 @@ * @author Stepan Kopriva * @author Michal Gajdos */ -public class JacksonFeature implements Feature { +public class JacksonFeature extends JaxrsFeatureBag implements Feature { /** * Define whether to use Jackson's exception mappers ore not @@ -100,6 +102,16 @@ public JacksonFeature maxStringLength(int maxStringLength) { return this; } + /** + * Register {@link JaxRSFeature} with the Jackson providers. + * @param feature the {@link JaxRSFeature} to be enabled or disabled. + * @param state {@code true} for enabling the feature, {@code false} for disabling. + * @return JacksonFeature with {@link JaxRSFeature} registered to be set on a created Jackson provider. + */ + public JacksonFeature jaxrsFeature(JaxRSFeature feature, boolean state) { + return super.jaxrsFeature(feature, state); + } + private static final String JSON_FEATURE = JacksonFeature.class.getSimpleName(); @Override @@ -138,6 +150,10 @@ public boolean configure(final FeatureContext context) { context.property(MessageProperties.JSON_MAX_STRING_LENGTH, maxStringLength); } + if (hasJaxrsFeature()) { + context.property(JaxrsFeatureBag.JAXRS_FEATURE, this); + } + return true; } } diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JaxRSFeatureObjectMapper.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JaxRSFeatureObjectMapper.java new file mode 100644 index 0000000000..d759e56243 --- /dev/null +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JaxRSFeatureObjectMapper.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available 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.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.glassfish.jersey.jackson.internal.AbstractObjectMapper; +import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature; + + +/** + * The Jackson {@link ObjectMapper} supporting {@link JaxRSFeature}s. + */ +public class JaxRSFeatureObjectMapper extends AbstractObjectMapper { + + public JaxRSFeatureObjectMapper() { + super(); + } + + /** + * Method for changing state of an on/off {@link org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature} + * features. + */ + public ObjectMapper configure(JaxRSFeature f, boolean state) { + jaxrsFeatureBag.jaxrsFeature(f, state); + return this; + } + + /** + * Method for enabling specified {@link org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature}s + * for parser instances this object mapper creates. + */ + public ObjectMapper enable(JaxRSFeature... features) { + if (features != null) { + for (JaxRSFeature f : features) { + jaxrsFeatureBag.jaxrsFeature(f, true); + } + } + return this; + } + + /** + * Method for disabling specified {@link org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature}s + * for parser instances this object mapper creates. + */ + public ObjectMapper disable(JaxRSFeature... features) { + if (features != null) { + for (JaxRSFeature f : features) { + jaxrsFeatureBag.jaxrsFeature(f, false); + } + } + return this; + } +} diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/AbstractObjectMapper.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/AbstractObjectMapper.java new file mode 100644 index 0000000000..2f331cfe42 --- /dev/null +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/AbstractObjectMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available 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.jackson.internal; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Internal ObjectMapper with {@link JaxrsFeatureBag}. + */ +public abstract class AbstractObjectMapper extends ObjectMapper { + protected AbstractObjectMapper() { + + } + protected JaxrsFeatureBag jaxrsFeatureBag = new JaxrsFeatureBag(); +} diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java index bea071483a..29951abd94 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.StreamReadConstraints; -import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.core.json.PackageVersion; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.Module; @@ -41,6 +40,7 @@ import javax.inject.Singleton; import javax.ws.rs.core.Configuration; import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.Providers; /** @@ -53,8 +53,7 @@ public class DefaultJacksonJaxbJsonProvider extends JacksonJaxbJsonProvider { @Inject public DefaultJacksonJaxbJsonProvider(@Context Providers providers, @Context Configuration config) { - this.commonConfig = config; - _providers = providers; + this(providers, config, DEFAULT_ANNOTATIONS); } //do not register JaxbAnnotationModule because it brakes default annotations processing @@ -64,6 +63,20 @@ public DefaultJacksonJaxbJsonProvider(Providers providers, Configuration config, super(annotationsToUse); this.commonConfig = config; _providers = providers; + + Object jaxrsFeatureBag = config.getProperty(JaxrsFeatureBag.JAXRS_FEATURE); + if (jaxrsFeatureBag != null && (JaxrsFeatureBag.class.isInstance(jaxrsFeatureBag))) { + ((JaxrsFeatureBag) jaxrsFeatureBag).configureJaxrsFeatures(this); + } + } + + @Override + protected ObjectMapper _locateMapperViaProvider(Class type, MediaType mediaType) { + ObjectMapper mapper = super._locateMapperViaProvider(type, mediaType); + if (AbstractObjectMapper.class.isInstance(mapper)) { + ((AbstractObjectMapper) mapper).jaxrsFeatureBag.configureJaxrsFeatures(this); + } + return mapper; } @Override diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/JaxrsFeatureBag.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/JaxrsFeatureBag.java new file mode 100644 index 0000000000..4711b56808 --- /dev/null +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/JaxrsFeatureBag.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available 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.jackson.internal; + +import org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.ProviderBase; +import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Internal holder class for {@link JaxRSFeature} settings and their values. + */ +public class JaxrsFeatureBag { + protected static final String JAXRS_FEATURE = "jersey.config.jackson.jaxrs.feature"; + + private static class JaxRSFeatureState { + /* package */ final JaxRSFeature feature; + /* package */ final boolean state; + public JaxRSFeatureState(JaxRSFeature feature, boolean state) { + this.feature = feature; + this.state = state; + } + } + + private Optional> jaxRSFeature = Optional.empty(); + + public T jaxrsFeature(JaxRSFeature feature, boolean state) { + if (!jaxRSFeature.isPresent()) { + jaxRSFeature = Optional.of(new ArrayList<>()); + } + jaxRSFeature.ifPresent(list -> list.add(new JaxrsFeatureBag.JaxRSFeatureState(feature, state))); + return (T) this; + } + + protected boolean hasJaxrsFeature() { + return jaxRSFeature.isPresent(); + } + + /* package */ void configureJaxrsFeatures(ProviderBase providerBase) { + jaxRSFeature.ifPresent(list -> list.stream().forEach(state -> providerBase.configure(state.feature, state.state))); + } +} diff --git a/media/json-jackson/src/test/java/org/glassfish/jersey/jackson/internal/JaxRSFeatureTest.java b/media/json-jackson/src/test/java/org/glassfish/jersey/jackson/internal/JaxRSFeatureTest.java new file mode 100644 index 0000000000..431f8437a7 --- /dev/null +++ b/media/json-jackson/src/test/java/org/glassfish/jersey/jackson/internal/JaxRSFeatureTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available 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.jackson.internal; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.jersey.jackson.JaxRSFeatureObjectMapper; +import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.Providers; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class JaxRSFeatureTest { + @Test + public void testJaxrsFeatureOnJacksonFeature() { + Client client = ClientBuilder.newClient() + .register(new JacksonFeature().jaxrsFeature(JaxRSFeature.READ_FULL_STREAM, false)) + .register(JaxrsFeatureFilter.class); + + try (Response r = client.target("http://xxx.yyy").request().get()) { + MatcherAssert.assertThat(r.getStatus(), Matchers.is(200)); + } + } + + @Test + public void testJaxrsFeatureOnContextResolver() { + Client client = ClientBuilder.newClient() + .register(JacksonFeature.class) + .register(JaxrsFetureContextResolver.class) + .register(JaxrsFeatureFilter.class); + + try (Response r = client.target("http://xxx.yyy").request().get()) { + MatcherAssert.assertThat(r.getStatus(), Matchers.is(200)); + } + } + + + public static class JaxrsFeatureFilter implements ClientRequestFilter { + private final DefaultJacksonJaxbJsonProvider jacksonProvider; + @Inject + public JaxrsFeatureFilter(Providers allProviders) { + jacksonProvider = (DefaultJacksonJaxbJsonProvider) + allProviders.getMessageBodyReader(Object.class, Object.class, null, MediaType.APPLICATION_JSON_TYPE); + try { + jacksonProvider.readFrom(Object.class, Object.class, null, MediaType.APPLICATION_JSON_TYPE, null, + new ByteArrayInputStream("{}".getBytes())); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + Response.Status status = jacksonProvider.isEnabled(JaxRSFeature.READ_FULL_STREAM) + ? Response.Status.FORBIDDEN + : Response.Status.OK; + requestContext.abortWith(Response.status(status).build()); + } + } + + public static class JaxrsFetureContextResolver implements ContextResolver { + + @Override + public ObjectMapper getContext(Class type) { + JaxRSFeatureObjectMapper objectMapper = new JaxRSFeatureObjectMapper(); + objectMapper.disable(JaxRSFeature.READ_FULL_STREAM); + return objectMapper; + } + } +} From 6d2b14296c5223d71ab5592dfe1968d09103fff2 Mon Sep 17 00:00:00 2001 From: Maxim Nesen Date: Wed, 18 Dec 2024 12:50:24 +0100 Subject: [PATCH 6/7] Examples bundle - JDK 24 adjust Signed-off-by: Maxim Nesen --- bundles/examples/pom.xml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/bundles/examples/pom.xml b/bundles/examples/pom.xml index e39812ca17..48d44ac4c4 100644 --- a/bundles/examples/pom.xml +++ b/bundles/examples/pom.xml @@ -196,13 +196,6 @@ gf-project-src zip - - org.glassfish.jersey.examples - groovy - ${project.version} - project-src - zip - org.glassfish.jersey.examples helloworld @@ -736,4 +729,22 @@ + + + groovy_jdk_11 + + [11,) + + + + org.glassfish.jersey.examples + groovy + ${project.version} + project-src + zip + + + + + From f16b6535f080c8d28d895f7673c983481140abd0 Mon Sep 17 00:00:00 2001 From: Maxim Nesen Date: Wed, 18 Dec 2024 06:59:40 +0100 Subject: [PATCH 7/7] after merge Signed-off-by: Maxim Nesen --- examples/groovy/pom.xml | 8 ++++ pom.xml | 3 +- .../container-runner-maven-plugin/pom.xml | 40 ++++++++++++++++++- tests/e2e-inject/non-inject/pom.xml | 2 +- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/examples/groovy/pom.xml b/examples/groovy/pom.xml index 0ee145f0c9..a12fae22b2 100644 --- a/examples/groovy/pom.xml +++ b/examples/groovy/pom.xml @@ -42,6 +42,14 @@ org.junit.jupiter junit-jupiter-api + + org.junit.jupiter + junit-jupiter-engine + + + org.junit.platform + junit-platform-commons + org.ow2.asm asm diff --git a/pom.xml b/pom.xml index 297dae22c7..bc1397ead7 100644 --- a/pom.xml +++ b/pom.xml @@ -2197,6 +2197,7 @@ 2.3.33 2.0.29 5.0.0-alpha-11 + 4.0.24 2.11.0 @@ -2236,7 +2237,7 @@ 1.37 1.49 4.13.2 - 5.11.0 + 5.11.4 5.10.3 1.11.0 4.0.3 diff --git a/test-framework/maven/container-runner-maven-plugin/pom.xml b/test-framework/maven/container-runner-maven-plugin/pom.xml index f2f9cc05df..21bbfd3205 100644 --- a/test-framework/maven/container-runner-maven-plugin/pom.xml +++ b/test-framework/maven/container-runner-maven-plugin/pom.xml @@ -36,7 +36,6 @@ - 3.7.0 3.0.8-01 3.9.2 @@ -305,5 +304,44 @@ + + jdk_8 + + 1.8 + + + ${groovy.jdk8.version} + + + + org.apache.groovy + groovy-all + pom + ${groovy.version} + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + org.junit.jupiter + junit-jupiter-api + + + org.junit.jupiter + junit-jupiter-engine + + + org.junit.platform + junit-platform-commons + + + + + diff --git a/tests/e2e-inject/non-inject/pom.xml b/tests/e2e-inject/non-inject/pom.xml index 39a348ecd3..f1f1082233 100644 --- a/tests/e2e-inject/non-inject/pom.xml +++ b/tests/e2e-inject/non-inject/pom.xml @@ -33,7 +33,7 @@ org.glassfish.jersey.incubator jersey-injectless-client - 2.46-SNAPSHOT + ${project.version} org.glassfish.jersey.core