diff --git a/NOTICE.md b/NOTICE.md index 877100d02f..c2d817bccf 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -70,7 +70,7 @@ Javassist Version 3.30.2-GA * Project: http://www.javassist.org/ * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. -Jackson JAX-RS Providers Version 2.17.0 +Jackson JAX-RS Providers Version 2.17.1 * License: Apache License, 2.0 * Project: https://github.com/FasterXML/jackson-jaxrs-providers * Copyright: (c) 2009-2024 FasterXML, LLC. All rights reserved unless otherwise indicated. diff --git a/archetypes/jersey-example-java8-webapp/src/main/resources/archetype-resources/pom.xml b/archetypes/jersey-example-java8-webapp/src/main/resources/archetype-resources/pom.xml index 45513c7fe7..9f6fb8d961 100644 --- a/archetypes/jersey-example-java8-webapp/src/main/resources/archetype-resources/pom.xml +++ b/archetypes/jersey-example-java8-webapp/src/main/resources/archetype-resources/pom.xml @@ -1,7 +1,7 @@ - - - jersey-quickstart-grizzly2 - - src/main/java/Main.java - src/main/java/MyResource.java - - - src/test/java/MyResourceTest.java - - diff --git a/archetypes/jersey-quickstart-grizzly2/src/main/resources/META-INF/maven/archetype-metadata.xml b/archetypes/jersey-quickstart-grizzly2/src/main/resources/META-INF/maven/archetype-metadata.xml new file mode 100644 index 0000000000..54380e52c4 --- /dev/null +++ b/archetypes/jersey-quickstart-grizzly2/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -0,0 +1,42 @@ + + + + + + + src/main/java + + **/* + + + + + src/test/java + + **/* + + + + \ No newline at end of file diff --git a/archetypes/jersey-quickstart-grizzly2/src/main/resources/archetype-resources/pom.xml b/archetypes/jersey-quickstart-grizzly2/src/main/resources/archetype-resources/pom.xml index 779f0802af..087417f001 100644 --- a/archetypes/jersey-quickstart-grizzly2/src/main/resources/archetype-resources/pom.xml +++ b/archetypes/jersey-quickstart-grizzly2/src/main/resources/archetype-resources/pom.xml @@ -40,7 +40,7 @@ org.junit.jupiter junit-jupiter - \${junit-jupiter.version} + \${junit5.version} test @@ -50,7 +50,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + ${compiler.mvn.plugin.version} true 1.8 @@ -60,7 +60,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + ${exec.mvn.plugin.version} @@ -83,8 +83,7 @@ ${project.version} - 5.10.2 + ${junit5.version} UTF-8 - 3.2.5 diff --git a/archetypes/jersey-quickstart-webapp/pom.xml b/archetypes/jersey-quickstart-webapp/pom.xml index 25d4ebed9f..1b5e2d3893 100644 --- a/archetypes/jersey-quickstart-webapp/pom.xml +++ b/archetypes/jersey-quickstart-webapp/pom.xml @@ -36,7 +36,7 @@ org.apache.maven.plugins maven-resources-plugin - 2.5 + ${resources.mvn.plugin.version} \ diff --git a/archetypes/jersey-quickstart-webapp/src/main/resources/META-INF/archetype.xml b/archetypes/jersey-quickstart-webapp/src/main/resources/META-INF/archetype.xml deleted file mode 100644 index cea1eea5e9..0000000000 --- a/archetypes/jersey-quickstart-webapp/src/main/resources/META-INF/archetype.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - jersey-quickstart-webapp - - src/main/java/MyResource.java - - - src/main/webapp/index.jsp - src/main/webapp/WEB-INF/web.xml - - diff --git a/archetypes/jersey-quickstart-webapp/src/main/resources/META-INF/maven/archetype-metadata.xml b/archetypes/jersey-quickstart-webapp/src/main/resources/META-INF/maven/archetype-metadata.xml new file mode 100644 index 0000000000..bea65b3530 --- /dev/null +++ b/archetypes/jersey-quickstart-webapp/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -0,0 +1,42 @@ + + + + + + + src/main/java + + + src/main/webapp + + **/* + + + + src/test/java + + **/* + + + + \ No newline at end of file diff --git a/archetypes/jersey-quickstart-webapp/src/main/resources/archetype-resources/pom.xml b/archetypes/jersey-quickstart-webapp/src/main/resources/archetype-resources/pom.xml index fc70d51357..341fb67664 100644 --- a/archetypes/jersey-quickstart-webapp/src/main/resources/archetype-resources/pom.xml +++ b/archetypes/jersey-quickstart-webapp/src/main/resources/archetype-resources/pom.xml @@ -15,7 +15,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + ${compiler.mvn.plugin.version} true 1.8 @@ -63,6 +63,5 @@ ${project.version} UTF-8 - 3.4.0 diff --git a/bundles/jaxrs-ri/pom.xml b/bundles/jaxrs-ri/pom.xml index 282151f2dc..844d89efde 100644 --- a/bundles/jaxrs-ri/pom.xml +++ b/bundles/jaxrs-ri/pom.xml @@ -423,7 +423,7 @@ org.codehaus.mojo wagon-maven-plugin - 1.0-beta-4 + 2.0.2 false 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 index 3b6bed5ad5..99e5c7a2d7 100644 --- 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 @@ -105,6 +105,7 @@ 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.innate.io.InputStreamWrapper; import org.glassfish.jersey.internal.util.PropertiesHelper; import org.glassfish.jersey.message.internal.HeaderUtils; import org.glassfish.jersey.message.internal.OutboundMessageContext; @@ -521,7 +522,7 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing final HttpEntity entity = response.getEntity(); if (entity != null) { - if (headers.get(HttpHeaders.CONTENT_LENGTH) == null) { + if (headers.get(HttpHeaders.CONTENT_LENGTH) == null && entity.getContentLength() >= 0) { headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(entity.getContentLength())); } @@ -895,7 +896,7 @@ protected void prepareSocket(SSLSocket socket) throws IOException { } } - private static class CancellableInputStream extends InputStream { + private static class CancellableInputStream extends InputStreamWrapper { private final InputStream in; private final Supplier isCancelled; @@ -904,58 +905,17 @@ private CancellableInputStream(InputStream in, Supplier isCancelled) { this.isCancelled = isCancelled; } - public int read(byte b[]) throws IOException { - checkAborted(); - return in.read(); - } - - public int read(byte b[], int off, int len) throws IOException { - checkAborted(); - return in.read(b, off, len); - } - - @Override - public int read() throws IOException { - checkAborted(); - return in.read(); - } - - public boolean markSupported() { - return in.markSupported(); - } - - @Override - public long skip(long n) throws IOException { - checkAborted(); - return in.skip(n); - } - - @Override - public int available() throws IOException { - checkAborted(); - return in.available(); - } - @Override - public void close() throws IOException { - in.close(); + protected InputStream getWrapped() { + return in; } @Override - public void mark(int readlimit) { - in.mark(readlimit); - } - - @Override - public void reset() throws IOException { - checkAborted(); - in.reset(); - } - - private void checkAborted() throws IOException { + protected InputStream getWrappedIOE() throws IOException { if (isCancelled.get()) { throw new IOException(new CancellationException()); } + return in; } } diff --git a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpParser.java b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpParser.java index 4070300e0c..6099e8e5a0 100644 --- a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpParser.java +++ b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpParser.java @@ -514,8 +514,15 @@ private void decideTransferEncoding() throws ParseException { } return; + } else if (httpResponse.getHasContent()) { + // missing Content-Length + transferEncodingParser = TransferEncodingParser + .createFixedLengthParser(httpResponse.getBodyStream(), Long.MAX_VALUE); + return; } + + // TODO what now? Expect no content or fail loudly? } diff --git a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/TransferEncodingParser.java b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/TransferEncodingParser.java index 7dccd87375..1a94ff7d32 100644 --- a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/TransferEncodingParser.java +++ b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/TransferEncodingParser.java @@ -63,7 +63,7 @@ boolean parse(ByteBuffer input) throws ParseException { responseBody.notifyDataAvailable(parsed); consumedLength += data.length; - return consumedLength == expectedLength; + return consumedLength == expectedLength || expectedLength == Long.MAX_VALUE /* unknown at the beginning */; } } diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java index 5d1d2f294c..adf8e033b6 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java @@ -99,6 +99,8 @@ public void channelInactive(ChannelHandlerContext ctx) { if (readTimedOut) { responseDone.completeExceptionally(new TimeoutException("Stream closed: read timeout")); + } else if (jerseyRequest.isCancelled()) { + responseDone.completeExceptionally(new CancellationException()); } else { responseDone.completeExceptionally(new IOException("Stream closed")); } @@ -187,21 +189,10 @@ public String getReasonPhrase() { } // request entity handling. - if ((response.headers().contains(HttpHeaders.CONTENT_LENGTH) && HttpUtil.getContentLength(response) > 0) - || HttpUtil.isTransferEncodingChunked(response)) { - - nis = new NettyInputStream(); - responseDone.whenComplete((_r, th) -> nis.complete(th)); - - jerseyResponse.setEntityStream(nis); - } else { - jerseyResponse.setEntityStream(new InputStream() { - @Override - public int read() throws IOException { - return -1; - } - }); - } + nis = new NettyInputStream(); + responseDone.whenComplete((_r, th) -> nis.complete(th)); + + jerseyResponse.setEntityStream(nis); } if (msg instanceof HttpContent) { diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyExpectContinueHandler.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyExpectContinueHandler.java index 8bceac63ab..bdede02a34 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyExpectContinueHandler.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyExpectContinueHandler.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 @@ -59,7 +59,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception ctx.pipeline().remove(JerseyExpectContinueHandler.class); } } else { - if (!isExpected) { + if (!isExpected + && ctx.pipeline().context(JerseyExpectContinueHandler.class) != null) { ctx.pipeline().remove(JerseyExpectContinueHandler.class); } ctx.fireChannelRead(msg); //bypass the message to the next handler in line diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java index 4fbec4ccc0..cec13488e9 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java @@ -89,6 +89,9 @@ import org.glassfish.jersey.client.spi.AsyncConnectorCallback; import org.glassfish.jersey.client.spi.Connector; import org.glassfish.jersey.innate.VirtualThreadUtil; +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.internal.OutboundMessageContext; import org.glassfish.jersey.netty.connector.internal.NettyEntityWriter; @@ -104,6 +107,17 @@ class NettyConnector implements Connector { final Client client; final HashMap> connections = new HashMap<>(); + private static final LazyValue NETTY_VERSION = Values.lazy( + (Value) () -> { + String nettyVersion = null; + try { + nettyVersion = io.netty.util.Version.identify().values().iterator().next().artifactVersion(); + } catch (Throwable t) { + nettyVersion = "4.1.x"; + } + return "Netty " + nettyVersion; + }); + // If HTTP keepalive is enabled the value of "http.maxConnections" determines the maximum number // of idle connections that will be simultaneously kept alive, per destination. private static final String HTTP_KEEPALIVE_STRING = System.getProperty("http.keepAlive"); @@ -525,7 +539,7 @@ private String buildPathWithQueryParameters(URI requestUri) { @Override public String getName() { - return "Netty 4.1.x"; + return NETTY_VERSION.get(); } @Override diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyEntityWriter.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyEntityWriter.java index a9e70409f8..bcd3fd868c 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyEntityWriter.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyEntityWriter.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 @@ -24,8 +24,10 @@ import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** * The Entity Writer is used to write entity in Netty. One implementation is delayed, @@ -196,10 +198,7 @@ private void _flush() throws IOException { for (Runnable runnable : delayedOps) { runnable.run(); } - - if (outputStream.b != null) { - writer.getOutputStream().write(outputStream.b, outputStream.off, outputStream.len); - } + outputStream._flush(); } } @@ -216,7 +215,7 @@ public OutputStream getOutputStream() { @Override public long getLength() { - return outputStream.len - outputStream.off; + return outputStream.writeLen; } @Override @@ -225,9 +224,9 @@ public Type getType() { } private class DelayedOutputStream extends OutputStream { - private byte[] b; - private int off; - private int len; + private final List actions = new ArrayList<>(); + private int writeLen = 0; + private AtomicBoolean streamFlushed = new AtomicBoolean(false); @Override public void write(int b) throws IOException { @@ -241,15 +240,39 @@ public void write(byte[] b) throws IOException { @Override public void write(byte[] b, int off, int len) throws IOException { - if (!flushed && this.b == null) { - this.b = b; - this.off = off; - this.len = len; + if (!flushed) { + actions.add(new WriteAction(b, off, len)); + writeLen += len; } else { - DelayedEntityWriter.this._flush(); + _flush(); writer.getOutputStream().write(b, off, len); + writer.getOutputStream().flush(); + } + } + + public void _flush() throws IOException { + if (streamFlushed.compareAndSet(false, true)) { + DelayedEntityWriter.this._flush(); + for (WriteAction action : actions) { + action.run(); + } + actions.clear(); } } } + + private class WriteAction { + private final byte[] b; + + private WriteAction(byte[] b, int off, int len) { + this.b = new byte[len]; // b passed in can be reused + System.arraycopy(b, off, this.b, 0, len); + } + + public void run() throws IOException { + writer.getOutputStream().write(b, 0, b.length); + writer.getOutputStream().flush(); + } + } } } diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java index a6f0c1b081..c2c5bf4cc3 100644 --- a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java +++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java @@ -17,6 +17,8 @@ package org.glassfish.jersey.servlet; import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; import java.lang.reflect.Type; import java.net.URI; import java.security.AccessController; @@ -54,6 +56,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.glassfish.jersey.innate.io.InputStreamWrapper; import org.glassfish.jersey.innate.inject.InternalBinder; import org.glassfish.jersey.innate.inject.InjectionIds; import org.glassfish.jersey.innate.inject.ServiceFinderBinder; @@ -438,7 +441,7 @@ public Integer get() { } /** - * Initialize {@code ContainerRequest} instance to be used to handle {@code servletRequest}. + * Initialize {@code ContainerRequest} instance to handle {@code servletRequest}. */ private void initContainerRequest( final ContainerRequest requestContext, @@ -446,7 +449,21 @@ private void initContainerRequest( final HttpServletResponse servletResponse, final ResponseWriter responseWriter) throws IOException { - requestContext.setEntityStream(servletRequest.getInputStream()); + try { + requestContext.setEntityStream(new InputStreamWrapper() { + @Override + protected InputStream getWrapped() { + try { + return servletRequest.getInputStream(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + }); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + requestContext.setRequestScopedInitializer(requestScopedInitializer.get(new RequestContextProvider() { @Override public HttpServletRequest getHttpServletRequest() { diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ResponseWriter.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ResponseWriter.java index 5059c306ef..9876c01135 100644 --- a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ResponseWriter.java +++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ResponseWriter.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 @@ -142,12 +142,7 @@ public OutputStream writeResponseStatusAndHeaders(final long contentLength, fina } } - final String reasonPhrase = responseContext.getStatusInfo().getReasonPhrase(); - if (reasonPhrase != null) { - response.setStatus(responseContext.getStatus()); - } else { - response.setStatus(responseContext.getStatus()); - } + response.setStatus(responseContext.getStatus()); if (!responseContext.hasEntity()) { return null; @@ -214,12 +209,13 @@ public void failure(final Throwable error) { try { if (!response.isCommitted()) { try { + final int statusCode = Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(); if (configSetStatusOverSendError) { response.reset(); //noinspection deprecation - response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + response.setStatus(statusCode); } else { - response.sendError(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Request failed."); + response.sendError(statusCode, "Request failed."); } } catch (final IllegalStateException ex) { // a race condition externally committing the response can still occur... diff --git a/containers/jersey-servlet-core/src/test/java/org/glassfish/jersey/servlet/internal/RequestInputStreamTest.java b/containers/jersey-servlet-core/src/test/java/org/glassfish/jersey/servlet/internal/RequestInputStreamTest.java new file mode 100644 index 0000000000..feb899c498 --- /dev/null +++ b/containers/jersey-servlet-core/src/test/java/org/glassfish/jersey/servlet/internal/RequestInputStreamTest.java @@ -0,0 +1,97 @@ +/* + * 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.servlet.internal; + +import org.glassfish.jersey.CommonProperties; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.ServerProperties; +import org.glassfish.jersey.servlet.ServletProperties; +import org.glassfish.jersey.servlet.WebComponent; +import org.glassfish.jersey.servlet.WebConfig; +import org.glassfish.jersey.servlet.WebFilterConfig; +import org.junit.jupiter.api.Test; + +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.URI; +import java.util.Collections; +import java.util.Enumeration; + +public class RequestInputStreamTest { + @Test + public void test404RequestInputStream() throws ServletException, IOException { + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "getHeaderNames": + return Collections.emptyEnumeration(); + case "getInputStream": + throw new IllegalStateException("ServletRequest#getInputStream clashes with ServletRequest#getReader"); + } + return null; + } + }; + + FilterConfig filterConfig = new FilterConfig() { + @Override + public String getFilterName() { + return null; + } + + @Override + public ServletContext getServletContext() { + return (ServletContext) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ServletContext.class}, + handler); + } + + @Override + public String getInitParameter(String name) { + return null; + } + + @Override + public Enumeration getInitParameterNames() { + return null; + } + }; + WebConfig dummyWebConfig = new WebFilterConfig(filterConfig); + ResourceConfig resourceConfig = new ResourceConfig() + .property(CommonProperties.PROVIDER_DEFAULT_DISABLE, "ALL") + .property(ServerProperties.WADL_FEATURE_DISABLE, true) + .property(ServletProperties.FILTER_FORWARD_ON_404, true) + .property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true); + WebComponent component = new WebComponent(dummyWebConfig, resourceConfig); + component.service(URI.create("http://localhost"), URI.create("http://localhost"), + (HttpServletRequest) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] {HttpServletRequest.class}, + handler + ), + (HttpServletResponse) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{HttpServletResponse.class}, + handler) + ); + } +} 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 74c8cfbef7..fa020cf3c1 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, 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 @@ -35,6 +35,7 @@ import org.glassfish.jersey.internal.PropertiesDelegate; import org.glassfish.jersey.internal.util.ReflectionHelper; import org.glassfish.jersey.message.MessageBodyWorkers; +import org.glassfish.jersey.message.internal.ReaderInterceptorExecutor; /** * {@link jakarta.ws.rs.ext.MessageBodyWriter} for {@link ChunkedInput}. @@ -71,7 +72,7 @@ public ChunkedInput readFrom(Class chunkedInputClass, return new ChunkedInput( chunkType, - inputStream, + ReaderInterceptorExecutor.closeableInputStream(inputStream), annotations, mediaType, headers, 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 78a7111b37..162e4a0405 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 @@ -91,7 +91,7 @@ public final class NonInjectionManager implements InjectionManager { */ private class TypedInstances { private final MultivaluedMap> singletonInstances = new MultivaluedHashMap<>(); - private final ThreadLocal>> threadInstances = new ThreadLocal<>(); + private ThreadLocal>> threadInstances = new ThreadLocal<>(); private final List threadPredestroyables = Collections.synchronizedList(new LinkedList<>()); private final ReentrantLock singletonInstancesLock = new ReentrantLock(); @@ -203,6 +203,8 @@ T getInstance(TYPE clazz, Annotation[] annotations) { void dispose() { singletonInstances.forEach((clazz, instances) -> instances.forEach(instance -> preDestroy(instance.getInstance()))); threadPredestroyables.forEach(NonInjectionManager.this::preDestroy); + /* The java.lang.ThreadLocal$ThreadLocalMap$Entry[] keeps references to this NonInjectionManager */ + threadInstances = null; } } 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 b5b28f389c..ca73f4c25c 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 @@ -66,6 +66,7 @@ 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.internal.util.collection.LRU; import org.glassfish.jersey.internal.util.collection.LazyValue; import org.glassfish.jersey.internal.util.collection.UnsafeValue; import org.glassfish.jersey.internal.util.collection.Value; @@ -83,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 LazyValue DEFAULT_SSL_SOCKET_FACTORY = + private static final Value 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 = { @@ -114,7 +115,12 @@ public class HttpUrlConnector implements Connector { private final boolean fixLengthStreaming; private final boolean setMethodWorkaround; private final boolean isRestrictedHeaderPropertySet; - private LazyValue sslSocketFactory; + private Value sslSocketFactory; + + // SSLContext#getSocketFactory not idempotent + // JDK KeepAliveCache keeps connections per Factory + // SSLContext set per request blows that -> keep factory in LRU + private final LRU sslSocketFactoryCache = LRU.create(); private final ConnectorExtension connectorExtension = new HttpUrlExpect100ContinueConnectorExtension(); @@ -143,6 +149,13 @@ public HttpUrlConnector( this.fixLengthStreaming = fixLengthStreaming; this.setMethodWorkaround = setMethodWorkaround; + this.sslSocketFactory = Values.lazy(new Value() { + @Override + public SSLSocketFactory get() { + return client.getSslContext().getSocketFactory(); + } + }); + // check if sun.net.http.allowRestrictedHeaders system property has been set and log the result // the property is being cached in the HttpURLConnection, so this is only informative - there might // already be some connection(s), that existed before the property was set/changed. @@ -342,16 +355,23 @@ private void secureConnection( } } - private void setSslContextFactory(Client client, ClientRequest request) { + protected 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(); - } - }); + if (supplier != null) { + sslSocketFactory = Values.lazy(new Value() { // lazy for double-check locking if multiple requests + @Override + public SSLSocketFactory get() { + SSLContext sslContext = supplier.get(); + SSLSocketFactory factory = sslSocketFactoryCache.getIfPresent(sslContext); + if (factory == null) { + factory = sslContext.getSocketFactory(); + sslSocketFactoryCache.put(sslContext, factory); + } + return factory; + } + }); + } } private ClientResponse _apply(final ClientRequest request) throws IOException { 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 291ac98580..8940d72197 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, 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 @@ -50,7 +50,7 @@ negative.chunk.size=Negative chunked HTTP transfer coding chunk size value speci Reverting to programmatically set default: [{1}] 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.fallback=Jersey-HK2 module is missing. Falling back to injection-less client. Injection may not be supported on the client. noninject.no.constructor=No applicable constructor for ${0} found. noninject.no.binding=No binding found for ${0}. noninject.requestscope.created=RequestScope already created. diff --git a/core-client/src/test/java/org/glassfish/jersey/client/SSLSocketFactoryTest.java b/core-client/src/test/java/org/glassfish/jersey/client/SSLSocketFactoryTest.java new file mode 100644 index 0000000000..362bbe67ed --- /dev/null +++ b/core-client/src/test/java/org/glassfish/jersey/client/SSLSocketFactoryTest.java @@ -0,0 +1,157 @@ +/* + * 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.client; + +import org.glassfish.jersey.client.internal.HttpUrlConnector; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.internal.PropertiesDelegate; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.core.Response; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public class SSLSocketFactoryTest { + static final AtomicReference factoryHolder = new AtomicReference<>(); + static SSLSocketFactory defaultSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); + + // @Test + // Alternative test + // Check KeepAliveCache#get(URL url, Object obj) + public void testSingleConnection() throws InterruptedException, IOException { + Client client = ClientBuilder.newClient(); + + for (int i = 0; i < 3; i++) { + try (Response response = client.target("https://www.spiegel.de") + .request() + .get()) { + + response.readEntity(String.class); + System.out.println(String.format("response = %s", response)); + Thread.sleep(1000); + } + } + + System.in.read(); + } + + @Test + public void testSslContextFactoryOnClientIsSameForConsecutiveRequests() throws IOException, URISyntaxException { + int firstRequestFactory, secondRequestFactory = 0; + Client client = ClientBuilder.newClient(); + HttpUrlConnectorProvider.ConnectionFactory connectionFactory = (url) -> (HttpURLConnection) url.openConnection(); + SSLSocketFactoryConnector connector = (SSLSocketFactoryConnector) new SSlSocketFactoryUrlConnectorProvider() + .createHttpUrlConnector(client, connectionFactory, 4096, true, false); + URL url = new URL("https://somewhere.whereever:8080"); + URLConnection urlConnection = url.openConnection(); + + // First Request + connector.setSslContextFactory(client, new ClientRequest(url.toURI(), + (ClientConfig) client.getConfiguration(), new MapPropertiesDelegate())); + connector.secureConnection((JerseyClient) client, (HttpURLConnection) urlConnection); + firstRequestFactory = factoryHolder.get().hashCode(); + + // reset to the default socketFactory + ((HttpsURLConnection) urlConnection).setSSLSocketFactory(defaultSocketFactory); + + // Second Request + connector.setSslContextFactory(client, new ClientRequest(url.toURI(), + (ClientConfig) client.getConfiguration(), new MapPropertiesDelegate())); + connector.secureConnection((JerseyClient) client, (HttpURLConnection) urlConnection); + secondRequestFactory = factoryHolder.get().hashCode(); + + MatcherAssert.assertThat(firstRequestFactory, Matchers.equalTo(secondRequestFactory)); + } + + @Test + public void testSslContextFactoryOnRequestIsSameForConsecutiveRequests() throws IOException, URISyntaxException { + SSLSocketFactory firstRequestFactory, secondRequestFactory = null; + Client client = ClientBuilder.newClient(); + SSLContext sslContext = new SslContextClientBuilder().build(); + HttpUrlConnectorProvider.ConnectionFactory connectionFactory = (url) -> (HttpURLConnection) url.openConnection(); + SSLSocketFactoryConnector connector = (SSLSocketFactoryConnector) new SSlSocketFactoryUrlConnectorProvider() + .createHttpUrlConnector(client, connectionFactory, 4096, true, false); + URL url = new URL("https://somewhere.whereever:8080"); + URLConnection urlConnection = url.openConnection(); + PropertiesDelegate propertiesDelegate = new MapPropertiesDelegate(); + propertiesDelegate.setProperty(ClientProperties.SSL_CONTEXT_SUPPLIER, (Supplier) () -> sslContext); + + // First Request + connector.setSslContextFactory(client, new ClientRequest(url.toURI(), + (ClientConfig) client.getConfiguration(), propertiesDelegate)); + connector.secureConnection((JerseyClient) client, (HttpURLConnection) urlConnection); + firstRequestFactory = factoryHolder.get(); + + // reset to the default socketFactory + ((HttpsURLConnection) urlConnection).setSSLSocketFactory(defaultSocketFactory); + + // Second Request + connector.setSslContextFactory(client, new ClientRequest(url.toURI(), + (ClientConfig) client.getConfiguration(), propertiesDelegate)); + connector.secureConnection((JerseyClient) client, (HttpURLConnection) urlConnection); + secondRequestFactory = factoryHolder.get(); + + MatcherAssert.assertThat(firstRequestFactory, Matchers.equalTo(secondRequestFactory)); + } + + private static class SSLSocketFactoryConnector extends HttpUrlConnector { + public SSLSocketFactoryConnector(Client client, HttpUrlConnectorProvider.ConnectionFactory connectionFactory, + int chunkSize, boolean fixLengthStreaming, boolean setMethodWorkaround) { + super(client, connectionFactory, chunkSize, fixLengthStreaming, setMethodWorkaround); + } + + @Override + protected void secureConnection(JerseyClient client, HttpURLConnection uc) { + super.secureConnection(client, uc); + if (HttpsURLConnection.class.isInstance(uc)) { + SSLSocketFactory factory = ((HttpsURLConnection) uc).getSSLSocketFactory(); + factoryHolder.set(factory); + } + } + + @Override + protected void setSslContextFactory(Client client, ClientRequest request) { + super.setSslContextFactory(client, request); + } + } + + private static class SSlSocketFactoryUrlConnectorProvider extends HttpUrlConnectorProvider { + @Override + protected Connector createHttpUrlConnector(Client client, ConnectionFactory connectionFactory, int chunkSize, + boolean fixLengthStreaming, boolean setMethodWorkaround) { + return new SSLSocketFactoryConnector( + client, + connectionFactory, + chunkSize, + fixLengthStreaming, + setMethodWorkaround); + } + } +} diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/inject/InjectionIds.java b/core-common/src/main/java/org/glassfish/jersey/innate/inject/InjectionIds.java index 35cc45d9a3..e6f0fde0c7 100644 --- a/core-common/src/main/java/org/glassfish/jersey/innate/inject/InjectionIds.java +++ b/core-common/src/main/java/org/glassfish/jersey/innate/inject/InjectionIds.java @@ -43,6 +43,7 @@ public enum InjectionIds { CLIENT_SCHEDULED_EXECUTOR_SERVICE_PROVIDER(2021), CLIENT_BYTE_ARRAY_PROVIDER(2030), // MBW & MBR CLIENT_FILE_PROVIDER(2031), // MBW & MBR + CLIENT_PATH_PROVIDER(2040), // MBW & MBR CLIENT_MULTIVALUED_MAP_PROVIDER(2032), // MBW & MBR CLIENT_FORM_PROVIDER(2033), // MBW & MBR CLIENT_INPUT_STREAM_PROVIDER(2034), // MBW & MBR @@ -72,6 +73,7 @@ public enum InjectionIds { SERVER_MESSAGE_BODY_WORKERS(3020), SERVER_BYTE_ARRAY_PROVIDER(3030), // MBW & MBR SERVER_FILE_PROVIDER(3031), // MBW & MBR + SERVER_PATH_PROVIDER(3040), // MBW & MBR SERVER_MULTIVALUED_MAP_PROVIDER(3032), // MBW & MBR SERVER_FORM_PROVIDER(3033), // MBW & MBR SERVER_INPUT_STREAM_PROVIDER(3034), // MBW & MBR diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/io/InputStreamWrapper.java b/core-common/src/main/java/org/glassfish/jersey/innate/io/InputStreamWrapper.java new file mode 100644 index 0000000000..c2109fe99f --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/innate/io/InputStreamWrapper.java @@ -0,0 +1,86 @@ +/* + * 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.innate.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Generic wrapper template for InputStream. + */ +public abstract class InputStreamWrapper extends InputStream { + + /** + * Return the wrapped stream + * @return + */ + protected abstract InputStream getWrapped(); + + /** + * Get wrapped stream that can throw {@link IOException} + * @return the wrapped InputStream. + * @throws IOException + */ + protected InputStream getWrappedIOE() throws IOException { + return getWrapped(); + } + + @Override + public int read() throws IOException { + return getWrappedIOE().read(); + } + + @Override + public int read(byte[] b) throws IOException { + return getWrappedIOE().read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return getWrappedIOE().read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + return getWrappedIOE().skip(n); + } + + @Override + public int available() throws IOException { + return getWrappedIOE().available(); + } + + @Override + public void close() throws IOException { + getWrappedIOE().close(); + } + + @Override + public void mark(int readlimit) { + getWrapped().mark(readlimit); + } + + @Override + public void reset() throws IOException { + getWrappedIOE().reset(); + } + + @Override + public boolean markSupported() { + return getWrapped().markSupported(); + } +} diff --git a/core-common/src/main/java/org/glassfish/jersey/innate/io/package-info.java b/core-common/src/main/java/org/glassfish/jersey/innate/io/package-info.java new file mode 100644 index 0000000000..e569f79f58 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/innate/io/package-info.java @@ -0,0 +1,21 @@ +/* + * 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 + */ + +/** + * Jersey innate io related packages. The innate packages will not be opened by JPMS outside of Jersey. + * Not for public use. + */ +package org.glassfish.jersey.innate.io; 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 e4159ba295..d5b0e63aa9 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 @@ -96,6 +96,8 @@ protected void configure() { // bindSingletonWorker(DataSourceProvider.class); bindSingletonWorker(FileProvider.class, runtimeType == RuntimeType.CLIENT ? InjectionIds.CLIENT_FILE_PROVIDER.id() : InjectionIds.SERVER_FILE_PROVIDER.id()); + bindSingletonWorker(PathProvider.class, runtimeType == RuntimeType.CLIENT + ? InjectionIds.CLIENT_PATH_PROVIDER.id() : InjectionIds.SERVER_PATH_PROVIDER.id()); bindSingletonWorker(FormMultivaluedMapProvider.class, runtimeType == RuntimeType.CLIENT ? InjectionIds.CLIENT_MULTIVALUED_MAP_PROVIDER.id() : InjectionIds.SERVER_MULTIVALUED_MAP_PROVIDER.id()); bindSingletonWorker(FormProvider.class, runtimeType == RuntimeType.CLIENT diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/PathProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/PathProvider.java new file mode 100644 index 0000000000..461a4f85a2 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/PathProvider.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 Markus KARG and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available 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.message.internal; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM; +import static jakarta.ws.rs.core.MediaType.WILDCARD; + +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.Path; +import java.nio.file.StandardCopyOption; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; + +import jakarta.inject.Singleton; + +/** + * Provider for marshalling/un-marshalling of {@code application/octet-stream} + * entity type to/from a {@link Path} instance. + * + * @author Markus KARG + */ +@Produces({APPLICATION_OCTET_STREAM, WILDCARD}) +@Consumes({APPLICATION_OCTET_STREAM, WILDCARD}) +@Singleton +public final class PathProvider extends AbstractMessageReaderWriterProvider { + + @Override + public final boolean isReadable(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) { + return Path.class == type; + } + + @Override + public final Path readFrom(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap httpHeaders, + final InputStream entityStream) throws IOException { + final var path = Utils.createTempFile().toPath(); + Files.copy(entityStream, path, StandardCopyOption.REPLACE_EXISTING); + return path; + } + + @Override + public final boolean isWriteable(final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType) { + return Path.class.isAssignableFrom(type); + } + + @Override + public final void writeTo(final Path t, + final Class type, + final Type genericType, + final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap httpHeaders, + final OutputStream entityStream) throws IOException { + Files.copy(t, entityStream); + } +} diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderInterceptorExecutor.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderInterceptorExecutor.java index 7a1a4a5c1c..20a01d6ca3 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderInterceptorExecutor.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderInterceptorExecutor.java @@ -40,6 +40,7 @@ import jakarta.ws.rs.ext.ReaderInterceptor; import jakarta.ws.rs.ext.ReaderInterceptorContext; +import org.glassfish.jersey.innate.io.InputStreamWrapper; import org.glassfish.jersey.internal.LocalizationMessages; import org.glassfish.jersey.internal.PropertiesDelegate; import org.glassfish.jersey.internal.inject.InjectionManager; @@ -250,7 +251,7 @@ private Object invokeReadFrom(final ReaderInterceptorContext context, final Mess * {@link jakarta.ws.rs.ext.MessageBodyReader}s should not close the given {@link java.io.InputStream stream}. This input * stream makes sure that the stream is not closed even if MBR tries to do it. */ - private static class UnCloseableInputStream extends InputStream { + private static class UnCloseableInputStream extends InputStreamWrapper { private final InputStream original; private final MessageBodyReader reader; @@ -262,53 +263,8 @@ private UnCloseableInputStream(final InputStream original, final MessageBodyRead } @Override - public int read() throws IOException { - return original.read(); - } - - @Override - public int read(final byte[] b) throws IOException { - return original.read(b); - } - - @Override - public int read(final byte[] b, final int off, final int len) throws IOException { - return original.read(b, off, len); - } - - @Override - public long skip(final long l) throws IOException { - return original.skip(l); - } - - @Override - public int available() throws IOException { - return original.available(); - } - - @Override - public void mark(final int i) { - markLock.lock(); - try { - original.mark(i); - } finally { - markLock.unlock(); - } - } - - @Override - public void reset() throws IOException { - markLock.lock(); - try { - original.reset(); - } finally { - markLock.unlock(); - } - } - - @Override - public boolean markSupported() { - return original.markSupported(); + protected InputStream getWrapped() { + return original; } @Override @@ -317,10 +273,6 @@ public void close() throws IOException { LOGGER.log(Level.FINE, LocalizationMessages.MBR_TRYING_TO_CLOSE_STREAM(reader.getClass())); } } - - private InputStream unwrap() { - return original; - } } /** @@ -333,7 +285,7 @@ private InputStream unwrap() { */ public static InputStream closeableInputStream(InputStream inputStream) { if (inputStream instanceof UnCloseableInputStream) { - return ((UnCloseableInputStream) inputStream).unwrap(); + return ((UnCloseableInputStream) inputStream).getWrapped(); } else { return inputStream; } 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 ef2ae4d4b9..184402f495 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 @@ -57,6 +57,12 @@ "allDeclaredMethods":true, "allDeclaredConstructors":true }, + { + "name":"org.glassfish.jersey.message.internal.PathProvider", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true + }, { "name":"org.glassfish.jersey.message.internal.FormMultivaluedMapProvider", "allDeclaredFields":true, diff --git a/core-server/pom.xml b/core-server/pom.xml index 1d5279be02..51e59b2f80 100644 --- a/core-server/pom.xml +++ b/core-server/pom.xml @@ -234,7 +234,13 @@ org.jboss jboss-vfs - 3.2.6.Final + ${jboss.vfs.version} + test + + + org.jboss.logging + jboss-logging + ${jboss.logging.version} test diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/JarFileScanner.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/JarFileScanner.java index 818bdbebb9..7b97eb93c8 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/JarFileScanner.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/JarFileScanner.java @@ -26,6 +26,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import org.glassfish.jersey.innate.io.InputStreamWrapper; import org.glassfish.jersey.server.internal.AbstractResourceFinderAdapter; import org.glassfish.jersey.server.internal.LocalizationMessages; @@ -111,63 +112,17 @@ public void reset() { @Override public InputStream open() { //noinspection NullableProblems - return new InputStream() { - private final Lock markLock = new ReentrantLock(); + return new InputStreamWrapper() { @Override - public int read() throws IOException { - return jarInputStream.read(); - } - - @Override - public int read(final byte[] bytes) throws IOException { - return jarInputStream.read(bytes); - } - - @Override - public int read(final byte[] bytes, final int i, final int i2) throws IOException { - return jarInputStream.read(bytes, i, i2); - } - - @Override - public long skip(final long l) throws IOException { - return jarInputStream.skip(l); - } - - @Override - public int available() throws IOException { - return jarInputStream.available(); + protected InputStream getWrapped() { + return jarInputStream; } @Override public void close() throws IOException { jarInputStream.closeEntry(); } - - @Override - public void mark(final int i) { - markLock.lock(); - try { - jarInputStream.mark(i); - } finally { - markLock.unlock(); - } - } - - @Override - public void reset() throws IOException { - markLock.lock(); - try { - jarInputStream.reset(); - } finally { - markLock.unlock(); - } - } - - @Override - public boolean markSupported() { - return jarInputStream.markSupported(); - } }; } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/wadl/internal/WadlResource.java b/core-server/src/main/java/org/glassfish/jersey/server/wadl/internal/WadlResource.java index 982a44b69d..62ce38dcdd 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/wadl/internal/WadlResource.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/wadl/internal/WadlResource.java @@ -23,6 +23,7 @@ import java.util.Date; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.Locale; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -65,7 +66,7 @@ public final class WadlResource { public WadlResource() { - this.lastModified = new SimpleDateFormat(HTTPDATEFORMAT).format(new Date()); + this.lastModified = new SimpleDateFormat(HTTPDATEFORMAT, Locale.US).format(new Date()); } private boolean isCached(UriInfo uriInfo, boolean detailedWadl) { @@ -85,7 +86,7 @@ public Response getWadl(@Context UriInfo uriInfo) { if ((wadlXmlRepresentation == null) || (!isCached(uriInfo, detailedWadl))) { this.lastBaseUri = uriInfo.getBaseUri(); lastDetailedWadl = detailedWadl; - this.lastModified = new SimpleDateFormat(HTTPDATEFORMAT).format(new Date()); + this.lastModified = new SimpleDateFormat(HTTPDATEFORMAT, Locale.US).format(new Date()); ApplicationDescription applicationDescription = wadlContext.getApplication(uriInfo, detailedWadl); diff --git a/etc/scripts/validation/pom.template.xml b/etc/scripts/validation/pom.template.xml index 1e58300400..08a65ea1b2 100644 --- a/etc/scripts/validation/pom.template.xml +++ b/etc/scripts/validation/pom.template.xml @@ -36,6 +36,6 @@ - 3.0.99-SNAPSHOT + 4.0.99-SNAPSHOT \ No newline at end of file diff --git a/examples/NOTICE.md b/examples/NOTICE.md index ab5da5fdc1..678b6eb368 100644 --- a/examples/NOTICE.md +++ b/examples/NOTICE.md @@ -71,7 +71,7 @@ Javassist Version 3.30.2-GA * Project: http://www.javassist.org/ * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. -Jackson JAX-RS Providers Version 2.17.0 +Jackson JAX-RS Providers Version 2.17.1 * License: Apache License, 2.0 * Project: https://github.com/FasterXML/jackson-jaxrs-providers * Copyright: (c) 2009-2023 FasterXML, LLC. All rights reserved unless otherwise indicated. diff --git a/examples/groovy/pom.xml b/examples/groovy/pom.xml index a6d864ad9c..90d6463780 100644 --- a/examples/groovy/pom.xml +++ b/examples/groovy/pom.xml @@ -140,7 +140,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.0.0 + ${buildhelper.mvn.plugin.version} 3 diff --git a/examples/jaxb/pom.xml b/examples/jaxb/pom.xml index b3d06f6085..c3b338a5d1 100644 --- a/examples/jaxb/pom.xml +++ b/examples/jaxb/pom.xml @@ -41,7 +41,7 @@ org.codehaus.woodstox woodstox-core-asl - 4.1.2 + 4.4.1 org.glassfish.jersey.media diff --git a/examples/osgi-http-service/functional-test/pom.xml b/examples/osgi-http-service/functional-test/pom.xml index 49f88a1590..e52bacc261 100644 --- a/examples/osgi-http-service/functional-test/pom.xml +++ b/examples/osgi-http-service/functional-test/pom.xml @@ -145,7 +145,7 @@ org.slf4j slf4j-log4j12 - 1.6.4 + ${slf4j.version} test diff --git a/examples/pom.xml b/examples/pom.xml index ece6c56a1a..26d8eb9e42 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -185,7 +185,7 @@ org.commonjava.maven.plugins directory-maven-plugin - 0.3.1 + 1.0 directories 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 index 66cdaf7b1e..8b6d9b3dfe 100644 --- 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 @@ -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 @@ -23,6 +23,9 @@ import org.glassfish.jersey.server.ExtendedUriInfo; import org.glassfish.jersey.server.monitoring.RequestEvent; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; + /** * Factory methods for {@link KeyValue KeyValues} associated with a request-response * exchange that is handled by Jersey server. @@ -38,6 +41,9 @@ class JerseyKeyValues { private static final KeyValue URI_ROOT = JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.URI .withValue("root"); + private static final KeyValue URI_UNKNOWN = JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.URI + .withValue("UNKNOWN"); + private static final KeyValue EXCEPTION_NONE = JerseyObservationDocumentation.JerseyLegacyLowCardinalityTags.EXCEPTION .withValue("None"); @@ -82,17 +88,30 @@ static KeyValue status(ContainerResponse response) { * @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(); + int status = 0; + if (event.getContainerResponse() != null) { + status = event.getContainerResponse().getStatus(); + } else if (WebApplicationException.class.isInstance(event.getException())) { + Response webAppResponse = ((WebApplicationException) event.getException()).getResponse(); + if (webAppResponse != null) { + status = webAppResponse.getStatus(); + } + } + if (status != 0) { if (JerseyTags.isRedirection(status) && event.getUriInfo().getMatchedResourceMethod() == null) { return URI_REDIRECTION; } if (status == 404 && event.getUriInfo().getMatchedResourceMethod() == null) { return URI_NOT_FOUND; } + if (status >= 500 && status <= 599) { + return STATUS_SERVER_ERROR; + } } String matchingPattern = JerseyTags.getMatchingPattern(event); + if (matchingPattern == null) { + return URI_UNKNOWN; + } if (matchingPattern.equals("/")) { return URI_ROOT; } diff --git a/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/exception/JerseyKeyValuesTest.java b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/exception/JerseyKeyValuesTest.java new file mode 100644 index 0000000000..99bad31000 --- /dev/null +++ b/ext/micrometer/src/test/java/org/glassfish/jersey/micrometer/server/exception/JerseyKeyValuesTest.java @@ -0,0 +1,62 @@ +/* + * 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.micrometer.server.exception; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.glassfish.jersey.micrometer.server.DefaultJerseyObservationConvention; +import org.glassfish.jersey.micrometer.server.JerseyContext; +import org.glassfish.jersey.server.ExtendedUriInfo; +import org.glassfish.jersey.server.internal.monitoring.RequestEventImpl; +import org.glassfish.jersey.server.monitoring.RequestEvent; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.NotFoundException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.Optional; + +public class JerseyKeyValuesTest { + @Test + public void testOnException() { + ExtendedUriInfo uriInfo = (ExtendedUriInfo) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[]{ExtendedUriInfo.class}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "getMatchedTemplates": + return Collections.emptyList(); + } + return null; + } + }); + RequestEventImpl event = new RequestEventImpl.Builder() + .setExtendedUriInfo(uriInfo) + .setException(new NotFoundException(), RequestEvent.ExceptionCause.ORIGINAL) + .build(RequestEvent.Type.ON_EXCEPTION); + JerseyContext context = new JerseyContext(event); + DefaultJerseyObservationConvention convention = new DefaultJerseyObservationConvention("Test-Metric"); + KeyValues values = convention.getLowCardinalityKeyValues(context); + Optional kv = values.stream().filter(p -> p.getValue().equals("NOT_FOUND")).findFirst(); + MatcherAssert.assertThat(kv.isPresent(), Matchers.equalTo(true)); + } +} diff --git a/incubator/declarative-linking/pom.xml b/incubator/declarative-linking/pom.xml index 8f7c84452b..209b70a4a5 100644 --- a/incubator/declarative-linking/pom.xml +++ b/incubator/declarative-linking/pom.xml @@ -88,7 +88,7 @@ org.skyscreamer jsonassert - 1.4.0 + 1.5.1 test diff --git a/media/jaxb/pom.xml b/media/jaxb/pom.xml index 12978a80fa..9c4c921fa7 100644 --- a/media/jaxb/pom.xml +++ b/media/jaxb/pom.xml @@ -82,21 +82,6 @@ build-helper-maven-plugin true - - de.jflex - maven-jflex-plugin - 1.4.3 - - - - generate - - - ${project.build.directory}/generated-sources/rsrc-gen - - - - org.apache.felix maven-bundle-plugin diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/cfg/MapperConfiguratorBase.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/cfg/MapperConfiguratorBase.java index 75bc66eec7..bc280ccbfb 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/cfg/MapperConfiguratorBase.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/cfg/MapperConfiguratorBase.java @@ -13,8 +13,8 @@ * well as accessing it. */ public abstract class MapperConfiguratorBase, - MAPPER extends ObjectMapper -> + MAPPER extends ObjectMapper + > { /** * Mapper provider was constructed with if any, or that was constructed @@ -22,7 +22,7 @@ public abstract class MapperConfiguratorBase + extends MapperConfiguratorBase { + // @since 2.17.1 + private final ReentrantLock _lock = new ReentrantLock(); + /* /********************************************************** /* Construction /********************************************************** */ - + public JsonMapperConfigurator(ObjectMapper mapper, Annotations[] defAnnotations) { super(mapper, defAnnotations); @@ -33,18 +37,24 @@ public JsonMapperConfigurator(ObjectMapper mapper, Annotations[] defAnnotations) * Method that locates, configures and returns {@link ObjectMapper} to use */ @Override - public synchronized ObjectMapper getConfiguredMapper() { - /* important: should NOT call mapper(); needs to return null - * if no instance has been passed or constructed - */ + public ObjectMapper getConfiguredMapper() { + // important: should NOT call mapper(); needs to return null + // if no instance has been passed or constructed return _mapper; } @Override - public synchronized ObjectMapper getDefaultMapper() { + public ObjectMapper getDefaultMapper() { if (_defaultMapper == null) { - _defaultMapper = new ObjectMapper(); - _setAnnotations(_defaultMapper, _defaultAnnotationsToUse); + _lock.lock(); + try { + if (_defaultMapper == null) { + _defaultMapper = new ObjectMapper(); + _setAnnotations(_defaultMapper, _defaultAnnotationsToUse); + } + } finally { + _lock.unlock(); + } } return _defaultMapper; } @@ -64,8 +74,15 @@ public synchronized ObjectMapper getDefaultMapper() { protected ObjectMapper mapper() { if (_mapper == null) { - _mapper = new ObjectMapper(); - _setAnnotations(_mapper, _defaultAnnotationsToUse); + _lock.lock(); + try { + if (_mapper == null) { + _mapper = new ObjectMapper(); + _setAnnotations(_mapper, _defaultAnnotationsToUse); + } + } finally { + _lock.unlock(); + } } return _mapper; } @@ -100,22 +117,22 @@ protected AnnotationIntrospector _resolveIntrospectors(Annotations[] annotations protected AnnotationIntrospector _resolveIntrospector(Annotations ann) { switch (ann) { - case JACKSON: - return new JacksonAnnotationIntrospector(); - case JAXB: - /* For this, need to use indirection just so that error occurs - * when we get here, and not when this class is being loaded - */ - try { - if (_jaxbIntrospectorClass == null) { - _jaxbIntrospectorClass = JaxbAnnotationIntrospector.class; + case JACKSON: + return new JacksonAnnotationIntrospector(); + case JAXB: + /* For this, need to use indirection just so that error occurs + * when we get here, and not when this class is being loaded + */ + try { + if (_jaxbIntrospectorClass == null) { + _jaxbIntrospectorClass = JaxbAnnotationIntrospector.class; + } + return _jaxbIntrospectorClass.newInstance(); + } catch (Exception e) { + throw new IllegalStateException("Failed to instantiate JaxbAnnotationIntrospector: "+e.getMessage(), e); } - return _jaxbIntrospectorClass.newInstance(); - } catch (Exception e) { - throw new IllegalStateException("Failed to instantiate JaxbAnnotationIntrospector: "+e.getMessage(), e); - } - default: - throw new IllegalStateException(); + default: + throw new IllegalStateException(); } } } diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/PackageVersion.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/PackageVersion.java index 88e31680b8..5d328da992 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/PackageVersion.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/PackageVersion.java @@ -11,7 +11,7 @@ */ public final class PackageVersion implements Versioned { public final static Version VERSION = VersionUtil.parseVersion( - "2.17.0", "com.fasterxml.jackson.jaxrs", "jackson-jaxrs-json-provider"); + "2.17.1", "com.fasterxml.jackson.jaxrs", "jackson-jaxrs-json-provider"); @Override public Version version() { diff --git a/media/json-jackson/src/main/resources/META-INF/NOTICE.markdown b/media/json-jackson/src/main/resources/META-INF/NOTICE.markdown index 6034c04d7c..4edecfc595 100644 --- a/media/json-jackson/src/main/resources/META-INF/NOTICE.markdown +++ b/media/json-jackson/src/main/resources/META-INF/NOTICE.markdown @@ -31,7 +31,7 @@ The project maintains the following source code repositories: ## Third-party Content -Jackson JAX-RS Providers version 2.17.0 +Jackson JAX-RS Providers version 2.17.1 * License: Apache License, 2.0 * Project: https://github.com/FasterXML/jackson-jaxrs-providers * Copyright: (c) 2009-2023 FasterXML, LLC. All rights reserved unless otherwise indicated. diff --git a/media/moxy/pom.xml b/media/moxy/pom.xml index 1e8df0a2ab..9c593909d0 100644 --- a/media/moxy/pom.xml +++ b/media/moxy/pom.xml @@ -66,21 +66,6 @@ - - de.jflex - maven-jflex-plugin - 1.4.3 - - - - generate - - - ${project.build.directory}/generated-sources/rsrc-gen - - - - diff --git a/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/EventProcessor.java b/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/EventProcessor.java index 00e9fe7a4c..ee9f56d584 100644 --- a/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/EventProcessor.java +++ b/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/EventProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -24,6 +24,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -87,6 +88,10 @@ public class EventProcessor implements Runnable, EventListener { * A map of listeners bound to receive only events of a particular name. */ private final Map> boundListeners; + /** + * A list of Error Consumers. + */ + private final List> throwableConsumers; /** * Shutdown handler is invoked when Event processor reaches terminal stage. @@ -111,6 +116,7 @@ private EventProcessor(final EventProcessor that) { this.unboundListeners = that.unboundListeners; this.eventListener = that.eventListener; this.shutdownHandler = that.shutdownHandler; + this.throwableConsumers = that.throwableConsumers; } private EventProcessor(Builder builder) { @@ -128,6 +134,7 @@ private EventProcessor(Builder builder) { this.unboundListeners = builder.unboundListeners == null ? Collections.EMPTY_LIST : builder.unboundListeners; this.eventListener = builder.eventListener; this.shutdownHandler = builder.shutdownHandler; + this.throwableConsumers = builder.throwableConsumers; } /** @@ -199,6 +206,16 @@ public void run() { } // if we're here, an unrecoverable error has occurred - just turn off the lights... shutdownHandler.shutdown(); + // and notify error handlers + if (throwableConsumers != null) { + for (Consumer consumer : throwableConsumers) { + try { + consumer.accept(ex); + } catch (Throwable throwable) { + LOGGER.fine(String.format("User throwable ignored: %s", throwable.getMessage())); + } + } + } } finally { if (eventInput != null && !eventInput.isClosed()) { eventInput.close(); @@ -357,6 +374,7 @@ public static class Builder { private boolean disableKeepAlive; private List unboundListeners; private Map> boundListeners; + private List> throwableConsumers = null; private Builder(WebTarget target, AtomicReference state, @@ -420,6 +438,17 @@ public Builder disableKeepAlive() { return this; } + /** + * Set the consumers of {@link Throwable} occurring during connection. + * + * @param throwableConsumers a list of consumers of throwable. + * @return updated builder instance. + */ + public Builder throwableConsumers(List> throwableConsumers) { + this.throwableConsumers = throwableConsumers; + return this; + } + /** * Build the {@link EventProcessor}. * diff --git a/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/JerseySseEventSource.java b/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/JerseySseEventSource.java index 4f5e87a0ab..501b95f493 100644 --- a/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/JerseySseEventSource.java +++ b/media/sse/src/main/java/org/glassfish/jersey/media/sse/internal/JerseySseEventSource.java @@ -16,6 +16,8 @@ package org.glassfish.jersey.media.sse.internal; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.concurrent.Flow; import java.util.concurrent.TimeUnit; @@ -72,6 +74,10 @@ public class JerseySseEventSource implements SseEventSource { * Client provided executor facade. */ private final ClientExecutor clientExecutor; + /** + * List of Throwable consumers passed to EventProcessor.Builder. + */ + private final List> throwableConsumers = new ArrayList<>(); /** * Private constructor. @@ -110,11 +116,13 @@ public void register(final Consumer onEvent) { public void register(final Consumer onEvent, final Consumer onError) { this.subscribe(DEFAULT_SUBSCRIPTION_HANDLER, onEvent, onError, () -> { }); + throwableConsumers.add(onError); } @Override public void register(final Consumer onEvent, final Consumer onError, final Runnable onComplete) { this.subscribe(DEFAULT_SUBSCRIPTION_HANDLER, onEvent, onError, onComplete); + throwableConsumers.add(onError); } private void subscribe(final Consumer onSubscribe, @@ -173,6 +181,7 @@ public void open() { EventProcessor processor = EventProcessor .builder(endpoint, state, clientExecutor, this::onEvent, this::close) .reconnectDelay(reconnectDelay, reconnectTimeUnit) + .throwableConsumers(throwableConsumers) .build(); clientExecutor.submit(processor); diff --git a/media/sse/src/test/java/org/glassfish/jersey/media/sse/SseEventSourceRegisterErrorHandlerTest.java b/media/sse/src/test/java/org/glassfish/jersey/media/sse/SseEventSourceRegisterErrorHandlerTest.java new file mode 100644 index 0000000000..52cdcc06a6 --- /dev/null +++ b/media/sse/src/test/java/org/glassfish/jersey/media/sse/SseEventSourceRegisterErrorHandlerTest.java @@ -0,0 +1,128 @@ +/* + * 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.media.sse; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.InternalServerErrorException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.sse.InboundSseEvent; +import jakarta.ws.rs.sse.Sse; +import jakarta.ws.rs.sse.SseEventSink; +import jakarta.ws.rs.sse.SseEventSource; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +public class SseEventSourceRegisterErrorHandlerTest extends JerseyTest { + @Path("sse") + public static class SseEventSourceRegisterTestSseEndpoint { + + @Path("hello") + @GET + @Produces(SseFeature.SERVER_SENT_EVENTS) + public void hello(@Context SseEventSink output, @Context Sse sse) throws InterruptedException { + output.send(sse.newEvent("HELLO")); + } + + @Path("close") + @GET + @Produces(SseFeature.SERVER_SENT_EVENTS) + public void close(@Context SseEventSink output, @Context Sse sse) throws IOException { + output.close(); + } + + @Path("500") + @GET + @Produces(SseFeature.SERVER_SENT_EVENTS) + public void throw500(@Context SseEventSink output, @Context Sse sse) throws InterruptedException { + throw new WebApplicationException(); + } + + @Path("400") + @GET + @Produces(SseFeature.SERVER_SENT_EVENTS) + public void throw400(@Context SseEventSink output, @Context Sse sse) throws InterruptedException { + throw new BadRequestException(); + } + } + + @Override + protected Application configure() { + return new ResourceConfig(SseEventSourceRegisterTestSseEndpoint.class); + } + + private static final Consumer EMPTY = event -> { + }; + + @Test + public void testConnection404() throws InterruptedException { + WebTarget sseTarget = target("sse"); + AtomicReference throwable = new AtomicReference<>(); + CountDownLatch completeLatch = new CountDownLatch(1); + + SseEventSource eventSource = SseEventSource.target(sseTarget).build(); + eventSource.register(EMPTY, throwable::set, completeLatch::countDown); + eventSource.open(); + completeLatch.await(10_000, TimeUnit.MILLISECONDS); + MatcherAssert.assertThat(throwable.get(), Matchers.notNullValue()); + MatcherAssert.assertThat(throwable.get().getClass(), Matchers.is(NotFoundException.class)); + } + + @Test + public void testError500() throws InterruptedException { + WebTarget sseTarget = target("sse/500"); + AtomicReference throwable = new AtomicReference<>(); + CountDownLatch completeLatch = new CountDownLatch(1); + + SseEventSource eventSource = SseEventSource.target(sseTarget).build(); + eventSource.register(EMPTY, throwable::set, completeLatch::countDown); + eventSource.open(); + completeLatch.await(10_000, TimeUnit.MILLISECONDS); + MatcherAssert.assertThat(throwable.get(), Matchers.notNullValue()); + MatcherAssert.assertThat(throwable.get().getClass(), Matchers.is(InternalServerErrorException.class)); + } + + @Test + public void testError400() throws InterruptedException { + WebTarget sseTarget = target("sse/400"); + AtomicReference throwable = new AtomicReference<>(); + CountDownLatch completeLatch = new CountDownLatch(1); + + SseEventSource eventSource = SseEventSource.target(sseTarget).build(); + eventSource.register(EMPTY, throwable::set, completeLatch::countDown); + eventSource.open(); + completeLatch.await(10_000, TimeUnit.MILLISECONDS); + MatcherAssert.assertThat(throwable.get(), Matchers.notNullValue()); + MatcherAssert.assertThat(throwable.get().getClass(), Matchers.is(BadRequestException.class)); + } +} diff --git a/pom.xml b/pom.xml index ada126d97b..f893990bde 100644 --- a/pom.xml +++ b/pom.xml @@ -2134,7 +2134,7 @@ 3.0.3 3.0.1 3.2.6 - 3.2.6 + 3.2.8 3.7.1 33.1.0-jre @@ -2142,10 +2142,11 @@ 2.10.0 4.5.14 5.3.1 - 2.17.0 + 2.17.1 3.30.2-GA - 3.5.3.Final 1.3.7 + 3.3.2.Final + 3.6.0.Final 1.37 1.49 4.13.2 diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/ResponseReadAndBufferEntityTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/ResponseReadAndBufferEntityTest.java index 8b54346eac..951de51e5c 100644 --- a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/ResponseReadAndBufferEntityTest.java +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/ResponseReadAndBufferEntityTest.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 @@ -35,6 +35,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.glassfish.jersey.innate.io.InputStreamWrapper; import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; @@ -57,7 +58,7 @@ public class ResponseReadAndBufferEntityTest extends JerseyTest { private static final Logger LOGGER = Logger.getLogger(ResponseReadAndBufferEntityTest.class.getName()); - public static class CorruptableInputStream extends InputStream { + public static class CorruptableInputStream extends InputStreamWrapper { private final AtomicInteger closeCounter = new AtomicInteger(0); @@ -71,53 +72,16 @@ public CorruptableInputStream() { } @Override - public synchronized int read() throws IOException { - if (corruptRead) { - corrupt(); - } - return delegate.read(); - } - - @Override - public int read(final byte[] b) throws IOException { - if (corruptRead) { - corrupt(); - } - return delegate.read(b); + protected InputStream getWrapped() { + return delegate; } @Override - public int read(final byte[] b, final int off, final int len) throws IOException { + protected InputStream getWrappedIOE() throws IOException { if (corruptRead) { corrupt(); } - return delegate.read(b, off, len); - } - - @Override - public long skip(final long n) throws IOException { - if (corruptRead) { - corrupt(); - } - return delegate.skip(n); - } - - @Override - public int available() throws IOException { - if (corruptRead) { - corrupt(); - } - return delegate.available(); - } - - @Override - public boolean markSupported() { - return delegate.markSupported(); - } - - @Override - public void mark(final int readAheadLimit) { - delegate.mark(readAheadLimit); + return delegate; } @Override diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/MultiPartTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/MultiPartTest.java index 83a52dd8a3..f381d5fe9b 100644 --- a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/MultiPartTest.java +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/MultiPartTest.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 @@ -16,11 +16,18 @@ package org.glassfish.jersey.tests.e2e.client.connector; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; 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.spi.ConnectorProvider; import org.glassfish.jersey.jdk.connector.JdkConnectorProvider; import org.glassfish.jersey.jetty.connector.JettyConnectorProvider; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataMultiPart; +import org.glassfish.jersey.netty.connector.NettyConnectorProvider; import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.media.multipart.BodyPart; import org.glassfish.jersey.media.multipart.BodyPartEntity; @@ -32,6 +39,8 @@ import org.glassfish.jersey.test.JerseyTest; import org.glassfish.jersey.test.TestProperties; import org.glassfish.jersey.test.spi.TestHelper; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.Test; @@ -40,16 +49,25 @@ import jakarta.ws.rs.Consumes; 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.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; +import jakarta.ws.rs.ext.WriterInterceptor; +import jakarta.ws.rs.ext.WriterInterceptorContext; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.logging.Handler; import java.util.logging.Level; +import java.util.logging.LogManager; import java.util.logging.Logger; public class MultiPartTest { @@ -129,5 +147,72 @@ public void testMultipart() { } } } + + @Test + public void testNettyBufferedMultipart() { +// setDebugLevel(Level.FINEST); + ClientConfig config = new ClientConfig(); + + config.connectorProvider(new NettyConnectorProvider()); + config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED); + config.register(org.glassfish.jersey.media.multipart.MultiPartFeature.class); + config.register(new LoggingHandler(LogLevel.DEBUG)); + config.register(new LoggingInterceptor()); + config.property(ClientProperties.ASYNC_THREADPOOL_SIZE, 10); + config.property("jersey.config.client.logging.verbosity", LoggingFeature.Verbosity.PAYLOAD_TEXT); + config.property("jersey.config.client.logging.logger.level", Level.FINEST.toString()); + + Client client = ClientBuilder.newClient(config); + + FormDataMultiPart formData = new FormDataMultiPart(); + FormDataBodyPart bodyPart1 = new FormDataBodyPart("hello1", "{\"first\":\"firstLine\",\"second\":\"secondLine\"}", + MediaType.APPLICATION_JSON_TYPE); + formData.bodyPart(bodyPart1); + formData.bodyPart(new FormDataBodyPart("hello2", + "{\"first\":\"firstLine\",\"second\":\"secondLine\",\"third\":\"thirdLine\"}", + MediaType.APPLICATION_JSON_TYPE)); + formData.bodyPart(new FormDataBodyPart("hello3", + "{\"first\":\"firstLine\",\"second\":\"secondLine\",\"" + + "second\":\"secondLine\",\"second\":\"secondLine\",\"second\":\"secondLine\"}", + MediaType.APPLICATION_JSON_TYPE)); + formData.bodyPart(new FormDataBodyPart("plaintext", "hello")); + + Response response1 = client.target(target().getUri()).path("upload") + .request() + .post(Entity.entity(formData, formData.getMediaType())); + + MatcherAssert.assertThat(response1.getStatus(), Matchers.is(200)); + MatcherAssert.assertThat(response1.readEntity(String.class), + Matchers.stringContainsInOrder("first", "firstLine", "second", "secondLine")); + response1.close(); + client.close(); + } + + public static void setDebugLevel(Level newLvl) { + Logger rootLogger = LogManager.getLogManager().getLogger(""); + Handler[] handlers = rootLogger.getHandlers(); + rootLogger.setLevel(newLvl); + for (Handler h : handlers) { + h.setLevel(Level.ALL); + } + Logger nettyLogger = Logger.getLogger("io.netty"); + nettyLogger.setLevel(Level.FINEST); + } + + @Provider + public class LoggingInterceptor implements WriterInterceptor { + + @Override + public void aroundWriteTo(WriterInterceptorContext context) + throws IOException, WebApplicationException { + try { + MultivaluedMap headers = context.getHeaders(); + headers.forEach((key, val) -> System.out.println(key + ":" + val)); + context.proceed(); + } catch (Exception e) { + throw e; + } + } + } } } diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/NoContentLengthTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/NoContentLengthTest.java new file mode 100644 index 0000000000..ce8600e7dc --- /dev/null +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/NoContentLengthTest.java @@ -0,0 +1,134 @@ +/* + * 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.client.connector; + +import org.glassfish.jersey.apache5.connector.Apache5ConnectorProvider; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.client.spi.ConnectorProvider; +import org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider; +import org.glassfish.jersey.jdk.connector.JdkConnectorProvider; +import org.glassfish.jersey.jetty.connector.JettyConnectorProvider; +import org.glassfish.jersey.netty.connector.NettyConnectorProvider; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class NoContentLengthTest { + + private static final String MSG = "12345678901234567890123456789012345678901234567890"; + + private static int port; + private static AtomicBoolean running = new AtomicBoolean(false); + + @BeforeEach + void beforeEach() { + while (!running.compareAndSet(false, true)) { + try { + Thread.sleep(1000L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + Runnable runnable = new Runnable() { + @Override + public void run() { + try { + String _port = System.getProperty("jersey.config.test.container.port"); + port = Integer.parseInt(_port == null || _port.isEmpty() ? "8080" : _port); + ServerSocket serverSocket = new ServerSocket(port); + System.err.println("Starting server on port : " + port); + + Socket clientSocket = serverSocket.accept(); + + BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())); + + String s; + while ((s = in.readLine()) != null) { + // System.out.println(s); + if (s.isEmpty()) { + break; + } + } + + out.write("HTTP/1.0 200 OK\r\n"); + out.write("Content-Type: text/plain\r\n"); + out.write("\r\n"); + out.write(MSG); + + out.close(); + in.close(); + clientSocket.close(); + serverSocket.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + running.set(false); + } + } + }; + Thread newThread = new Thread(runnable); + newThread.start(); + } + + public static List providers() { + return Arrays.asList( + new Apache5ConnectorProvider(), + new HttpUrlConnectorProvider(), + new NettyConnectorProvider(), + new JettyConnectorProvider(), + new GrizzlyConnectorProvider(), + new JdkConnectorProvider() + ); + } + + @ParameterizedTest + @MethodSource("providers") + public void testNoContentLength(ConnectorProvider connectorProvider) { + try (Response r = target(connectorProvider).request().get()) { + MatcherAssert.assertThat(r.getStatus(), Matchers.is(200)); + MatcherAssert.assertThat(r.getHeaderString(HttpHeaders.CONTENT_LENGTH), Matchers.nullValue()); + MatcherAssert.assertThat(r.hasEntity(), Matchers.is(true)); + MatcherAssert.assertThat(r.readEntity(String.class), Matchers.is(MSG)); + } + } + + private WebTarget target(ConnectorProvider connectorProvider) { + ClientConfig config = new ClientConfig(); + config.connectorProvider(connectorProvider); + return ClientBuilder.newClient(config).target("http://localhost:" + port); + } +} diff --git a/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/entity/ChunkedInputReaderTest.java b/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/entity/ChunkedInputReaderTest.java new file mode 100644 index 0000000000..5c0c944b26 --- /dev/null +++ b/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/entity/ChunkedInputReaderTest.java @@ -0,0 +1,100 @@ +/* + * 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.entity; + +import org.glassfish.jersey.client.ChunkedInput; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.WebApplicationException; +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.GenericType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ReaderInterceptor; +import jakarta.ws.rs.ext.ReaderInterceptorContext; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ChunkedInputReaderTest extends JerseyTest { + + @Path("/") + public static class ChunkedInputReaderTestResource { + @GET + public String get() { + return "To_be_replaced_by_client_reader"; + } + } + + @Override + protected Application configure() { + return new ResourceConfig(ChunkedInputReaderTestResource.class); + } + + @Test + public void testChunkedInputStreamIsClosed() { + AtomicBoolean closed = new AtomicBoolean(false); + InputStream inputStream = new ByteArrayInputStream("TEST".getBytes()) { + @Override + public void close() throws IOException { + closed.set(true); + super.close(); + } + }; + + final GenericType> chunkedInputGenericType = new GenericType(new ParameterizedType() { + @Override + public Type[] getActualTypeArguments() { + return new Type[]{String.class}; + } + + @Override + public Type getRawType() { + return ChunkedInput.class; + } + + @Override + public Type getOwnerType() { + return ChunkedInput.class; + } + }); + + + ChunkedInput response = target().register(new ReaderInterceptor() { + @Override + public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException { + context.setInputStream(inputStream); + return context.proceed(); + }; + }).request().get(chunkedInputGenericType); + MatcherAssert.assertThat(response.read(), Matchers.is("TEST")); + response.close(); + MatcherAssert.assertThat(closed.get(), Matchers.is(true)); + } +} diff --git a/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/entity/EntityTypesTest.java b/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/entity/EntityTypesTest.java index 77a74a6e9e..9e872fc6d6 100644 --- a/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/entity/EntityTypesTest.java +++ b/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/entity/EntityTypesTest.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 @@ -72,6 +72,7 @@ import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap; import org.glassfish.jersey.jettison.JettisonFeature; import org.glassfish.jersey.message.internal.FileProvider; +import org.glassfish.jersey.message.internal.PathProvider; import org.glassfish.jersey.server.ResourceConfig; import org.codehaus.jettison.json.JSONArray; @@ -431,6 +432,20 @@ public void testFileRepresentation() throws IOException { _test(in, FileResource.class); } + @Path("PathResource") + public static class PathResource extends AResource { + } + + @Test + @Execution(ExecutionMode.CONCURRENT) + public void testPathRepresentation() throws IOException { + final var pp = new PathProvider(); + final var in = pp.readFrom(java.nio.file.Path.class, java.nio.file.Path.class, null, null, null, + new ByteArrayInputStream("CONTENT".getBytes())); + + _test(in, PathResource.class); + } + @Produces("application/x-www-form-urlencoded") @Consumes("application/x-www-form-urlencoded") @Path("FormResource") diff --git a/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/sse/BroadcasterExecutorTest.java b/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/sse/BroadcasterExecutorTest.java index 8af4ef7d9e..0f7b854af8 100644 --- a/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/sse/BroadcasterExecutorTest.java +++ b/tests/e2e-entity/src/test/java/org/glassfish/jersey/tests/e2e/sse/BroadcasterExecutorTest.java @@ -157,6 +157,7 @@ private static class CustomClientAsyncExecutor extends ThreadPoolExecutorProvide @Test public void test() throws InterruptedException { final String[] onEventThreadName = {""}; + final CountDownLatch onEventLatch = new CountDownLatch(1); SseEventSource eventSource = SseEventSource .target(target().path("sse/events")) .build(); @@ -164,6 +165,7 @@ public void test() throws InterruptedException { eventSource.register((event) -> { LOGGER.info("Event: " + event + " from: " + Thread.currentThread().getName()); onEventThreadName[0] = Thread.currentThread().getName(); + onEventLatch.countDown(); } ); @@ -179,6 +181,7 @@ public void test() throws InterruptedException { Assertions.assertTrue(sendThreadOk, "send either not invoked at all or from wrong thread"); Assertions.assertTrue(onCompleteThreadOk, "onComplete either not invoked at all or from wrong thread"); + Assertions.assertTrue(onEventLatch.await(2_000L, TimeUnit.MILLISECONDS)); Assertions.assertTrue(onEventThreadName[0].startsWith("custom-client-executor"), "Client event called from wrong thread ( " + onEventThreadName[0] + ")"); } diff --git a/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/wadl/WadlResourceTest.java b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/wadl/WadlResourceTest.java index 749eb22ebd..899d9ca38b 100644 --- a/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/wadl/WadlResourceTest.java +++ b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/wadl/WadlResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2023 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 @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutionException; @@ -108,6 +109,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -399,6 +401,20 @@ public void testLastModifiedGET() { assertTrue(r.getHeaders().containsKey("Last-modified")); } + @Test + public void testLastModifiedGETOnJPLocale() { + Locale defaultLocale = Locale.getDefault(); + try { + Locale.setDefault(new Locale("ja", "JP")); + final WebTarget target = target("/application.wadl"); + + final Response r = target.queryParam(WadlUtils.DETAILED_WADL_QUERY_PARAM, "true").request().get(Response.class); + assertDoesNotThrow(() -> r.getLastModified()); + } finally { + Locale.setDefault(defaultLocale); + } + } + @Test public void testLastModifiedOPTIONS() { final WebTarget target = target("/widgets/3/verbose"); diff --git a/tests/e2e-tls/pom.xml b/tests/e2e-tls/pom.xml index 9aa5f85923..1d5d2473c1 100644 --- a/tests/e2e-tls/pom.xml +++ b/tests/e2e-tls/pom.xml @@ -97,7 +97,7 @@ io.specto hoverfly-java-junit5 - 0.14.0 + 0.18.1 test diff --git a/tests/integration/jersey-2776/pom.xml b/tests/integration/jersey-2776/pom.xml index 0cff060ad8..4c6a2640b3 100644 --- a/tests/integration/jersey-2776/pom.xml +++ b/tests/integration/jersey-2776/pom.xml @@ -42,7 +42,7 @@ org.apache.cxf cxf-rt-rs-client - 3.0.3 + 3.5.8 test diff --git a/tests/jersey-tck/pom.tomcat.xml b/tests/jersey-tck/pom.tomcat.xml index 081c7731f9..a6e71a9349 100644 --- a/tests/jersey-tck/pom.tomcat.xml +++ b/tests/jersey-tck/pom.tomcat.xml @@ -702,7 +702,7 @@ jersey-tck - 3.1.99-SNAPSHOT + 4.0.99-SNAPSHOT diff --git a/tests/jersey-tck/pom.xml b/tests/jersey-tck/pom.xml index d8f6816ece..b6254ccd91 100644 --- a/tests/jersey-tck/pom.xml +++ b/tests/jersey-tck/pom.xml @@ -1,7 +1,7 @@ + 4.0.99-SNAPSHOT diff --git a/tests/performance/runners/jersey-grizzly-runner/pom.xml b/tests/performance/runners/jersey-grizzly-runner/pom.xml index 8f61ad48b8..4584d61980 100644 --- a/tests/performance/runners/jersey-grizzly-runner/pom.xml +++ b/tests/performance/runners/jersey-grizzly-runner/pom.xml @@ -49,7 +49,7 @@ org.apache.maven.plugins maven-jar-plugin - 2.4 + ${jar.mvn.plugin.version} true diff --git a/tests/performance/test-cases/monitoring/pom.xml b/tests/performance/test-cases/monitoring/pom.xml index c4076c2a7d..d5b84bd93c 100644 --- a/tests/performance/test-cases/monitoring/pom.xml +++ b/tests/performance/test-cases/monitoring/pom.xml @@ -37,13 +37,13 @@ com.yammer.metrics metrics-core - 2.1.2 + 2.2.0 org.junit.jupiter junit-jupiter - 5.9.1 + 5.10.2 test @@ -56,13 +56,13 @@ commons-codec commons-codec - 1.5 + 1.17.0 org.slf4j slf4j-jdk14 - 1.6.1 + 2.0.13 @@ -97,11 +97,11 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 + 3.13.0 true - 1.7 - 1.7 + 1.8 + 1.8 false false diff --git a/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/ClassVersionChecker.java b/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/ClassVersionChecker.java index 743d609627..8a97941ab0 100644 --- a/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/ClassVersionChecker.java +++ b/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/ClassVersionChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -27,7 +27,14 @@ class ClassVersionChecker { static TestResult checkClassVersion(JarFile jar, JarEntry entry, Properties properties) throws IOException { final String jerseyVersion = MavenUtil.getJerseyVersion(properties); - final int minVersion = jerseyVersion.startsWith("3.1") ? 11 : 8; + final int minVersion; + if (jerseyVersion.startsWith("4")) { + minVersion = 17; + } else if (jerseyVersion.startsWith("3.1")) { + minVersion = 11; + } else { + minVersion = 8; + } return checkClassVersion(jar.getInputStream(entry), jar.getName() + File.separator + entry.getName(), minVersion); } diff --git a/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/DependencyResolver.java b/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/DependencyResolver.java index b1a7ee3bba..eeea9822e6 100644 --- a/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/DependencyResolver.java +++ b/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/DependencyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -42,4 +42,28 @@ static Artifact resolveArtifact(org.apache.maven.model.Dependency d, List remoteRepos, + RepositorySystem repoSystem, RepositorySystemSession repoSession) + throws ArtifactResolutionException { + DefaultArtifact artifact = new DefaultArtifact( + d.getGroupId(), d.getArtifactId(), "sources", d.getType(), d.getVersion() + ); + ArtifactRequest request = new ArtifactRequest(); + request.setArtifact(artifact); + request.setRepositories(remoteRepos); + return repoSystem.resolveArtifact(repoSession, request).getArtifact(); + } + + static Artifact resolveJavadoc(org.apache.maven.model.Dependency d, List remoteRepos, + RepositorySystem repoSystem, RepositorySystemSession repoSession) + throws ArtifactResolutionException { + DefaultArtifact artifact = new DefaultArtifact( + d.getGroupId(), d.getArtifactId(), "javadoc", d.getType(), d.getVersion() + ); + ArtifactRequest request = new ArtifactRequest(); + request.setArtifact(artifact); + request.setRepositories(remoteRepos); + return repoSystem.resolveArtifact(repoSession, request).getArtifact(); + } } \ No newline at end of file diff --git a/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/MavenUtil.java b/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/MavenUtil.java index 20e77377b7..98d4616fcf 100644 --- a/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/MavenUtil.java +++ b/tests/release-test/src/main/java/org/glassfish/jersey/test/artifacts/MavenUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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,6 +29,7 @@ import java.util.Collections; import java.util.List; import java.util.Properties; +import java.util.stream.Collectors; import java.util.stream.Stream; public final class MavenUtil { @@ -37,7 +38,7 @@ public final class MavenUtil { private static final String PROJECT_VERSION = "project.version"; static File getArtifactJar(File repositoryRoot, Dependency dependency, Properties properties) { - return getArtifactFile(repositoryRoot, dependency, properties, "jar"); + return getArtifactFile(repositoryRoot, dependency, properties, dependency.getType()); } private static File getArtifactFile(File repositoryRoot, Dependency dependency, Properties properties, String extension) { @@ -52,7 +53,11 @@ private static File getArtifactFile(File repositoryRoot, Dependency dependency, } String version = MavenUtil.getDependencyVersion(dependency, properties); fileSuffix.append(version).append(File.separator); - fileSuffix.append(dependency.getArtifactId()).append('-').append(version).append(".").append(extension); + fileSuffix.append(dependency.getArtifactId()).append('-').append(version); + if (dependency.getClassifier() != null) { + fileSuffix.append('-').append(dependency.getClassifier()); + } + fileSuffix.append(".").append(extension); return new File(repositoryRoot, fileSuffix.toString()); } @@ -103,7 +108,16 @@ static Stream keepJerseyJars(Stream stream, DependencyPa static Stream streamJerseyJars() throws IOException, XmlPullParserException { Model model = getModelFromFile("pom.xml"); List deps = getBomPomDependencies(model); + return streamJerseyJars(deps); + } + static Stream streamJerseySources() throws IOException, XmlPullParserException { + Model model = getModelFromFile("pom.xml"); + List deps = getBomPomSources(model); + return streamJerseyJars(deps); + } + + private static Stream streamJerseyJars(List deps) throws IOException, XmlPullParserException { return deps.stream() .filter(dep -> dep.getGroupId().startsWith("org.glassfish.jersey")) .filter(dep -> dep.getType().equals("jar")); @@ -139,6 +153,15 @@ private static List getBomPomDependencies(Model model) throws IOExce return bomPomModel.getDependencyManagement().getDependencies(); } + private static List getBomPomSources(Model model) throws XmlPullParserException, IOException { + return getBomPomDependencies(model).stream() + .map(dependency -> { + dependency.setClassifier("sources"); + return dependency; + }) + .collect(Collectors.toList()); + } + static String getJerseyVersion(Properties properties) { String property = properties.getProperty(JERSEY_VERSION); // when it is in the pom.file if (property == null || property.startsWith("${")) { diff --git a/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/ArchetypesTest.java b/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/ArchetypesTest.java index b42830ba54..5be312c314 100644 --- a/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/ArchetypesTest.java +++ b/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/ArchetypesTest.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 @@ -52,16 +52,21 @@ public void testPropertiesVersion() throws XmlPullParserException, IOException { continue; } // Update the names with the ones in Jersey - Map.Entry updatedEntry = updateEntry(pomEntry); // Check the properties are there - if (properties.getProperty(updatedEntry.getKey().toString()) == null) { + final String key = pomEntry.getKey().toString(); + + if (properties.getProperty(key) == null) { testResult.ok().append("Property ") .append(pomEntry.getKey().toString()) .append(" from ").append(pom).println(" not in Jersey"); failed = true; } // check the values - else if (!properties.getProperty(updatedEntry.getKey().toString()).equals(updatedEntry.getValue())) { + else if ( + //archetype property value can be a variable from the main pom.xml - check and exclude if so + !(properties.containsKey(key) && pomEntry.getValue().toString().contains(key)) + && !properties.getProperty(key).equals(pomEntry.getValue()) + ) { testResult.exception().append("The property ") .append(pomEntry.getKey().toString()) .append(" in archetype pom ") @@ -81,25 +86,4 @@ else if (!properties.getProperty(updatedEntry.getKey().toString()).equals(update } } - private Map.Entry updateEntry(Map.Entry pomEntry) { - if (pomEntry.getKey().equals("junit-jupiter.version")) { - return new Map.Entry() { - @Override - public Object getKey() { - return "junit5.version"; - } - - @Override - public Object getValue() { - return pomEntry.getValue(); - } - - @Override - public Object setValue(Object value) { - return value; - } - }; - } - return pomEntry; - } } diff --git a/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/DownloadBomPomDependencies.java b/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/DownloadBomPomDependencies.java index 99c94eee3b..6a74f0787c 100644 --- a/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/DownloadBomPomDependencies.java +++ b/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/DownloadBomPomDependencies.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -58,6 +58,12 @@ public void testDownloadBomPomDependencies() throws Exception { Artifact m = mavenEnvironment.resolveArtifact(member); System.out.append("Resolved ").append(member.getGroupId()).append(":").append(member.getArtifactId()).append(":") .append(member.getVersion()).append(" to ").println(m.getFile().getName()); + m = mavenEnvironment.resolveSource(member); + System.out.append("Resolved sources ").append(member.getGroupId()).append(":").append(member.getArtifactId()) + .append(":").append(member.getVersion()).append(" to ").println(m.getFile().getName()); + m = mavenEnvironment.resolveJavadoc(member); + System.out.append("Resolved javadoc ").append(member.getGroupId()).append(":").append(member.getArtifactId()) + .append(":").append(member.getVersion()).append(" to ").println(m.getFile().getName()); } } @@ -74,6 +80,14 @@ public void testDownloadNonBomPomDependencies() throws Exception { System.out.append("Resolved ").append(dependency.getGroupId()).append(":") .append(dependency.getArtifactId()).append(":") .append(dependency.getVersion()).append(" to ").println(m.getFile().getName()); + m = mavenEnvironment.resolveSource(dependency); + System.out.append("Resolved source ").append(dependency.getGroupId()).append(":") + .append(dependency.getArtifactId()).append(":") + .append(dependency.getVersion()).append(" to ").println(m.getFile().getName()); + m = mavenEnvironment.resolveJavadoc(dependency); + System.out.append("Resolved javadoc ").append(dependency.getGroupId()).append(":") + .append(dependency.getArtifactId()).append(":") + .append(dependency.getVersion()).append(" to ").println(m.getFile().getName()); } } @@ -102,6 +116,16 @@ Artifact resolveArtifact(Dependency dependency) throws ArtifactResolutionExcepti return DependencyResolver.resolveArtifact(dependency, remoteRepos, repositorySystem, repoSession); } + Artifact resolveSource(Dependency dependency) throws ArtifactResolutionException { + dependency.setVersion(jerseyVersion); + return DependencyResolver.resolveSource(dependency, remoteRepos, repositorySystem, repoSession); + } + + Artifact resolveJavadoc(Dependency dependency) throws ArtifactResolutionException { + dependency.setVersion(jerseyVersion); + return DependencyResolver.resolveJavadoc(dependency, remoteRepos, repositorySystem, repoSession); + } + private List getRemoteRepositories() throws Exception { MavenProject project = getMavenProjectForResourceFile("/release-test-pom.xml"); List remoteArtifactRepositories = project.getRemoteProjectRepositories(); diff --git a/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/LegalDocsIncludedTest.java b/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/LegalDocsIncludedTest.java index 74e0281a84..8c784af933 100644 --- a/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/LegalDocsIncludedTest.java +++ b/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/LegalDocsIncludedTest.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 @@ -41,7 +41,15 @@ public void testLegalFiles() throws IOException, XmlPullParserException { List jars = MavenUtil.streamJerseyJars() .map(dependency -> MavenUtil.getArtifactJar(localRepository, dependency, properties)) .collect(Collectors.toList()); + testLegalFiles(jars, testResult); + jars = MavenUtil.streamJerseySources() + .map(dependency -> MavenUtil.getArtifactJar(localRepository, dependency, properties)) + .collect(Collectors.toList()); + testLegalFiles(jars, testResult); + } + + private void testLegalFiles(List jars, TestResult testResult) throws IOException { for (File jar : jars) { for (String filename : new String[]{LICENSE_FILE, NOTICE_FILE}) { JarFile jarFile = new JarFile(jar); diff --git a/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/ManifestTest.java b/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/ManifestTest.java index 0c77d69c3c..a5208681dc 100644 --- a/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/ManifestTest.java +++ b/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/ManifestTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -25,6 +25,7 @@ import java.util.Properties; import java.util.jar.JarFile; import java.util.stream.Collectors; +import java.util.zip.ZipEntry; public class ManifestTest { private static final File localRepository = MavenUtil.getLocalMavenRepository(); @@ -63,6 +64,25 @@ public void testHasOsgiManifest() throws IOException, XmlPullParserException { } } + for (File jar : jars) { + JarFile jarFile = new JarFile(jar); + String value = jarFile.getManifest().getMainAttributes().getValue("Multi-Release"); +// System.out.append("Accessing META-INF/versions").append(" of ").println(jar.getName()); + ZipEntry versions = jarFile.getEntry("META-INF/versions/"); + if (versions != null) { + if (!"true".equals(value)) { + testResult.exception().append("'Multi-Release: true' not set for ").println(jar.getName()); + } else { + testResult.ok().append("'Multi-Release: true' set for ").println(jar.getName()); + } + } else { + if ("true".equals(value)) { + testResult.exception().append("'Multi-Release: true' SET for ").println(jar.getName()); + } + } + + } + //Assertions.assertTrue(testResult.result(), "Some error occurred, see previous messages"); Assert.assertTrue("Some error occurred, see previous messages", testResult.result()); } diff --git a/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/MultiReleaseTest.java b/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/MultiReleaseTest.java index 08be0206fa..11a417e25f 100644 --- a/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/MultiReleaseTest.java +++ b/tests/release-test/src/test/java/org/glassfish/jersey/test/artifacts/MultiReleaseTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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,12 +40,14 @@ public class MultiReleaseTest { private static final DependencyPair[] jdk11multiRelease = jdk11multiRelease(properties); private static final DependencyPair[] jdk12multiRelease = jdk12multiRelease(properties); private static final DependencyPair[] jdk17multiRelease = jdk17multiRelease(properties); + private static final DependencyPair[] jdk21multiRelease = jdk21multiRelease(properties); @Test public void testIsJdkMultiRelease() throws IOException, XmlPullParserException { TestResult result = testJdkVersions("11", jdk11multiRelease); result.append(testJdkVersions("12", jdk12multiRelease)); result.append(testJdkVersions("17", jdk17multiRelease)); + result.append(testJdkVersions("21", jdk21multiRelease)); //Assertions.assertTrue(result.result(), "Some error occurred, see previous messages"); Assert.assertTrue("Some error occurred, see previous messages", result.result()); } @@ -54,6 +56,7 @@ private static TestResult testJdkVersions(String version, DependencyPair... depe throws XmlPullParserException, IOException { final TestResult result = new TestResult(); if (dependencies == null || dependencies.length == 0) { + System.out.append("No dependencies found for jdk ").println(version); return result; } @@ -81,6 +84,7 @@ private static TestResult testJdkVersions(String version, DependencyPair... depe result.exception().append("Not a multirelease jar ").append(jar.getName()).println("!"); } ZipEntry versions = jarFile.getEntry("META-INF/versions/" + version); + System.out.append("Accessing META-INF/versions/").append(version).append(" of ").println(jar.getName()); if (versions == null) { result.exception().append("No classes for JDK ").append(version).append(" for ").println(jar.getName()); } @@ -95,6 +99,26 @@ private static TestResult testJdkVersions(String version, DependencyPair... depe result.append(ClassVersionChecker.checkClassVersion(jarFile, jarEntry, properties)); } + // Verify that number of multirelease jars matches the expected dependencies + StringBuilder multi = new StringBuilder(); + int multiCnt = 0; + List allFiles = MavenUtil.streamJerseyJars() + .map(dependency -> MavenUtil.getArtifactJar(localRepository, dependency, properties)) + .collect(Collectors.toList()); + for (File jar : files) { + JarFile jarFile = new JarFile(jar); + if (jarFile.isMultiRelease()) { + multiCnt++; + multi.append("Multirelease jar ").append(jar.getName()).append('\n'); + } + } + if (files.size() == multiCnt) { + result.ok().println("There is expected number of multirelease jars"); + } else { + result.exception().println("There is unexpected number of multirelease jars:"); + result.exception().append(multi).println(""); + } + return result; } @@ -136,14 +160,40 @@ private static DependencyPair[] jdk12multiRelease(Properties properties) { private static DependencyPair[] jdk17multiRelease(Properties properties) { String jerseyVersion = MavenUtil.getJerseyVersion(properties); - if (jerseyVersion.startsWith("3")) { + if (jerseyVersion.startsWith("3.0")) { + return new DependencyPair[] { + new DependencyPair("org.glassfish.jersey.connectors", "jersey-helidon-connector"), + new DependencyPair("org.glassfish.jersey.connectors", "jersey-jetty-connector"), + new DependencyPair("org.glassfish.jersey.containers", "jersey-container-jetty-http"), + new DependencyPair("org.glassfish.jersey.ext", "jersey-spring6") + }; + } else if (jerseyVersion.startsWith("3")) { return new DependencyPair[] { new DependencyPair("org.glassfish.jersey.connectors", "jersey-helidon-connector"), new DependencyPair("org.glassfish.jersey.connectors", "jersey-jetty-connector"), + new DependencyPair("org.glassfish.jersey.connectors", "jersey-jetty-http2-connector"), new DependencyPair("org.glassfish.jersey.containers", "jersey-container-jetty-http"), + new DependencyPair("org.glassfish.jersey.containers", "jersey-container-jetty-http2"), + new DependencyPair("org.glassfish.jersey.test-framework.providers", "jersey-test-framework-provider-jetty"), + new DependencyPair("org.glassfish.jersey.test-framework.providers", + "jersey-test-framework-provider-jetty-http2"), new DependencyPair("org.glassfish.jersey.ext", "jersey-spring6") }; } return new DependencyPair[]{}; } + + private static DependencyPair[] jdk21multiRelease(Properties properties) { + String jerseyVersion = MavenUtil.getJerseyVersion(properties); + if (jerseyVersion.startsWith("4")) { + return new DependencyPair[]{ + new DependencyPair("org.glassfish.jersey.core", "jersey-common") + }; + } else { + return new DependencyPair[]{ + new DependencyPair("org.glassfish.jersey.bundles", "jaxrs-ri"), + new DependencyPair("org.glassfish.jersey.core", "jersey-common") + }; + } + } } diff --git a/tests/version-agnostic/pom.xml b/tests/version-agnostic/pom.xml index 5116491f28..6d76dbbcb9 100644 --- a/tests/version-agnostic/pom.xml +++ b/tests/version-agnostic/pom.xml @@ -1,7 +1,7 @@