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 new file mode 100644 index 0000000000..e69de29bb2 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 f7c30a9fa3..8d93b9112a 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 @@ -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 @@ -32,8 +32,10 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -531,7 +533,7 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing try { final ConnectionClosingMechanism closingMechanism = new ConnectionClosingMechanism(clientRequest, request); - responseContext.setEntityStream(getInputStream(response, closingMechanism)); + responseContext.setEntityStream(getInputStream(response, closingMechanism, () -> clientRequest.isCancelled())); } catch (final IOException e) { LOGGER.log(Level.SEVERE, null, e); } @@ -741,13 +743,14 @@ private static Map writeOutBoundHeaders(final ClientRequest clie } private static InputStream getInputStream(final CloseableHttpResponse response, - final ConnectionClosingMechanism closingMechanism) throws IOException { + final ConnectionClosingMechanism closingMechanism, + final Supplier isCancelled) throws IOException { final InputStream inputStream; if (response.getEntity() == null) { inputStream = new ByteArrayInputStream(new byte[0]); } else { - final InputStream i = response.getEntity().getContent(); + final InputStream i = new CancellableInputStream(response.getEntity().getContent(), isCancelled); if (i.markSupported()) { inputStream = i; } else { @@ -889,4 +892,69 @@ protected void prepareSocket(SSLSocket socket) throws IOException { } } } + + private static class CancellableInputStream extends InputStream { + private final InputStream in; + private final Supplier isCancelled; + + private CancellableInputStream(InputStream in, Supplier isCancelled) { + this.in = in; + 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(); + } + + @Override + public synchronized void mark(int readlimit) { + in.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + checkAborted(); + in.reset(); + } + + private void checkAborted() throws IOException { + if (isCancelled.get()) { + throw new IOException(new CancellationException()); + } + } + } + } diff --git a/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpConnector.java b/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpConnector.java index 69d25eb66f..57296c6df9 100644 --- a/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpConnector.java +++ b/connectors/jnh-connector/src/main/java/org/glassfish/jersey/jnh/connector/JavaNetHttpConnector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -360,24 +360,30 @@ private FirstByteCachingStream(InputStream inner) { @Override public int read() throws IOException { lock.lock(); - final int r = zero != -1 ? zero : inner.read(); - zero = -1; - lock.unlock(); - return r; + try { + final int r = zero != -1 ? zero : inner.read(); + zero = -1; + return r; + } finally { + lock.unlock(); + } } @Override public int read(byte[] b, int off, int len) throws IOException { lock.lock(); int r; - if (zero != -1) { - b[off] = (byte) (zero & 0xFF); - r = inner.read(b, off + 1, len - 1); - } else { - r = inner.read(b, off, len); + try { + if (zero != -1) { + b[off] = (byte) (zero & 0xFF); + r = inner.read(b, off + 1, len - 1); + } else { + r = inner.read(b, off, len); + } + zero = -1; + } finally { + lock.unlock(); } - zero = -1; - lock.unlock(); return r; } @@ -385,23 +391,24 @@ public int read(byte[] b, int off, int len) throws IOException { @Override public int available() throws IOException { lock.lock(); - if (zero != -1) { - lock.unlock(); - return 1; - } + try { + if (zero != -1) { + return 1; + } - int available = inner.available(); - if (available != 1) { - lock.unlock(); - return available; - } + int available = inner.available(); + if (available != 1) { + return available; + } - zero = inner.read(); - if (zero == -1) { - available = 0; + zero = inner.read(); + if (zero == -1) { + available = 0; + } + return available; + } finally { + lock.unlock(); } - lock.unlock(); - return available; } @Override @@ -418,10 +425,14 @@ public boolean markSupported() { } @Override - public synchronized void mark(int readlimit) { + public void mark(int readlimit) { inner.mark(readlimit); } + @Override + public void reset() throws IOException { + inner.reset(); + } } } 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 4a9836d338..5d1d2f294c 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeoutException; import java.util.function.Predicate; @@ -158,6 +159,10 @@ protected void notifyResponse() { @Override public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) { + if (jerseyRequest.isCancelled()) { + responseAvailable.completeExceptionally(new CancellationException()); + return; + } if (msg instanceof HttpResponse) { final HttpResponse response = (HttpResponse) msg; jerseyResponse = new ClientResponse(new Response.StatusType() { 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 56cf0ea000..19810fa494 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 @@ -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 @@ -294,6 +294,7 @@ protected void service(final HttpServletRequest request, final HttpServletRespon final URI baseUri; final URI requestUri; try { + LOGGER.debugLog("ServletContainer.service(...) started"); baseUri = absoluteUriBuilder.replacePath(encodedBasePath).build(); String queryParameters = ContainerUtils.encodeUnsafeCharacters(request.getQueryString()); if (queryParameters == null) { @@ -530,6 +531,7 @@ private void doFilter(final HttpServletRequest request, final HttpServletRespons final URI baseUri; final URI requestUri; try { + LOGGER.debugLog("ServletContainer.doFilter(...) started"); final UriBuilder absoluteUriBuilder = UriBuilder.fromUri(request.getRequestURL().toString()); // depending on circumstances, use the correct path to replace in the absolute request URI diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java index 73229c7c8a..586033d78c 100644 --- a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java +++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java @@ -48,6 +48,7 @@ import org.glassfish.jersey.internal.inject.ReferencingFactory; import org.glassfish.jersey.internal.util.ExtendedLogger; import org.glassfish.jersey.internal.util.collection.Ref; +import org.glassfish.jersey.jetty.internal.LocalizationMessages; import org.glassfish.jersey.process.internal.RequestScoped; import org.glassfish.jersey.server.ApplicationHandler; import org.glassfish.jersey.server.ContainerException; @@ -137,6 +138,7 @@ public boolean handle(Request request, Response response, Callback callback) thr final ResponseWriter responseWriter = new ResponseWriter(request, response, callback, configSetStatusOverSendError); try { + LOGGER.debugLog(LocalizationMessages.CONTAINER_STARTED()); final URI baseUri = getBaseUri(request); final URI requestUri = getRequestUri(request, baseUri); final ContainerRequest requestContext = new ContainerRequest( diff --git a/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty-http/src/main/java11/org/glassfish/jersey/jetty/JettyHttpContainer.java new file mode 100644 index 0000000000..e69de29bb2 diff --git a/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties b/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties index e678f3aa16..f3109b361b 100644 --- a/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties +++ b/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties @@ -15,6 +15,7 @@ # # {0} - status code; {1} - status reason message +container.started=JettyHttpContainer.handle(...) started. exception.sending.error.response=I/O exception occurred while sending "{0}/{1}" error response. error.when.creating.server=Exception thrown when trying to create jetty server. unable.to.close.response=Unable to close response output. diff --git a/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty11-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java new file mode 100644 index 0000000000..e69de29bb2 diff --git a/containers/jetty11-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties b/containers/jetty11-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java index 9b0e9c1e48..9896c7a349 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 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 @@ -25,6 +25,10 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; @@ -90,6 +94,8 @@ public class ClientRequest extends OutboundMessageContext private LazyValue propertiesResolver = Values.lazy( (Value) () -> PropertiesResolver.create(getConfiguration(), getPropertiesDelegate()) ); + // by default nothing to be cancelled. + private Future cancellable = NotCancellable.INSTANCE; private static final Logger LOGGER = Logger.getLogger(ClientRequest.class.getName()); @@ -126,6 +132,7 @@ public ClientRequest(final ClientRequest original) { this.writerInterceptors = original.writerInterceptors; this.propertiesDelegate = new MapPropertiesDelegate(original.propertiesDelegate); this.ignoreUserAgent = original.ignoreUserAgent; + this.cancellable = original.cancellable; } @Override @@ -599,4 +606,66 @@ public boolean ignoreUserAgent() { public void ignoreUserAgent(final boolean ignore) { this.ignoreUserAgent = ignore; } + + /** + * Sets the new {@code Future} that may cancel this {@link ClientRequest}. + * @param cancellable + */ + void setCancellable(Future cancellable) { + this.cancellable = cancellable; + } + + /** + * Cancels this {@link ClientRequest}. May result in {@link java.util.concurrent.CancellationException} later in this + * request processing if this {@link ClientRequest} is backed by a {@link Future} provided to + * {@link JerseyInvocation.Builder#setCancellable(Future)}. + * @param mayInterruptIfRunning may have no effect or {@code true} if the thread executing this task should be interrupted + * (if the thread is known to the implementation); + * otherwise, in-progress tasks are allowed to complete + */ + public void cancel(boolean mayInterruptIfRunning) { + cancellable.cancel(mayInterruptIfRunning); + } + + /** + * Returns {@code true} if this {@link ClientRequest} was cancelled + * before it completed normally. + * + * @return {@code true} if this {@link ClientRequest} was cancelled + * before it completed normally + */ + public boolean isCancelled() { + return cancellable.isCancelled(); + } + + private static class NotCancellable implements Future { + public static final Future INSTANCE = new NotCancellable(); + private boolean isCancelled = false; + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + isCancelled = true; + return isCancelled; + } + + @Override + public boolean isCancelled() { + return isCancelled; + } + + @Override + public boolean isDone() { + return false; + } + + @Override + public Object get() throws InterruptedException, ExecutionException { + return null; + } + + @Override + public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return null; + } + } } diff --git a/core-client/src/main/java/org/glassfish/jersey/client/JerseyInvocation.java b/core-client/src/main/java/org/glassfish/jersey/client/JerseyInvocation.java index 8f97a3bab9..c131119ed3 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/JerseyInvocation.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/JerseyInvocation.java @@ -570,6 +570,18 @@ private T createRxInvoker(Class clazz throw new IllegalStateException( LocalizationMessages.CLIENT_RX_PROVIDER_NOT_REGISTERED(clazz.getSimpleName())); } + + /** + * Sets Future that backs {@link ClientRequest} {@link ClientRequest#isCancelled()} method. Can be used for instance + * by {@link CompletionStageRxInvoker} to pass the created {@link CompletableFuture} to the provided {@link SyncInvoker}. + * @param cancellable the {@link Future} whose result of {@link Future#cancel(boolean)} will be available by + * {@link ClientRequest#isCancelled()}. + * @return the updated builder. + */ + public Builder setCancellable(Future cancellable) { + requestContext.setCancellable(cancellable); + return this; + } } /* package */ static class AsyncInvoker extends CompletableFutureAsyncInvoker implements jakarta.ws.rs.client.AsyncInvoker { @@ -708,6 +720,8 @@ private T call(Producer producer, RequestScope scope) public Future submit() { final CompletableFuture responseFuture = new CompletableFuture<>(); final ClientRuntime runtime = request().getClientRuntime(); + + requestContext.setCancellable(responseFuture); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new InvocationResponseCallback<>(responseFuture, (request, scope) -> translate(request, scope, Response.class)))); @@ -722,6 +736,7 @@ public Future submit(final Class responseType) { final CompletableFuture responseFuture = new CompletableFuture<>(); final ClientRuntime runtime = request().getClientRuntime(); + requestContext.setCancellable(responseFuture); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new InvocationResponseCallback(responseFuture, (request, scope) -> translate(request, scope, responseType)))); @@ -761,6 +776,7 @@ public Future submit(final GenericType responseType) { final CompletableFuture responseFuture = new CompletableFuture<>(); final ClientRuntime runtime = request().getClientRuntime(); + requestContext.setCancellable(responseFuture); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new InvocationResponseCallback(responseFuture, (request, scope) -> translate(request, scope, responseType)))); @@ -885,6 +901,7 @@ public void failed(final ProcessingException error) { } }; final ClientRuntime runtime = request().getClientRuntime(); + requestContext.setCancellable(responseFuture); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), responseCallback)); } catch (final Throwable error) { final ProcessingException ce; diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java index 6e118ee29e..78a7111b37 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java @@ -56,6 +56,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -92,18 +93,23 @@ private class TypedInstances { private final MultivaluedMap> singletonInstances = new MultivaluedHashMap<>(); private final ThreadLocal>> threadInstances = new ThreadLocal<>(); private final List threadPredestroyables = Collections.synchronizedList(new LinkedList<>()); + private final ReentrantLock singletonInstancesLock = new ReentrantLock(); private List> _getSingletons(TYPE clazz) { List> si; - synchronized (singletonInstances) { + singletonInstancesLock.lock(); + try { si = singletonInstances.get(clazz); + } finally { + singletonInstancesLock.unlock(); } return si; } @SuppressWarnings("unchecked") T _addSingleton(TYPE clazz, T instance, Binding binding, Annotation[] qualifiers) { - synchronized (singletonInstances) { + singletonInstancesLock.lock(); + try { // check existing singleton with a qualifier already created by another thread io a meantime List> values = singletonInstances.get(clazz); if (values != null) { @@ -118,6 +124,8 @@ T _addSingleton(TYPE clazz, T instance, Binding binding, Annotation[] singletonInstances.add(clazz, new InstanceContext<>(instance, binding, qualifiers)); threadPredestroyables.add(instance); return instance; + } finally { + singletonInstancesLock.unlock(); } } 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 f5a34090d9..c89cf198e3 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.function.Supplier; @@ -155,7 +156,7 @@ public HttpUrlConnector( ); } - private static InputStream getInputStream(final HttpURLConnection uc) throws IOException { + private static InputStream getInputStream(final HttpURLConnection uc, final ClientRequest clientRequest) throws IOException { return new InputStream() { private final UnsafeValue in = Values.lazy(new UnsafeValue() { @Override @@ -190,6 +191,10 @@ private void throwIOExceptionIfClosed() throws IOException { if (closed) { throw new IOException("Stream closed"); } + if (clientRequest.isCancelled()) { + close(); + throw new IOException(new CancellationException()); + } } @Override @@ -311,7 +316,7 @@ protected void secureConnection(final JerseyClient client, final HttpURLConnecti if (DEFAULT_SSL_SOCKET_FACTORY.get() == suc.getSSLSocketFactory()) { // indicates that the custom socket factory was not set suc.setSSLSocketFactory(sslSocketFactory.get()); - } + } } } @@ -448,7 +453,7 @@ private ClientResponse _apply(final ClientRequest request) throws IOException { ); try { - InputStream inputStream = getInputStream(uc); + InputStream inputStream = getInputStream(uc, request); responseContext.setEntityStream(inputStream); } catch (IOException ioe) { // allow at least a partial response in a ResponseProcessingException diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/OsgiRegistry.java b/core-common/src/main/java/org/glassfish/jersey/internal/OsgiRegistry.java index cb7545f8ff..77ab1e1c21 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/OsgiRegistry.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/OsgiRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -37,7 +37,9 @@ import java.util.ResourceBundle; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; @@ -79,6 +81,7 @@ public final class OsgiRegistry implements SynchronousBundleListener { private final Map>>>> factories = new HashMap>>>>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private static final Lock INSTANCE_LOCK = new ReentrantLock(); private static OsgiRegistry instance; @@ -90,16 +93,21 @@ public final class OsgiRegistry implements SynchronousBundleListener { * * @return an {@code OsgiRegistry} instance. */ - public static synchronized OsgiRegistry getInstance() { - if (instance == null) { - final ClassLoader classLoader = AccessController - .doPrivileged(ReflectionHelper.getClassLoaderPA(ReflectionHelper.class)); - if (classLoader instanceof BundleReference) { - final BundleContext context = FrameworkUtil.getBundle(OsgiRegistry.class).getBundleContext(); - if (context != null) { // context could be still null if the current bundle has not been started - instance = new OsgiRegistry(context); + public static OsgiRegistry getInstance() { + INSTANCE_LOCK.lock(); + try { + if (instance == null) { + final ClassLoader classLoader = AccessController + .doPrivileged(ReflectionHelper.getClassLoaderPA(ReflectionHelper.class)); + if (classLoader instanceof BundleReference) { + final BundleContext context = FrameworkUtil.getBundle(OsgiRegistry.class).getBundleContext(); + if (context != null) { // context could be still null if the current bundle has not been started + instance = new OsgiRegistry(context); + } } } + } finally { + INSTANCE_LOCK.unlock(); } return instance; } diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java b/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java index 46184d8d09..03d15d31ec 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/ServiceFinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2018 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 @@ -33,6 +33,8 @@ import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; @@ -797,17 +799,20 @@ private void handleClassNotFoundException() throws ServiceConfigurationError { public abstract static class ServiceIteratorProvider { private static volatile ServiceIteratorProvider sip; - private static final Object sipLock = new Object(); + private static final Lock sipLock = new ReentrantLock(); private static ServiceIteratorProvider getInstance() { // TODO: check the following is a good practice: Double-check idiom for lazy initialization of fields. ServiceIteratorProvider result = sip; if (result == null) { // First check (no locking) - synchronized (sipLock) { + sipLock.lock(); + try { result = sip; if (result == null) { // Second check (with locking) sip = result = new DefaultServiceIteratorProvider(); } + } finally { + sipLock.unlock(); } } return result; @@ -819,8 +824,11 @@ private static void setInstance(final ServiceIteratorProvider sip) throws Securi final ReflectPermission rp = new ReflectPermission("suppressAccessChecks"); security.checkPermission(rp); } - synchronized (sipLock) { + sipLock.lock(); + try { ServiceIteratorProvider.sip = sip; + } finally { + sipLock.unlock(); } } diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Values.java b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Values.java index 47616447a8..5c3a7470e6 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Values.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Values.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019 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 @@ -16,6 +16,9 @@ package org.glassfish.jersey.internal.util.collection; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + /** * A collection of {@link Value Value provider} factory & utility methods. * @@ -297,25 +300,28 @@ public T get() { private static class LazyValueImpl implements LazyValue { - private final Object lock; + private final Lock lock; private final Value delegate; private volatile Value value; public LazyValueImpl(final Value delegate) { this.delegate = delegate; - this.lock = new Object(); + this.lock = new ReentrantLock(); } @Override public T get() { Value result = value; if (result == null) { - synchronized (lock) { + lock.lock(); + try { result = value; if (result == null) { value = result = Values.of(delegate.get()); } + } finally { + lock.unlock(); } } return result.get(); @@ -380,21 +386,22 @@ public static LazyUnsafeValue lazy(final UnsafeVa private static class LazyUnsafeValueImpl implements LazyUnsafeValue { - private final Object lock; + private final Lock lock; private final UnsafeValue delegate; private volatile UnsafeValue value; public LazyUnsafeValueImpl(final UnsafeValue delegate) { this.delegate = delegate; - this.lock = new Object(); + this.lock = new ReentrantLock(); } @Override public T get() throws E { UnsafeValue result = value; if (result == null) { - synchronized (lock) { + lock.lock(); + try { result = value; //noinspection ConstantConditions if (result == null) { @@ -406,6 +413,8 @@ public T get() throws E { } value = result; } + } finally { + lock.unlock(); } } return result.get(); diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java index bcd7e5fba7..14a008d82a 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/CookiesParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2021 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 @@ -168,7 +168,7 @@ public static NewCookie parseNewCookie(String header) { cookie.sameSite = NewCookie.SameSite.valueOf(value.toUpperCase()); } else if (param.startsWith("expires")) { try { - cookie.expiry = HttpDateFormat.readDate(value + ", " + bites[++i]); + cookie.expiry = HttpDateFormat.readDate(value + ", " + bites[++i].trim()); } catch (ParseException e) { LOGGER.log(Level.FINE, LocalizationMessages.ERROR_NEWCOOKIE_EXPIRES(value), e); } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/DateProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/DateProvider.java index 09e7553484..8417fb55eb 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/DateProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/DateProvider.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 @@ -43,7 +43,7 @@ public boolean supports(final Class type) { @Override public String toString(final Date header) { throwIllegalArgumentExceptionIfNull(header, LocalizationMessages.DATE_IS_NULL()); - return HttpDateFormat.getPreferredDateFormat().format(header); + return HttpDateFormat.getPreferredDateFormatter().format(header); } @Override diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpDateFormat.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpDateFormat.java index 75479e6846..9cf2abfd39 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpDateFormat.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpDateFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2019 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 @@ -18,12 +18,18 @@ import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Queue; import java.util.TimeZone; +import java.util.concurrent.ConcurrentLinkedQueue; /** * Helper class for HTTP specified date formats. @@ -33,6 +39,46 @@ */ public final class HttpDateFormat { + private static final boolean USE_SIMPLE_DATE_FORMAT_OVER_DATE_TIME_FORMATTER = true; + + /** + *

+ * A minimum formatter for converting java {@link Date} and {@link LocalDateTime} to {@code String} and vice-versa. + *

+ *

+ * Works as a facade for implementation backed by {@link SimpleDateFormat} and {@link DateTimeFormatter}. + *

+ */ + public static interface HttpDateFormatter { + /** + * + * @param date + * @return + */ + Date toDate(String date); + + /** + * + * @param date + * @return + */ + LocalDateTime toDateTime(String date); + /** + * Formats a {@link Date} into a date-time string. + * + * @param date the time value to be formatted into a date-time string. + * @return the formatted date-time string. + */ + String format(Date date); + /** + * Formats a {@link LocalDateTime} into a date-time string. + * + * @param dateTime the time value to be formatted into a date-time string. + * @return the formatted date-time string. + */ + String format(LocalDateTime dateTime); + } + private HttpDateFormat() { } /** @@ -50,39 +96,65 @@ private HttpDateFormat() { private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT"); - private static final ThreadLocal> dateFormats = new ThreadLocal>() { + private static final List dateFormats = createDateFormats(); + private static final Queue> simpleDateFormats = new ConcurrentLinkedQueue<>(); - @Override - protected synchronized List initialValue() { - return createDateFormats(); - } - }; + private static List createDateFormats() { + final HttpDateFormatter[] formats = new HttpDateFormatter[]{ + new HttpDateFormatterFromDateTimeFormatter( + DateTimeFormatter.ofPattern(RFC1123_DATE_FORMAT_PATTERN, Locale.US).withZone(GMT_TIME_ZONE.toZoneId())), + new HttpDateFormatterFromDateTimeFormatter( + DateTimeFormatter.ofPattern(RFC1123_DATE_FORMAT_PATTERN.replace("zzz", "ZZZ"), Locale.US) + .withZone(GMT_TIME_ZONE.toZoneId())), + new HttpDateFormatterFromDateTimeFormatter( + DateTimeFormatter.ofPattern(RFC1036_DATE_FORMAT_PATTERN, Locale.US).withZone(GMT_TIME_ZONE.toZoneId())), + new HttpDateFormatterFromDateTimeFormatter( + DateTimeFormatter.ofPattern(RFC1036_DATE_FORMAT_PATTERN.replace("zzz", "ZZZ"), Locale.US) + .withZone(GMT_TIME_ZONE.toZoneId())), + new HttpDateFormatterFromDateTimeFormatter( + DateTimeFormatter.ofPattern(ANSI_C_ASCTIME_DATE_FORMAT_PATTERN, Locale.US) + .withZone(GMT_TIME_ZONE.toZoneId())) + }; + + return Collections.unmodifiableList(Arrays.asList(formats)); + } - private static List createDateFormats() { - final SimpleDateFormat[] formats = new SimpleDateFormat[]{ - new SimpleDateFormat(RFC1123_DATE_FORMAT_PATTERN, Locale.US), - new SimpleDateFormat(RFC1036_DATE_FORMAT_PATTERN, Locale.US), - new SimpleDateFormat(ANSI_C_ASCTIME_DATE_FORMAT_PATTERN, Locale.US) + private static List createSimpleDateFormats() { + final HttpDateFormatterFromSimpleDateTimeFormat[] formats = new HttpDateFormatterFromSimpleDateTimeFormat[]{ + new HttpDateFormatterFromSimpleDateTimeFormat(new SimpleDateFormat(RFC1123_DATE_FORMAT_PATTERN, Locale.US)), + new HttpDateFormatterFromSimpleDateTimeFormat(new SimpleDateFormat(RFC1036_DATE_FORMAT_PATTERN, Locale.US)), + new HttpDateFormatterFromSimpleDateTimeFormat(new SimpleDateFormat(ANSI_C_ASCTIME_DATE_FORMAT_PATTERN, Locale.US)) }; - formats[0].setTimeZone(GMT_TIME_ZONE); - formats[1].setTimeZone(GMT_TIME_ZONE); - formats[2].setTimeZone(GMT_TIME_ZONE); + formats[0].simpleDateFormat.setTimeZone(GMT_TIME_ZONE); + formats[1].simpleDateFormat.setTimeZone(GMT_TIME_ZONE); + formats[2].simpleDateFormat.setTimeZone(GMT_TIME_ZONE); return Collections.unmodifiableList(Arrays.asList(formats)); } /** - * Return an unmodifiable list of HTTP specified date formats to use for - * parsing or formatting {@link Date}. + * Get the preferred HTTP specified date format (RFC 1123). *

- * The list of date formats are scoped to the current thread and may be - * used without requiring to synchronize access to the instances when + * The date format is scoped to the current thread and may be + * used without requiring to synchronize access to the instance when * parsing or formatting. * - * @return the list of data formats. + * @return the preferred of data format. */ - private static List getDateFormats() { - return dateFormats.get(); + public static HttpDateFormatter getPreferredDateFormatter() { + if (USE_SIMPLE_DATE_FORMAT_OVER_DATE_TIME_FORMATTER) { + List list = simpleDateFormats.poll(); + if (list == null) { + list = createSimpleDateFormats(); + } + // returns clone because calling SDF.parse(...) can change time zone + final SimpleDateFormat sdf = (SimpleDateFormat) + ((HttpDateFormatterFromSimpleDateTimeFormat) list.get(0)).simpleDateFormat.clone(); + simpleDateFormats.add(list); + return new HttpDateFormatterFromSimpleDateTimeFormat(sdf); + } else { + return dateFormats.get(0); + } } /** @@ -93,10 +165,20 @@ private static List getDateFormats() { * parsing or formatting. * * @return the preferred of data format. + * @deprecated Use getPreferredDateFormatter instead */ + // Unused in Jersey + @Deprecated(forRemoval = true) public static SimpleDateFormat getPreferredDateFormat() { + List list = simpleDateFormats.poll(); + if (list == null) { + list = createSimpleDateFormats(); + } // returns clone because calling SDF.parse(...) can change time zone - return (SimpleDateFormat) dateFormats.get().get(0).clone(); + final SimpleDateFormat sdf = (SimpleDateFormat) + ((HttpDateFormatterFromSimpleDateTimeFormat) list.get(0)).simpleDateFormat.clone(); + simpleDateFormats.add(list); + return sdf; } /** @@ -108,18 +190,106 @@ public static SimpleDateFormat getPreferredDateFormat() { * @throws java.text.ParseException in case the date string cannot be parsed. */ public static Date readDate(final String date) throws ParseException { - ParseException pe = null; - for (final SimpleDateFormat f : HttpDateFormat.getDateFormats()) { + return USE_SIMPLE_DATE_FORMAT_OVER_DATE_TIME_FORMATTER + ? readDateSDF(date) + : readDateDTF(date); + } + + private static Date readDateDTF(final String date) throws ParseException { + final List list = dateFormats; + return readDate(date, list); + } + + private static Date readDateSDF(final String date) throws ParseException { + List list = simpleDateFormats.poll(); + if (list == null) { + list = createSimpleDateFormats(); + } + final Date ret = readDate(date, list); + simpleDateFormats.add(list); + return ret; + } + + private static Date readDate(final String date, List formatters) throws ParseException { + Exception pe = null; + for (final HttpDateFormatter f : formatters) { try { - Date result = f.parse(date); - // parse can change time zone -> set it back to GMT - f.setTimeZone(GMT_TIME_ZONE); - return result; - } catch (final ParseException e) { + return f.toDate(date); + } catch (final Exception e) { pe = (pe == null) ? e : pe; } } - throw pe; + throw ParseException.class.isInstance(pe) ? (ParseException) pe + : new ParseException(pe.getMessage(), + DateTimeParseException.class.isInstance(pe) ? ((DateTimeParseException) pe).getErrorIndex() : 0); + } + + /** + * Warning! DateTimeFormatter is incompatible with SimpleDateFormat for two digits year, since SimpleDateFormat uses + * 80 years before now and 20 years after, whereas DateTimeFormatter uses years starting with 2000. + */ + private static class HttpDateFormatterFromDateTimeFormatter implements HttpDateFormatter { + private final DateTimeFormatter dateTimeFormatter; + + private HttpDateFormatterFromDateTimeFormatter(DateTimeFormatter dateTimeFormatter) { + this.dateTimeFormatter = dateTimeFormatter; + } + + @Override + public Date toDate(String date) { + return new Date(Instant.from(dateTimeFormatter.parse(date)).toEpochMilli()); + } + + @Override + public LocalDateTime toDateTime(String date) { + return Instant.from(dateTimeFormatter.parse(date)).atZone(GMT_TIME_ZONE.toZoneId()).toLocalDateTime(); + } + + @Override + public String format(Date date) { + return dateTimeFormatter.format(date.toInstant()); + } + + @Override + public String format(LocalDateTime dateTime) { + return dateTimeFormatter.format(dateTime); + } + } + + private static class HttpDateFormatterFromSimpleDateTimeFormat implements HttpDateFormatter { + private final SimpleDateFormat simpleDateFormat; + + private HttpDateFormatterFromSimpleDateTimeFormat(SimpleDateFormat simpleDateFormat) { + this.simpleDateFormat = simpleDateFormat; + } + + @Override + public Date toDate(String date) { + final Date result; + try { + result = simpleDateFormat.parse(date); + } catch (ParseException e) { + throw new RuntimeException(e); + } + // parse can change time zone -> set it back to GMT + simpleDateFormat.setTimeZone(GMT_TIME_ZONE); + return result; + } + + @Override + public LocalDateTime toDateTime(String date) { + return Instant.from(toDate(date).toInstant()).atZone(GMT_TIME_ZONE.toZoneId()).toLocalDateTime(); + } + + @Override + public String format(Date date) { + return simpleDateFormat.format(date); + } + + @Override + public String format(LocalDateTime dateTime) { + return simpleDateFormat.format(Date.from(dateTime.atZone(GMT_TIME_ZONE.toZoneId()).toInstant())); + } } } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpHeaderReader.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpHeaderReader.java index fe5a2497f1..62df83fdc1 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpHeaderReader.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/HttpHeaderReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 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 @@ -28,6 +28,8 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import jakarta.ws.rs.core.Cookie; import jakarta.ws.rs.core.MediaType; @@ -622,6 +624,7 @@ private StringListReader() { private abstract static class ListReader { private final LRU> LIST_CACHE = LRU.create(); + private final Lock lock = new ReentrantLock(); protected final ListElementCreator creator; protected ListReader(ListElementCreator creator) { @@ -639,7 +642,8 @@ private List readList(final List l, final String header) List list = LIST_CACHE.getIfPresent(header); if (list == null) { - synchronized (LIST_CACHE) { + lock.lock(); + try { list = LIST_CACHE.getIfPresent(header); if (list == null) { HttpHeaderReader reader = new HttpHeaderReaderImpl(header); @@ -655,6 +659,8 @@ private List readList(final List l, final String header) } LIST_CACHE.put(header, list); } + } finally { + lock.unlock(); } } diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java index 8615aeb686..08fbf5a126 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/NewCookieProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -81,7 +81,7 @@ public String toString(final NewCookie cookie) { } if (cookie.getExpiry() != null) { b.append(";Expires="); - b.append(HttpDateFormat.getPreferredDateFormat().format(cookie.getExpiry())); + b.append(HttpDateFormat.getPreferredDateFormatter().format(cookie.getExpiry())); } return b.toString(); 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..7a1a4a5c1c 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 @@ -22,6 +22,8 @@ import java.lang.reflect.Type; import java.util.Iterator; import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -252,6 +254,7 @@ private static class UnCloseableInputStream extends InputStream { private final InputStream original; private final MessageBodyReader reader; + private final Lock markLock = new ReentrantLock(); private UnCloseableInputStream(final InputStream original, final MessageBodyReader reader) { this.original = original; @@ -284,13 +287,23 @@ public int available() throws IOException { } @Override - public synchronized void mark(final int i) { - original.mark(i); + public void mark(final int i) { + markLock.lock(); + try { + original.mark(i); + } finally { + markLock.unlock(); + } } @Override - public synchronized void reset() throws IOException { - original.reset(); + public void reset() throws IOException { + markLock.lock(); + try { + original.reset(); + } finally { + markLock.unlock(); + } } @Override diff --git a/core-common/src/main/java/org/glassfish/jersey/process/internal/RequestScope.java b/core-common/src/main/java/org/glassfish/jersey/process/internal/RequestScope.java index ced41cdbb1..4d92447905 100644 --- a/core-common/src/main/java/org/glassfish/jersey/process/internal/RequestScope.java +++ b/core-common/src/main/java/org/glassfish/jersey/process/internal/RequestScope.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2019 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 @@ -244,6 +244,7 @@ protected void resume(RequestContext context) { */ protected void release(RequestContext context) { context.release(); + currentRequestContext.remove(); } /** diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java b/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java index 2ce0542b22..b671e68665 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ChunkedOutput.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 @@ -26,6 +26,8 @@ import java.util.concurrent.Callable; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import jakarta.ws.rs.container.ConnectionCallback; import jakarta.ws.rs.core.GenericType; @@ -54,7 +56,7 @@ public class ChunkedOutput extends GenericType implements Closeable { private final BlockingDeque queue = new LinkedBlockingDeque<>(); private final byte[] chunkDelimiter; private final AtomicBoolean resumed = new AtomicBoolean(false); - private final Object lock = new Object(); + private final Lock lock = new ReentrantLock(); // the following flushing and touchingEntityStream variables are used in a synchronized block exclusively private boolean flushing = false; @@ -202,7 +204,8 @@ public Void call() throws IOException { boolean shouldClose; T t; - synchronized (lock) { + lock.lock(); + try { if (flushing) { // if another thread is already flushing the queue, we don't have to do anything return null; @@ -220,13 +223,15 @@ public Void call() throws IOException { // and they don't have to bother flushing = true; } + } finally { + lock.unlock(); } while (t != null) { try { - synchronized (lock) { - touchingEntityStream = true; - } + lock.lock(); + touchingEntityStream = true; + lock.unlock(); final OutputStream origStream = responseContext.getEntityStream(); final OutputStream writtenStream = requestContext.getWorkers().writeTo( @@ -265,14 +270,15 @@ public Void call() throws IOException { } throw mpe; } finally { - synchronized (lock) { - touchingEntityStream = false; - } + lock.lock(); + touchingEntityStream = false; + lock.unlock(); } t = queue.poll(); if (t == null) { - synchronized (lock) { + lock.lock(); + try { // queue seems empty // check again in the synchronized block before clearing the flushing flag // first remember the closed flag (this has to be before polling the queue, @@ -290,6 +296,8 @@ public Void call() throws IOException { flushing = shouldClose; break; } + } finally { + lock.unlock(); } } } @@ -303,18 +311,20 @@ public Void call() throws IOException { onClose(e); } finally { if (closed) { + lock.lock(); try { - synchronized (lock) { - if (!touchingEntityStream) { - responseContext.close(); - } // else the next thread will close responseContext - } + if (!touchingEntityStream) { + responseContext.close(); + } // else the next thread will close responseContext } catch (final Exception e) { // if no exception remembered before, remember this one // otherwise the previously remembered exception (from catch clause) takes precedence ex = ex == null ? e : ex; + } finally { + lock.unlock(); } + requestScopeContext.release(); // rethrow remembered exception (if any) diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java index 6f744275b1..d6eb5a6ba7 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerRuntime.java @@ -34,6 +34,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import java.util.logging.Logger; @@ -800,7 +802,7 @@ public void handleTimeout(final AsyncResponse asyncResponse) { } }; - private final Object stateLock = new Object(); + private final ReadWriteLock stateLock = new ReentrantReadWriteLock(); private State state = RUNNING; private boolean cancelled = false; @@ -832,11 +834,13 @@ public AsyncResponder(final Responder responder, @Override public void onTimeout(final ContainerResponseWriter responseWriter) { final TimeoutHandler handler = timeoutHandler; + stateLock.readLock().lock(); try { - synchronized (stateLock) { - if (state == SUSPENDED) { - handler.handleTimeout(this); - } + if (state == SUSPENDED) { + stateLock.readLock().unlock(); // unlock before handleTimeout to prevent write lock in handleTimeout + handler.handleTimeout(this); + } else { + stateLock.readLock().unlock(); } } catch (final Throwable throwable) { resume(throwable); @@ -845,9 +849,9 @@ public void onTimeout(final ContainerResponseWriter responseWriter) { @Override public void onComplete(final Throwable throwable) { - synchronized (stateLock) { - state = COMPLETED; - } + stateLock.writeLock().lock(); + state = COMPLETED; + stateLock.writeLock().unlock(); } @Override @@ -875,14 +879,19 @@ public void run() { @Override public boolean suspend() { - synchronized (stateLock) { - if (state == RUNNING) { - if (responder.processingContext.request().getResponseWriter().suspend( - AsyncResponse.NO_TIMEOUT, TimeUnit.SECONDS, this)) { - state = SUSPENDED; - return true; - } + stateLock.readLock().lock(); + if (state == RUNNING) { + stateLock.readLock().unlock(); + if (responder.processingContext.request().getResponseWriter().suspend( + AsyncResponse.NO_TIMEOUT, TimeUnit.SECONDS, this)) { + // Must release read lock before acquiring write lock + stateLock.writeLock().lock(); + state = SUSPENDED; + stateLock.writeLock().unlock(); + return true; } + } else { + stateLock.readLock().unlock(); } return false; } @@ -925,12 +934,17 @@ public void run() { } private boolean resume(final Runnable handler) { - synchronized (stateLock) { + stateLock.readLock().lock(); + try { if (state != SUSPENDED) { return false; } - state = RESUMED; + } finally { + stateLock.readLock().unlock(); } + stateLock.writeLock().lock(); + state = RESUMED; + stateLock.writeLock().unlock(); try { responder.runtime.requestScope.runInScope(requestContext, handler); @@ -978,7 +992,8 @@ public Response get() { } private boolean cancel(final Value responseValue) { - synchronized (stateLock) { + stateLock.readLock().lock(); + try { if (cancelled) { return true; } @@ -986,10 +1001,15 @@ private boolean cancel(final Value responseValue) { if (state != SUSPENDED) { return false; } - state = RESUMED; - cancelled = true; + } finally { + stateLock.readLock().unlock(); } + stateLock.writeLock().lock(); + state = RESUMED; + cancelled = true; + stateLock.writeLock().unlock(); + responder.runtime.requestScope.runInScope(requestContext, new Runnable() { @Override public void run() { @@ -1006,29 +1026,41 @@ public void run() { } public boolean isRunning() { - synchronized (stateLock) { + stateLock.readLock().lock(); + try { return state == RUNNING; + } finally { + stateLock.readLock().unlock(); } } @Override public boolean isSuspended() { - synchronized (stateLock) { + stateLock.readLock().lock(); + try { return state == SUSPENDED; + } finally { + stateLock.readLock().unlock(); } } @Override public boolean isCancelled() { - synchronized (stateLock) { + stateLock.readLock().lock(); + try { return cancelled; + } finally { + stateLock.readLock().unlock(); } } @Override public boolean isDone() { - synchronized (stateLock) { + stateLock.readLock().lock(); + try { return state == COMPLETED; + } finally { + stateLock.readLock().unlock(); } } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/JerseyRequestTimeoutHandler.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/JerseyRequestTimeoutHandler.java index 761065e2dd..91ec5659f7 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/JerseyRequestTimeoutHandler.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/JerseyRequestTimeoutHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -19,6 +19,8 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; @@ -42,7 +44,7 @@ public class JerseyRequestTimeoutHandler { private ScheduledFuture timeoutTask = null; // guarded by runtimeLock private ContainerResponseWriter.TimeoutHandler timeoutHandler = null; // guarded by runtimeLock private boolean suspended = false; // guarded by runtimeLock - private final Object runtimeLock = new Object(); + private final Lock runtimeLock = new ReentrantLock(); private final ContainerResponseWriter containerResponseWriter; private final ScheduledExecutorService executor; @@ -71,7 +73,8 @@ public JerseyRequestTimeoutHandler(final ContainerResponseWriter containerRespon * @see ContainerResponseWriter#suspend(long, TimeUnit, ContainerResponseWriter.TimeoutHandler) */ public boolean suspend(final long timeOut, final TimeUnit unit, final TimeoutHandler handler) { - synchronized (runtimeLock) { + runtimeLock.lock(); + try { if (suspended) { return false; } @@ -81,6 +84,8 @@ public boolean suspend(final long timeOut, final TimeUnit unit, final TimeoutHan containerResponseWriter.setSuspendTimeout(timeOut, unit); return true; + } finally { + runtimeLock.unlock(); } } @@ -94,7 +99,8 @@ public boolean suspend(final long timeOut, final TimeUnit unit, final TimeoutHan * @see ContainerResponseWriter#setSuspendTimeout(long, TimeUnit) */ public void setSuspendTimeout(final long timeOut, final TimeUnit unit) throws IllegalStateException { - synchronized (runtimeLock) { + runtimeLock.lock(); + try { if (!suspended) { throw new IllegalStateException(LocalizationMessages.SUSPEND_NOT_SUSPENDED()); } @@ -110,18 +116,21 @@ public void setSuspendTimeout(final long timeOut, final TimeUnit unit) throws Il @Override public void run() { + runtimeLock.lock(); try { - synchronized (runtimeLock) { - timeoutHandler.onTimeout(containerResponseWriter); - } + timeoutHandler.onTimeout(containerResponseWriter); } catch (final Throwable throwable) { LOGGER.log(Level.WARNING, LocalizationMessages.SUSPEND_HANDLER_EXECUTION_FAILED(), throwable); + } finally { + runtimeLock.unlock(); } } }, timeOut, unit); } catch (final IllegalStateException ex) { LOGGER.log(Level.WARNING, LocalizationMessages.SUSPEND_SCHEDULING_ERROR(), ex); } + } finally { + runtimeLock.unlock(); } } @@ -132,10 +141,15 @@ public void close() { close(false); } - private synchronized void close(final boolean interruptIfRunning) { - if (timeoutTask != null) { - timeoutTask.cancel(interruptIfRunning); - timeoutTask = null; + private void close(final boolean interruptIfRunning) { + runtimeLock.lock(); + try { + if (timeoutTask != null) { + timeoutTask.cancel(interruptIfRunning); + timeoutTask = null; + } + } finally { + runtimeLock.unlock(); } } } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/JerseyResourceContext.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/JerseyResourceContext.java index dbd9a59381..4917f059e1 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/JerseyResourceContext.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/JerseyResourceContext.java @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.IdentityHashMap; import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.function.Function; import java.util.logging.Level; @@ -53,7 +55,7 @@ public class JerseyResourceContext implements ExtendedResourceContext { private final Consumer registerBinding; private final Set> bindingCache; - private final Object bindingCacheLock; + private final Lock bindingCacheLock; private volatile ResourceModel resourceModel; @@ -72,7 +74,7 @@ public JerseyResourceContext( this.injectInstance = injectInstance; this.registerBinding = registerBinding; this.bindingCache = Collections.newSetFromMap(new IdentityHashMap<>()); - this.bindingCacheLock = new Object(); + this.bindingCacheLock = new ReentrantLock(); } @Override @@ -109,11 +111,14 @@ public void bindResource(Class resourceClass) { return; } - synchronized (bindingCacheLock) { + bindingCacheLock.lock(); + try { if (bindingCache.contains(resourceClass)) { return; } unsafeBindResource(resourceClass, null); + } finally { + bindingCacheLock.unlock(); } } @@ -134,7 +139,8 @@ public void bindResourceIfSingleton(T resource) { return; } - synchronized (bindingCacheLock) { + bindingCacheLock.lock(); + try { if (bindingCache.contains(resourceClass)) { return; } @@ -143,6 +149,8 @@ public void bindResourceIfSingleton(T resource) { } bindingCache.add(resourceClass); + } finally { + bindingCacheLock.unlock(); } } diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/monitoring/jmx/MBeanExposer.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/monitoring/jmx/MBeanExposer.java index a7d58297a2..dc99a5d2f0 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/monitoring/jmx/MBeanExposer.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/monitoring/jmx/MBeanExposer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,6 +21,8 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; @@ -60,7 +62,7 @@ public class MBeanExposer extends AbstractContainerLifecycleListener implements private volatile ResourcesMBeanGroup resourceClassStatsGroup; private volatile ExceptionMapperMXBeanImpl exceptionMapperMXBean; private final AtomicBoolean destroyed = new AtomicBoolean(false); - private final Object LOCK = new Object(); + private final Lock LOCK = new ReentrantLock(); /** * Name of domain that will prefix mbeans {@link ObjectName}. The code uses this @@ -110,45 +112,47 @@ static String convertToObjectName(String name, boolean isUri) { void registerMBean(Object mbean, String namePostfix) { final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); final String name = domain + namePostfix; + LOCK.lock(); try { - synchronized (LOCK) { - if (destroyed.get()) { - // already destroyed - return; - } - final ObjectName objectName = new ObjectName(name); - if (mBeanServer.isRegistered(objectName)) { - - LOGGER.log(Level.WARNING, - LocalizationMessages.WARNING_MONITORING_MBEANS_BEAN_ALREADY_REGISTERED(objectName)); - mBeanServer.unregisterMBean(objectName); - } - mBeanServer.registerMBean(mbean, objectName); + if (destroyed.get()) { + // already destroyed + return; } + final ObjectName objectName = new ObjectName(name); + if (mBeanServer.isRegistered(objectName)) { + + LOGGER.log(Level.WARNING, + LocalizationMessages.WARNING_MONITORING_MBEANS_BEAN_ALREADY_REGISTERED(objectName)); + mBeanServer.unregisterMBean(objectName); + } + mBeanServer.registerMBean(mbean, objectName); } catch (JMException e) { throw new ProcessingException(LocalizationMessages.ERROR_MONITORING_MBEANS_REGISTRATION(name), e); + } finally { + LOCK.unlock(); } } private void unregisterJerseyMBeans(boolean destroy) { final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); + LOCK.lock(); try { - synchronized (LOCK) { - if (destroy) { - destroyed.set(true); // do not register new beans since now - } - - if (domain == null) { - // No bean has been registered yet. - return; - } - final Set names = mBeanServer.queryNames(new ObjectName(domain + ",*"), null); - for (ObjectName name : names) { - mBeanServer.unregisterMBean(name); - } + if (destroy) { + destroyed.set(true); // do not register new beans since now + } + + if (domain == null) { + // No bean has been registered yet. + return; + } + final Set names = mBeanServer.queryNames(new ObjectName(domain + ",*"), null); + for (ObjectName name : names) { + mBeanServer.unregisterMBean(name); } } catch (Exception e) { throw new ProcessingException(LocalizationMessages.ERROR_MONITORING_MBEANS_UNREGISTRATION_DESTROY(), e); + } finally { + LOCK.unlock(); } } 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..818bdbebb9 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 @@ -19,6 +19,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.NoSuchElementException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.logging.Level; @@ -110,6 +112,7 @@ public void reset() { public InputStream open() { //noinspection NullableProblems return new InputStream() { + private final Lock markLock = new ReentrantLock(); @Override public int read() throws IOException { @@ -142,13 +145,23 @@ public void close() throws IOException { } @Override - public synchronized void mark(final int i) { - jarInputStream.mark(i); + public void mark(final int i) { + markLock.lock(); + try { + jarInputStream.mark(i); + } finally { + markLock.unlock(); + } } @Override - public synchronized void reset() throws IOException { - jarInputStream.reset(); + public void reset() throws IOException { + markLock.lock(); + try { + jarInputStream.reset(); + } finally { + markLock.unlock(); + } } @Override diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/PackageNamesScanner.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/PackageNamesScanner.java index 06a9432a30..d482dde6ef 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/PackageNamesScanner.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/PackageNamesScanner.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 @@ -27,6 +27,8 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.glassfish.jersey.internal.OsgiRegistry; import org.glassfish.jersey.internal.util.ReflectionHelper; @@ -54,7 +56,7 @@ * URI schemes. *

* Further schemes may be registered by registering an implementation of - * {@link UriSchemeResourceFinderFactory} in the META-INF/services file whose name is the + * {@link UriSchemeResourceFinderFactory} in the META-INF/services file whose name is * the fully qualified class name of {@link UriSchemeResourceFinderFactory}. *

* If a URI scheme is not supported a {@link ResourceFinderException} will be thrown @@ -195,13 +197,15 @@ private void init() { public abstract static class ResourcesProvider { private static volatile ResourcesProvider provider; + private static final Lock RESOURCE_PROVIDER_LOCK = new ReentrantLock(); private static ResourcesProvider getInstance() { // Double-check idiom for lazy initialization ResourcesProvider result = provider; if (result == null) { // first check without locking - synchronized (ResourcesProvider.class) { + RESOURCE_PROVIDER_LOCK.lock(); + try { result = provider; if (result == null) { // second check with locking provider = result = new ResourcesProvider() { @@ -214,6 +218,8 @@ public Enumeration getResources(final String name, final ClassLoader cl) }; } + } finally { + RESOURCE_PROVIDER_LOCK.unlock(); } } @@ -226,9 +232,9 @@ private static void setInstance(final ResourcesProvider provider) throws Securit final ReflectPermission rp = new ReflectPermission("suppressAccessChecks"); security.checkPermission(rp); } - synchronized (ResourcesProvider.class) { - ResourcesProvider.provider = provider; - } + RESOURCE_PROVIDER_LOCK.lock(); + ResourcesProvider.provider = provider; + RESOURCE_PROVIDER_LOCK.unlock(); } /** 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..982a44b69d 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,8 @@ import java.net.URI; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -56,6 +58,7 @@ public final class WadlResource { private byte[] wadlXmlRepresentation; private String lastModified; + private final Lock lock = new ReentrantLock(); @Context private WadlApplicationContext wadlContext; @@ -71,7 +74,8 @@ private boolean isCached(UriInfo uriInfo, boolean detailedWadl) { @Produces({"application/vnd.sun.wadl+xml", "application/xml"}) @GET - public synchronized Response getWadl(@Context UriInfo uriInfo) { + public Response getWadl(@Context UriInfo uriInfo) { + lock.lock(); try { if (!wadlContext.isWadlGenerationEnabled()) { return Response.status(Response.Status.NOT_FOUND).build(); @@ -103,6 +107,8 @@ public synchronized Response getWadl(@Context UriInfo uriInfo) { return Response.ok(new ByteArrayInputStream(wadlXmlRepresentation)).header("Last-modified", lastModified).build(); } catch (Exception e) { throw new ProcessingException("Error generating /application.wadl.", e); + } finally { + lock.unlock(); } } @@ -110,9 +116,10 @@ public synchronized Response getWadl(@Context UriInfo uriInfo) { @Produces({"application/xml"}) @GET @Path("{path}") - public synchronized Response getExternalGrammar( + public Response getExternalGrammar( @Context UriInfo uriInfo, @PathParam("path") String path) { + lock.lock(); try { // Fail if wadl generation is disabled if (!wadlContext.isWadlGenerationEnabled()) { @@ -135,6 +142,8 @@ public synchronized Response getExternalGrammar( .build(); } catch (Exception e) { throw new ProcessingException(LocalizationMessages.ERROR_WADL_RESOURCE_EXTERNAL_GRAMMAR(), e); + } finally { + lock.unlock(); } } diff --git a/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/FormParamTest.java b/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/FormParamTest.java index 34f29d6548..07b368e577 100644 --- a/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/FormParamTest.java +++ b/core-server/src/test/java/org/glassfish/jersey/server/internal/inject/FormParamTest.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 @@ -402,8 +402,8 @@ public void testFormParamDate() throws ExecutionException, InterruptedException initiateWebApplication(FormResourceDate.class); final String date_RFC1123 = "Sun, 06 Nov 1994 08:49:37 GMT"; - final String date_RFC1036 = "Sunday, 06-Nov-94 08:49:37 GMT"; - final String date_ANSI_C = "Sun Nov 6 08:49:37 1994"; + final String date_RFC1036 = "Sunday, 07-Nov-04 08:49:37 GMT"; + final String date_ANSI_C = "Sun Nov 6 08:49:37 1994"; final Form form = new Form(); form.param("a", date_RFC1123); diff --git a/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/RequestParameters.java b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/RequestParameters.java index cd9d92a8f0..aeffb03dc8 100644 --- a/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/RequestParameters.java +++ b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/RequestParameters.java @@ -35,6 +35,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -43,6 +44,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * Collector to retrieve parameters for setting up the HTTP request sent in the invoke method of WebResourceFactory @@ -145,7 +147,7 @@ private void addBeanParameter(final Object beanParam) throws IllegalAccessException, IntrospectionException, InvocationTargetException { Class beanClass = beanParam.getClass(); List fields = new ArrayList<>(); - getAllFields(fields, beanClass); + getAllNonStaticFields(fields, beanClass); for (final Field field : fields) { Object value = null; @@ -178,11 +180,15 @@ private void addBeanParameter(final Object beanParam) } } - private List getAllFields(List fields, Class type) { - fields.addAll(Arrays.asList(type.getDeclaredFields())); + private List getAllNonStaticFields(List fields, Class type) { + + List nonStaticFields = Arrays.stream(type.getDeclaredFields()) + .filter(field -> !Modifier.isStatic(field.getModifiers())) + .collect(Collectors.toList()); + fields.addAll(nonStaticFields); if (type.getSuperclass() != null) { - getAllFields(fields, type.getSuperclass()); + getAllNonStaticFields(fields, type.getSuperclass()); } return fields; diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBeanParamWithPrivateField.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBeanParamWithPrivateField.java index beb98f6461..06250c7262 100644 --- a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBeanParamWithPrivateField.java +++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBeanParamWithPrivateField.java @@ -27,6 +27,10 @@ public class MyBeanParamWithPrivateField { @QueryParam("privateFieldParam") private String privateFieldParam; + private static String privateStaticField; + + static String staticField; + public MyBeanParamWithPrivateField() {} public String getPrivateFieldParam() { @@ -36,4 +40,20 @@ public String getPrivateFieldParam() { public void setPrivateFieldParam(String privateFieldParam) { this.privateFieldParam = privateFieldParam; } + + public static String getPrivateStaticField() { + return privateStaticField; + } + + public static void setPrivateStaticField(String privateStaticField) { + MyBeanParamWithPrivateField.privateStaticField = privateStaticField; + } + + public static String getStaticField() { + return staticField; + } + + public static void setStaticField(String staticField) { + MyBeanParamWithPrivateField.staticField = staticField; + } } diff --git a/media/jaxb/src/main/java/org/glassfish/jersey/jaxb/internal/AbstractJaxbProvider.java b/media/jaxb/src/main/java/org/glassfish/jersey/jaxb/internal/AbstractJaxbProvider.java index 1d3c5c5425..0b95efd483 100644 --- a/media/jaxb/src/main/java/org/glassfish/jersey/jaxb/internal/AbstractJaxbProvider.java +++ b/media/jaxb/src/main/java/org/glassfish/jersey/jaxb/internal/AbstractJaxbProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,6 +21,8 @@ import java.lang.ref.WeakReference; import java.util.Map; import java.util.WeakHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; @@ -57,6 +59,7 @@ public abstract class AbstractJaxbProvider extends AbstractMessageReaderWrite private static final Map, WeakReference> jaxbContexts = new WeakHashMap, WeakReference>(); + private static final Lock jaxbContextsLock = new ReentrantLock(); private final Providers jaxrsProviders; private final boolean fixedResolverMediaType; private final Value> mtContext; @@ -149,7 +152,7 @@ protected boolean isSupported(MediaType mediaType) { /** * Get the JAXB unmarshaller for the given class and media type. *

- * In case this provider instance has been {@link #AbstractJaxbProvider(Providers, MediaType) + * In case this provider instance has been {@link AbstractJaxbProvider(Providers, MediaType) * created with a fixed resolver media type}, the supplied media type argument will be ignored. *

* @@ -192,7 +195,7 @@ private Unmarshaller getUnmarshaller(Class type) throws JAXBException { /** * Get the JAXB marshaller for the given class and media type. *

- * In case this provider instance has been {@link #AbstractJaxbProvider(Providers, MediaType) + * In case this provider instance has been {@link AbstractJaxbProvider(Providers, MediaType) * created with a fixed resolver media type}, the supplied media type argument will be ignored. *

* @@ -280,7 +283,8 @@ private JAXBContext getJAXBContext(Class type) throws JAXBException { * @throws JAXBException in case the JAXB context retrieval fails. */ protected JAXBContext getStoredJaxbContext(Class type) throws JAXBException { - synchronized (jaxbContexts) { + jaxbContextsLock.lock(); + try { final WeakReference ref = jaxbContexts.get(type); JAXBContext c = (ref != null) ? ref.get() : null; if (c == null) { @@ -288,7 +292,10 @@ protected JAXBContext getStoredJaxbContext(Class type) throws JAXBException { jaxbContexts.put(type, new WeakReference(c)); } return c; + } finally { + jaxbContextsLock.unlock(); } + } /** diff --git a/media/jaxb/src/main/java/org/glassfish/jersey/jaxb/internal/JaxbStringReaderProvider.java b/media/jaxb/src/main/java/org/glassfish/jersey/jaxb/internal/JaxbStringReaderProvider.java index e7e399d961..7065c43ea8 100644 --- a/media/jaxb/src/main/java/org/glassfish/jersey/jaxb/internal/JaxbStringReaderProvider.java +++ b/media/jaxb/src/main/java/org/glassfish/jersey/jaxb/internal/JaxbStringReaderProvider.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 @@ -20,6 +20,8 @@ import java.lang.reflect.Type; import java.util.Map; import java.util.WeakHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.core.Context; @@ -54,6 +56,7 @@ public class JaxbStringReaderProvider { private static final Map jaxbContexts = new WeakHashMap(); + private static final Lock jaxbContextsLock = new ReentrantLock(); private final Value> mtContext; private final Value> mtUnmarshaller; @@ -116,13 +119,16 @@ private JAXBContext getJAXBContext(Class type) throws JAXBException { * @throws JAXBException in case JAXB context retrieval fails. */ protected JAXBContext getStoredJAXBContext(Class type) throws JAXBException { - synchronized (jaxbContexts) { + jaxbContextsLock.lock(); + try { JAXBContext c = jaxbContexts.get(type); if (c == null) { c = JAXBContext.newInstance(type); jaxbContexts.put(type, c); } return c; + } finally { + jaxbContextsLock.unlock(); } } diff --git a/media/multipart/pom.xml b/media/multipart/pom.xml index 3ac3603354..9ac0efd015 100644 --- a/media/multipart/pom.xml +++ b/media/multipart/pom.xml @@ -120,6 +120,7 @@ org.apache.maven.plugins maven-compiler-plugin + true org/glassfish/jersey/media/multipart/internal/MultiPartHeaderModificationTest.java diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java index 6d77453aa3..f6c7165960 100644 --- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java +++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java @@ -202,7 +202,9 @@ protected void addStringParameter(final StringBuilder sb, final String name, fin protected void addDateParameter(final StringBuilder sb, final String name, final Date p) { if (p != null) { - sb.append("; ").append(name).append("=\"").append(HttpDateFormat.getPreferredDateFormat().format(p)).append("\""); + sb.append("; ").append(name).append("=\"") + .append(HttpDateFormat.getPreferredDateFormatter().format(p)) + .append("\""); } } @@ -302,7 +304,7 @@ private Date createDate(final String name) throws ParseException { if (value == null) { return null; } - return HttpDateFormat.getPreferredDateFormat().parse(value); + return HttpDateFormat.getPreferredDateFormatter().toDate(value); } private long createLong(final String name) throws ParseException { diff --git a/pom.xml b/pom.xml index a614adc0df..4be2cde1f1 100644 --- a/pom.xml +++ b/pom.xml @@ -313,8 +313,6 @@ ${compiler.mvn.plugin.version} true - ${java.version} - ${java.version} @@ -322,9 +320,6 @@ false false - - module-info.java - @@ -820,6 +815,38 @@ + + org.apache.maven.plugins + maven-compiler-plugin + true + + ${java.version} + ${java.version} + + + + + base-compile + + compile + + + + + module-info.java + + + + + @@ -885,6 +912,27 @@ + + org.apache.maven.plugins + maven-checkstyle-plugin + + + verify + validate + + + check + + + etc/config/checkstyle-verify.xml + true + true + true + **/module-info.java + + + + @@ -896,81 +944,6 @@ - - jdk8 - - 1.8 - - - 9.3 - 3.0.9 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - true - - ${java.version} - ${java.version} - - module-info.java - - - - - - - - - jdk11+ - - - [11,) - - - - - - org.apache.maven.plugins - maven-compiler-plugin - true - - - - base-compile - - compile - - - - - module-info.java - - ${java.version} - ${java.version} - - - - - - - - testsSkip @@ -1190,37 +1163,6 @@ - - jdk1.7+ - - [1.7,) - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - - verify - validate - - - check - - - etc/config/checkstyle-verify.xml - true - true - true - **/module-info.java - - - - - - - sonar diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/FutureCancelTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/FutureCancelTest.java new file mode 100644 index 0000000000..72b2d41091 --- /dev/null +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/FutureCancelTest.java @@ -0,0 +1,202 @@ +/* + * 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.AbstractRxInvoker; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.client.JerseyInvocation; +import org.glassfish.jersey.client.RequestEntityProcessing; +import org.glassfish.jersey.client.spi.ConnectorProvider; +import org.glassfish.jersey.netty.connector.NettyConnectorProvider; +import org.glassfish.jersey.server.ChunkedOutput; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.CompletionStageRxInvoker; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.RxInvokerProvider; +import jakarta.ws.rs.client.SyncInvoker; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.GenericType; +import jakarta.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.function.Function; +import java.util.function.Supplier; + +public class FutureCancelTest extends JerseyTest { + + public static final long SLEEP = 100L; + + public static List testData() { + return Arrays.asList( + new ApacheConnectorProvider(), + new Apache5ConnectorProvider(), + new HttpUrlConnectorProvider(), + new NettyConnectorProvider() + ); + } + + @Path("/") + public static class FutureCancelResource { + @GET + public ChunkedOutput sendData() { + ChunkedOutput chunkedOutput = new ChunkedOutput<>(String.class); + Thread newThread = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i != 100; i++) { + try { + chunkedOutput.write(String.valueOf(i)); + Thread.sleep(SLEEP); + } catch (Exception e) { + // consume + } + } + } + }); + newThread.start(); + + return chunkedOutput; + } + } + + @Override + protected Application configure() { + return new ResourceConfig(FutureCancelResource.class); + } + + @ParameterizedTest + @MethodSource("testData") + public void testFutureCancel(ConnectorProvider connectorProvider) throws InterruptedException, ExecutionException { + ClientConfig config = new ClientConfig(); + config.connectorProvider(connectorProvider); + + Future> future = ClientBuilder.newClient(config) + .register(new FutureCancelRxInvokerProvider()) + .property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.CHUNKED) + .target(target().getUri()).request().rx(FutureCancelRxInvoker.class).get().toCompletableFuture(); + + int expectedSize = 2; + + while (RX_LIST.size() < expectedSize) { + Thread.sleep(SLEEP); + } + future.cancel(true); + + Thread.sleep(2 * SLEEP); // wait to see no new messages arrive + int size = RX_LIST.size(); // some might have beween RX_LIST.size() and cancel() + while (size > expectedSize) { // be sure no more come + Thread.sleep(SLEEP); + expectedSize = size; + size = RX_LIST.size(); + } + + Assertions.assertTrue(size < 10, "Received " + size + " messages"); + } + + private static List RX_LIST = new LinkedList<>(); + + public static class FutureCancelRxInvokerProvider implements RxInvokerProvider { + + Function function = new Function() { + @Override + public Object apply(InputStream inputStream) { + byte[] number = new byte[8]; + int len = 0; + do { + try { + if ((len = inputStream.read(number)) != -1) { + RX_LIST.add(new String(number).substring(0, len)); + } else { + break; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } while (true); + return RX_LIST; + } + }; + + @Override + public boolean isProviderFor(Class clazz) { + return FutureCancelRxInvoker.class.equals(clazz); + } + + @Override + public FutureCancelRxInvoker getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService) { + return new FutureCancelRxInvoker(syncInvoker, executorService, function); + } + } + + private static class FutureCancelRxInvoker extends AbstractRxInvoker implements CompletionStageRxInvoker { + private final Function consumer; + + public FutureCancelRxInvoker(SyncInvoker syncInvoker, ExecutorService executor, Function consumer) { + super(syncInvoker, executor); + this.consumer = consumer; + } + + @Override + public CompletionStage method(String name, Entity entity, Class responseType) { + CompletableFuture completableFuture = CompletableFuture.supplyAsync(new Supplier() { + @Override + public R get() { + Response r = getSyncInvoker().get(); + InputStream is = r.readEntity(InputStream.class); + Object o = consumer.apply(is); + return (R) o; + } + }, getExecutorService()); + ((JerseyInvocation.Builder) getSyncInvoker()).setCancellable(completableFuture); + return completableFuture; + } + + @Override + public CompletionStage method(String name, Entity entity, GenericType responseType) { + CompletableFuture completableFuture = CompletableFuture.supplyAsync(new Supplier() { + @Override + public R get() { + Response r = getSyncInvoker().get(); + InputStream is = r.readEntity(InputStream.class); + Object o = consumer.apply(is); + return (R) o; + } + }, getExecutorService()); + return completableFuture; + } + } +} diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java index 8cecb6b1c1..138466ec6e 100644 --- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java +++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java @@ -66,7 +66,7 @@ public void testCreate() { contentDisposition = new ContentDisposition(header); assertNotNull(contentDisposition); assertEquals(contentDispositionType, contentDisposition.getType()); - final String dateString = HttpDateFormat.getPreferredDateFormat().format(date); + final String dateString = HttpDateFormat.getPreferredDateFormatter().format(date); header = contentDispositionType + ";filename=\"test.file\";creation-date=\"" + dateString + "\";modification-date=\"" + dateString + "\";read-date=\"" + dateString + "\";size=1222"; @@ -101,7 +101,7 @@ public void testToString() { final Date date = new Date(); final ContentDisposition contentDisposition = ContentDisposition.type(contentDispositionType).fileName("test.file") .creationDate(date).modificationDate(date).readDate(date).size(1222).build(); - final String dateString = HttpDateFormat.getPreferredDateFormat().format(date); + final String dateString = HttpDateFormat.getPreferredDateFormatter().format(date); final String header = contentDispositionType + "; filename=\"test.file\"; creation-date=\"" + dateString + "\"; modification-date=\"" + dateString + "\"; read-date=\"" + dateString + "\"; size=1222"; assertEquals(header, contentDisposition.toString()); @@ -252,7 +252,7 @@ private void assertFileNameExt( final boolean decode ) throws ParseException { final Date date = new Date(); - final String dateString = HttpDateFormat.getPreferredDateFormat().format(date); + final String dateString = HttpDateFormat.getPreferredDateFormatter().format(date); final String prefixHeader = contentDispositionType + ";filename=\"" + actualFileName + "\";" + "creation-date=\"" + dateString + "\";modification-date=\"" + dateString + "\";read-date=\"" + dateString + "\";size=1222" + ";name=\"testData\";" + "filename*=\""; diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/FormDataContentDispositionTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/FormDataContentDispositionTest.java index c9318094b2..0595890319 100644 --- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/FormDataContentDispositionTest.java +++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/FormDataContentDispositionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -45,7 +45,7 @@ public void testCreate() { .modificationDate(date).readDate(date).size(1222).build(); assertFormDataContentDisposition(contentDisposition, date); try { - final String dateString = HttpDateFormat.getPreferredDateFormat().format(date); + final String dateString = HttpDateFormat.getPreferredDateFormatter().format(date); final String header = contentDispositionType + ";filename=\"test.file\";creation-date=\"" + dateString + "\";modification-date=\"" + dateString + "\";read-date=\"" + dateString + "\";size=1222" + ";name=\"testData\""; @@ -92,7 +92,7 @@ public void testToString() { final FormDataContentDisposition contentDisposition = FormDataContentDisposition.name("testData") .fileName("test.file").creationDate(date).modificationDate(date) .readDate(date).size(1222).build(); - final String dateString = HttpDateFormat.getPreferredDateFormat().format(date); + final String dateString = HttpDateFormat.getPreferredDateFormatter().format(date); final String header = contentDispositionType + "; filename=\"test.file\"; creation-date=\"" + dateString + "\"; modification-date=\"" + dateString + "\"; read-date=\"" + dateString + "\"; size=1222" + "; name=\"testData\""; diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/HttpHeaderTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/HttpHeaderTest.java index 63305db921..4e1bfd34ec 100644 --- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/HttpHeaderTest.java +++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/HttpHeaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -100,8 +100,8 @@ public void testAcceptableTokenList() throws Exception { @Test public void testDateParsing() throws ParseException { final String date_RFC1123 = "Sun, 06 Nov 1994 08:49:37 GMT"; - final String date_RFC1036 = "Sunday, 06-Nov-94 08:49:37 GMT"; - final String date_ANSI_C = "Sun Nov 6 08:49:37 1994"; + final String date_RFC1036 = "Sunday, 07-Nov-04 08:49:37 GMT"; + final String date_ANSI_C = "Sun Nov 6 08:49:37 1994"; HttpHeaderReader.readDate(date_RFC1123); HttpHeaderReader.readDate(date_RFC1036); @@ -113,7 +113,7 @@ public void testDateFormatting() throws ParseException { final String date_RFC1123 = "Sun, 06 Nov 1994 08:49:37 GMT"; final Date date = HttpHeaderReader.readDate(date_RFC1123); - final String date_formatted = HttpDateFormat.getPreferredDateFormat().format(date); + final String date_formatted = HttpDateFormat.getPreferredDateFormatter().format(date); assertEquals(date_RFC1123, date_formatted); }