diff --git a/NOTICE.md b/NOTICE.md index 3596992225..2818e63200 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 dc88fe13f3..af777dd4a0 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 33b1eca835..32e29698a4 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/apidocs/pom.xml b/bundles/apidocs/pom.xml index 709d10c7f5..b7c9921fca 100644 --- a/bundles/apidocs/pom.xml +++ b/bundles/apidocs/pom.xml @@ -125,7 +125,7 @@ org.glassfish javax.servlet - 3.1 + 3.1.1 org.glassfish.jersey.containers diff --git a/bundles/jaxrs-ri/pom.xml b/bundles/jaxrs-ri/pom.xml index d3eb5363b0..e6a7a7fb97 100644 --- a/bundles/jaxrs-ri/pom.xml +++ b/bundles/jaxrs-ri/pom.xml @@ -424,7 +424,7 @@ org.codehaus.mojo wagon-maven-plugin - 1.0-beta-4 + 2.0.2 false diff --git a/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java b/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java index d53401ecb7..dd3c4c43d8 100644 --- a/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java +++ b/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java @@ -60,6 +60,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; @@ -890,7 +891,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; @@ -899,58 +900,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/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 56f8dd5b07..d797f112a7 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())); } @@ -894,7 +895,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; @@ -903,58 +904,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/helidon-connector/pom.xml b/connectors/helidon-connector/pom.xml index 515e3b3291..8453f9b924 100644 --- a/connectors/helidon-connector/pom.xml +++ b/connectors/helidon-connector/pom.xml @@ -42,7 +42,7 @@ io.helidon.jersey helidon-jersey-connector - ${helidon.jersey.connector.version} + ${helidon.connector.version} provided 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/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/ServletContainer.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletContainer.java index 147ce8c39e..abde2ac29f 100644 --- a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletContainer.java +++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletContainer.java @@ -318,13 +318,33 @@ private void setResponseForInvalidUri(final HttpServletResponse response, final final Response.Status badRequest = Response.Status.BAD_REQUEST; if (webComponent.configSetStatusOverSendError) { response.reset(); - //noinspection deprecation - response.setStatus(badRequest.getStatusCode(), badRequest.getReasonPhrase()); + setStatus(response, badRequest.getStatusCode(), badRequest.getReasonPhrase()); } else { response.sendError(badRequest.getStatusCode(), badRequest.getReasonPhrase()); } } + /** + *

+ * Set status and reason-phrase if the API still contains the method. Otherwise, only a status is sent. + *

+ *

+ * It can happen the Servlet 6 API is used and the method is not there any longer. A proprietary API can be used, + * or the class is transformed to Jakarta using some transformer means. + *

+ * @param response the servlet {@link HttpServletResponse} + * @param statusCode the status code + * @param reasonPhrase the reason phrase + */ + public static void setStatus(HttpServletResponse response, int statusCode, String reasonPhrase) { + try { + // noinspection deprecation + response.setStatus(statusCode, reasonPhrase); + } catch (NoSuchMethodError noSuchMethodError) { + response.setStatus(statusCode); + } + } + @Override public void destroy() { super.destroy(); 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 59b1da9a23..6e795f1e8a 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 @@ -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 @@ -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.internal.ServiceFinderBinder; import org.glassfish.jersey.internal.inject.AbstractBinder; import org.glassfish.jersey.internal.inject.InjectionManager; @@ -401,8 +404,7 @@ public Integer get() { if (configSetStatusOverSendError) { servletResponse.reset(); - //noinspection deprecation - servletResponse.setStatus(status.getStatusCode(), status.getReasonPhrase()); + ServletContainer.setStatus(servletResponse, status.getStatusCode(), status.getReasonPhrase()); } else { servletResponse.sendError(status.getStatusCode(), status.getReasonPhrase()); } @@ -413,7 +415,7 @@ public Integer get() { } /** - * Initialize {@code ContainerRequest} instance to used used to handle {@code servletRequest}. + * Initialize {@code ContainerRequest} instance to used to handle {@code servletRequest}. */ private void initContainerRequest( final ContainerRequest requestContext, @@ -421,7 +423,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 31b8f93156..5188a1a975 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 @@ -37,6 +37,7 @@ import org.glassfish.jersey.server.ContainerResponse; import org.glassfish.jersey.server.internal.JerseyRequestTimeoutHandler; import org.glassfish.jersey.server.spi.ContainerResponseWriter; +import org.glassfish.jersey.servlet.ServletContainer; import org.glassfish.jersey.servlet.spi.AsyncContextDelegate; /** @@ -144,7 +145,7 @@ public OutputStream writeResponseStatusAndHeaders(final long contentLength, fina final String reasonPhrase = responseContext.getStatusInfo().getReasonPhrase(); if (reasonPhrase != null) { - response.setStatus(responseContext.getStatus(), reasonPhrase); + ServletContainer.setStatus(response, responseContext.getStatus(), reasonPhrase); } else { response.setStatus(responseContext.getStatus()); } @@ -214,12 +215,12 @@ 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(), "Request failed."); + ServletContainer.setStatus(response, statusCode, "Request failed."); } 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/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/pom.xml b/core-common/pom.xml index 56f0d056e9..46315aa9c8 100644 --- a/core-common/pom.xml +++ b/core-common/pom.xml @@ -674,7 +674,6 @@ org.apache.maven.plugins maven-source-plugin - 3.0.1 attach-sources 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/ReaderInterceptorExecutor.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/ReaderInterceptorExecutor.java index 91738c15fb..a0e5dfc364 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020 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 @@ -38,6 +38,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; @@ -248,7 +249,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; @@ -259,43 +260,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 synchronized void mark(final int i) { - original.mark(i); - } - - @Override - public synchronized void reset() throws IOException { - original.reset(); - } - - @Override - public boolean markSupported() { - return original.markSupported(); + protected InputStream getWrapped() { + return original; } @Override @@ -304,10 +270,6 @@ public void close() throws IOException { LOGGER.log(Level.FINE, LocalizationMessages.MBR_TRYING_TO_CLOSE_STREAM(reader.getClass())); } } - - private InputStream unwrap() { - return original; - } } /** @@ -320,7 +282,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-server/pom.xml b/core-server/pom.xml index f0af2e88f4..263ab483d8 100644 --- a/core-server/pom.xml +++ b/core-server/pom.xml @@ -233,7 +233,13 @@ org.jboss jboss-vfs - 3.2.6.Final + ${jboss.vfs.version} + test + + + org.jboss.logging + jboss-logging + ${jboss.logging.version} test @@ -246,6 +252,16 @@ + + jdk8 + + 1.8 + + + ${jboss.vfs.jdk8.version} + ${jboss.logging.8.version} + + securityOff 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 14e86e3eb5..8c116ca685 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2018 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 @@ -24,6 +24,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; @@ -109,52 +110,17 @@ public void reset() { @Override public InputStream open() { //noinspection NullableProblems - return new InputStream() { + 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 synchronized void mark(final int i) { - jarInputStream.mark(i); - } - - @Override - public synchronized void reset() throws IOException { - jarInputStream.reset(); - } - - @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 5a7188a2e0..98676812cc 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 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 @@ -21,6 +21,7 @@ import java.net.URI; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Locale; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -62,7 +63,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) { @@ -81,7 +82,7 @@ public synchronized 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/examples/NOTICE.md b/examples/NOTICE.md index e982511591..034205c6f3 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/extended-wadl-webapp/pom.xml b/examples/extended-wadl-webapp/pom.xml index 3feed37108..0b5f9f2db1 100644 --- a/examples/extended-wadl-webapp/pom.xml +++ b/examples/extended-wadl-webapp/pom.xml @@ -109,7 +109,7 @@ org.slf4j slf4j-log4j12 - 1.6.4 + 2.0.13 test diff --git a/examples/groovy/pom.xml b/examples/groovy/pom.xml index 8d7a370d85..0252257db2 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 f8e72a720c..44303095c9 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 5ec3b1c296..05b2447c27 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 + 2.0.13 test diff --git a/examples/pom.xml b/examples/pom.xml index 1798d4f1b2..8d25b08f5b 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -182,7 +182,7 @@ org.commonjava.maven.plugins directory-maven-plugin - 0.3.1 + 1.0 directories @@ -199,7 +199,7 @@ org.apache.maven.plugins maven-resources-plugin - 2.6 + ${resources.mvn.plugin.version} 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 181bb67105..cdb5680870 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 75a49a0399..6586b38ea5 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 335f80e509..5ae722eced 100644 --- a/media/moxy/pom.xml +++ b/media/moxy/pom.xml @@ -62,21 +62,6 @@
- - de.jflex - maven-jflex-plugin - 1.4.3 - - - - generate - - - ${project.build.directory}/generated-sources/rsrc-gen - - - - diff --git a/pom.xml b/pom.xml index 980af5de0e..b37f5c03e9 100644 --- a/pom.xml +++ b/pom.xml @@ -2234,7 +2234,7 @@ 3.0.3 3.0.1 3.2.6 - 3.2.6 + 3.2.8 1.4.14 3.7.1 @@ -2245,13 +2245,15 @@ org.jvnet.hk2.*;version="[2.5,4)" 4.5.14 5.3.1 - 2.17.0 + 2.17.1 3.30.2-GA - 3.5.3.Final 3.4.3.Final 1.19.3 ${jersey1.version} 1.3.7 + 3.3.2.Final + 3.2.17.Final + 3.6.0.Final 1.37 1.49 4.13.2 @@ -2321,7 +2323,7 @@ 3.0 3.0.0 org.eclipse.jetty.*;version="[11,15)" - 11.0.20 + 11.0.22 11.0.15 9.4.54.v20240208 11.0.20 diff --git a/tests/e2e-client/pom.xml b/tests/e2e-client/pom.xml index e0a036b36e..a8e9f41157 100644 --- a/tests/e2e-client/pom.xml +++ b/tests/e2e-client/pom.xml @@ -237,6 +237,7 @@ org/glassfish/jersey/tests/e2e/client/connector/proxy/Proxy*Test.java + org/glassfish/jersey/tests/e2e/client/connector/NoContentLengthTest.java org/glassfish/jersey/tests/e2e/client/nettyconnector/Expect100ContinueTest.java 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 3cb45b9c95..55376fa256 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,12 +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.internal.util.JdkVersion; 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; @@ -33,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; @@ -41,18 +49,26 @@ 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; -import java.util.stream.Stream; public class MultiPartTest { @@ -132,5 +148,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..e3c3caf80e --- /dev/null +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/NoContentLengthTest.java @@ -0,0 +1,136 @@ +/* + * 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.apache.connector.ApacheConnectorProvider; +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 ApacheConnectorProvider(), + 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-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 6c4907f014..f342c5f2ff 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, 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 @@ -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 a7a98de278..3f2bf1a18b 100644 --- a/tests/e2e-tls/pom.xml +++ b/tests/e2e-tls/pom.xml @@ -96,7 +96,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 c9fdda3360..82b05bb977 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/performance/runners/jersey-grizzly-runner/pom.xml b/tests/performance/runners/jersey-grizzly-runner/pom.xml index 1d26c297b8..03f7645660 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 288932a58c..317118d207 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,12 +160,38 @@ 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.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") + }; + } + } }