diff --git a/NOTICE.md b/NOTICE.md index bd12480dfd..1935ed09d5 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -70,7 +70,7 @@ Javassist Version 3.30.2-GA * Project: http://www.javassist.org/ * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. -Jackson JAX-RS Providers Version 2.17.2 +Jackson JAX-RS Providers Version 2.18.0 * License: Apache License, 2.0 * Project: https://github.com/FasterXML/jackson-jaxrs-providers * Copyright: (c) 2009-2024 FasterXML, LLC. All rights reserved unless otherwise indicated. @@ -95,7 +95,7 @@ KineticJS, v4.7.1 * Project: http://www.kineticjs.com, https://github.com/ericdrowell/KineticJS * Copyright: Eric Rowell -org.objectweb.asm Version 9.7 +org.objectweb.asm Version 9.7.1 * License: Modified BSD (https://asm.ow2.io/license.html) * Copyright (c) 2000-2011 INRIA, France Telecom. All rights reserved. diff --git a/connectors/netty-connector/pom.xml b/connectors/netty-connector/pom.xml index f59d7c28c8..c5d45933b4 100644 --- a/connectors/netty-connector/pom.xml +++ b/connectors/netty-connector/pom.xml @@ -101,6 +101,24 @@ + + InaccessibleObjectException + [12,) + + + + org.apache.maven.plugins + maven-surefire-plugin + + + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.lang.reflect=ALL-UNNAMED + + + + + + diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java index cec13488e9..454415773f 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java @@ -436,7 +436,7 @@ public void operationComplete(io.netty.util.concurrent.Future futu }; ch.closeFuture().addListener(closeListener); - final NettyEntityWriter entityWriter = NettyEntityWriter.getInstance(jerseyRequest, ch); + final NettyEntityWriter entityWriter = nettyEntityWriter(jerseyRequest, ch); switch (entityWriter.getType()) { case CHUNKED: HttpUtil.setTransferEncodingChunked(nettyRequest, true); @@ -524,6 +524,10 @@ public void run() { } } + /* package */ NettyEntityWriter nettyEntityWriter(ClientRequest clientRequest, Channel channel) { + return NettyEntityWriter.getInstance(clientRequest, channel); + } + private SSLContext getSslContext(Client client, ClientRequest request) { Supplier supplier = request.resolveProperty(ClientProperties.SSL_CONTEXT_SUPPLIER, Supplier.class); return supplier == null ? client.getSslContext() : supplier.get(); diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/JerseyChunkedInput.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/JerseyChunkedInput.java index ad6da7e9df..4ffe52e46e 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/JerseyChunkedInput.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/JerseyChunkedInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020 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 @@ -101,7 +101,15 @@ public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception { @Override public ByteBuf readChunk(ByteBufAllocator allocator) throws Exception { + try { + return readChunk0(allocator); + } catch (Exception e) { + closeOnThrowable(); + throw e; + } + } + private ByteBuf readChunk0(ByteBufAllocator allocator) throws Exception { if (!open) { return null; } @@ -143,6 +151,14 @@ public long progress() { return offset; } + private void closeOnThrowable() { + try { + close(); + } catch (Throwable t) { + // do not throw other throwable + } + } + @Override public void close() throws IOException { @@ -208,10 +224,12 @@ private void write(Provider bufferSupplier) throws IOException { try { boolean queued = queue.offer(bufferSupplier.get(), WRITE_TIMEOUT, TimeUnit.MILLISECONDS); if (!queued) { + closeOnThrowable(); throw new IOException("Buffer overflow."); } } catch (InterruptedException e) { + closeOnThrowable(); throw new IOException(e); } } diff --git a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputWriteErrorSimulationTest.java b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputWriteErrorSimulationTest.java new file mode 100644 index 0000000000..78a344834b --- /dev/null +++ b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/ChunkedInputWriteErrorSimulationTest.java @@ -0,0 +1,298 @@ +/* + * 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.netty.connector; + +import io.netty.channel.Channel; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.client.spi.ConnectorProvider; +import org.glassfish.jersey.netty.connector.internal.JerseyChunkedInput; +import org.glassfish.jersey.netty.connector.internal.NettyEntityWriter; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.Invocation; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.Configuration; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.Response; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ChunkedInputWriteErrorSimulationTest extends JerseyTest { + private static final String EXCEPTION_MSG = "BOGUS BUFFER OVERFLOW"; + private static final AtomicReference caught = new AtomicReference<>(null); + + public static class ClientThread extends Thread { + + public static AtomicInteger count = new AtomicInteger(); + public static String url; + public static int nLoops; + + private static Client client; + + public static void main(DequeOffer offer, String[] args) throws InterruptedException { + url = args[0]; + int nThreads = Integer.parseInt(args[1]); + nLoops = Integer.parseInt(args[2]); + initClient(offer); + Thread[] threads = new Thread[nThreads]; + for (int i = 0; i < nThreads; i++) { + threads[i] = new ClientThread(); + threads[i].start(); + } + + for (int i = 0; i < nThreads; i++) { + threads[i].join(); + } + // System.out.println("Processed calls: " + count); + } + + private static void initClient(DequeOffer offer) { + ClientConfig defaultConfig = new ClientConfig(); + defaultConfig.property(ClientProperties.CONNECT_TIMEOUT, 10 * 1000); + defaultConfig.property(ClientProperties.READ_TIMEOUT, 10 * 1000); + defaultConfig.connectorProvider(getJerseyChunkedInputModifiedNettyConnector(offer)); + client = ClientBuilder.newBuilder() + .withConfig(defaultConfig) + .build(); + } + + public void doCall() { + CompletableFuture cf = invokeResponse().toCompletableFuture() + .whenComplete((rsp, t) -> { + if (t != null) { +// System.out.println(Thread.currentThread() + " async complete. Caught exception " + t); +// t.printStackTrace(); + while (t.getCause() != null) { + t = t.getCause(); + } + caught.set(t); + } + }) + .handle((rsp, t) -> { + if (rsp != null) { + rsp.readEntity(String.class); + } else { + System.out.println(Thread.currentThread().getName() + " response is null"); + } + return rsp; + }).exceptionally(t -> { + System.out.println("async complete. completed exceptionally " + t); + throw new RuntimeException(t); + }); + + try { + cf.get(); + System.out.println("Done call " + count.incrementAndGet()); + } catch (InterruptedException | ExecutionException ex) { + Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private static CompletionStage invokeResponse() { + WebTarget target = client.target(url); + MultivaluedHashMap hdrs = new MultivaluedHashMap<>(); + StringBuilder sb = new StringBuilder("{"); + for (int i = 0; i < 10000; i++) { + sb.append("\"fname\":\"foo\", \"lname\":\"bar\""); + } + sb.append("}"); + String jsonPayload = sb.toString(); + Invocation.Builder builder = ((WebTarget) target).request().headers(hdrs); + return builder.rx().method("POST", Entity.entity(jsonPayload, MediaType.APPLICATION_JSON_TYPE)); + } + + @Override + public void run() { + for (int i = 0; i < nLoops; i++) { + try { + doCall(); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + } + } + + @Path("/console") + public static class HangingEndpoint { + @Path("/login") + @POST + public String post(String entity) { + return "Welcome"; + } + } + + @Override + protected Application configure() { + return new ResourceConfig(HangingEndpoint.class); + } + + @Test + public void testNoHangOnOfferInterrupt() throws InterruptedException { + String path = getBaseUri() + "console/login"; + ClientThread.main(new InterruptedExceptionOffer(), new String[] {path, "5", "10"}); + Assertions.assertTrue(caught.get().getMessage().contains(EXCEPTION_MSG)); + } + + @Test + public void testNoHangOnPollInterrupt() throws InterruptedException { + String path = getBaseUri() + "console/login"; + ClientThread.main(new DequePoll(), new String[] {path, "5", "10"}); + Assertions.assertNotNull(caught.get()); + } + + @Test + public void testNoHangOnOfferNoData() throws InterruptedException { + String path = getBaseUri() + "console/login"; + ClientThread.main(new ReturnFalseOffer(), new String[] {path, "5", "10"}); + Assertions.assertTrue(caught.get().getMessage().contains("Buffer overflow")); //JerseyChunkedInput + Thread.sleep(1_000L); // Sleep for the server to finish + } + + private interface DequeOffer { + public boolean offer(ByteBuffer e, long timeout, TimeUnit unit) throws InterruptedException; + } + + private static class InterruptedExceptionOffer implements DequeOffer { + private AtomicInteger ai = new AtomicInteger(0); + + @Override + public boolean offer(ByteBuffer e, long timeout, TimeUnit unit) throws InterruptedException { + if ((ai.getAndIncrement() % 10) == 0) { + throw new InterruptedException(EXCEPTION_MSG); + } + return true; + } + } + + private static class ReturnFalseOffer implements DequeOffer { + private AtomicInteger ai = new AtomicInteger(0); + @Override + public boolean offer(ByteBuffer e, long timeout, TimeUnit unit) throws InterruptedException { + return !((ai.getAndIncrement() % 10) == 1); + } + } + + private static class DequePoll extends InterruptedExceptionOffer { + } + + + private static ConnectorProvider getJerseyChunkedInputModifiedNettyConnector(DequeOffer offer) { + return new ConnectorProvider() { + @Override + public Connector getConnector(Client client, Configuration runtimeConfig) { + return new NettyConnector(client) { + NettyEntityWriter nettyEntityWriter(ClientRequest clientRequest, Channel channel) { + NettyEntityWriter wrapped = NettyEntityWriter.getInstance(clientRequest, channel); + + JerseyChunkedInput chunkedInput = (JerseyChunkedInput) wrapped.getChunkedInput(); + try { + Field field = JerseyChunkedInput.class.getDeclaredField("queue"); + field.setAccessible(true); + + removeFinal(field); + + field.set(chunkedInput, new LinkedBlockingDeque() { + @Override + public boolean offer(ByteBuffer e, long timeout, TimeUnit unit) throws InterruptedException { + if (!DequePoll.class.isInstance(offer) && !offer.offer(e, timeout, unit)) { + return false; + } + return super.offer(e, timeout, unit); + } + + @Override + public ByteBuffer poll(long timeout, TimeUnit unit) throws InterruptedException { + if (DequePoll.class.isInstance(offer)) { + offer.offer(null, timeout, unit); + } + return super.poll(timeout, unit); + } + }); + + } catch (Exception e) { + throw new RuntimeException(e); + } + + NettyEntityWriter proxy = (NettyEntityWriter) Proxy.newProxyInstance( + ConnectorProvider.class.getClassLoader(), new Class[]{NettyEntityWriter.class}, + (proxy1, method, args) -> { + if (method.getName().equals("readChunk")) { + try { + return method.invoke(wrapped, args); + } catch (RuntimeException e) { + // consume + } + } + return method.invoke(wrapped, args); + }); + return proxy; + } + }; + } + }; + } + + public static void removeFinal(Field field) throws RuntimeException { + try { + Method[] classMethods = Class.class.getDeclaredMethods(); + Method declaredFieldMethod = Arrays + .stream(classMethods).filter(x -> Objects.equals(x.getName(), "getDeclaredFields0")) + .findAny().orElseThrow(() -> new NoSuchElementException("No value present")); + declaredFieldMethod.setAccessible(true); + Field[] declaredFieldsOfField = (Field[]) declaredFieldMethod.invoke(Field.class, false); + Field modifiersField = Arrays + .stream(declaredFieldsOfField).filter(x -> Objects.equals(x.getName(), "modifiers")) + .findAny().orElseThrow(() -> new NoSuchElementException("No value present")); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } catch (RuntimeException re) { + throw re; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Attribute.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Attribute.java index f63fc71708..1fe6dbf9f1 100644 --- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Attribute.java +++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Attribute.java @@ -44,11 +44,11 @@ public class Attribute { public final String type; /** - * The raw content of this attribute, only used for unknown attributes (see {@link #isUnknown()}). - * The 6 header bytes of the attribute (attribute_name_index and attribute_length) are not - * included. + * The raw content of this attribute, as returned by {@link + * #write(ClassWriter,byte[],int,int,int)}. The 6 header bytes of the attribute + * (attribute_name_index and attribute_length) are not included. */ - private byte[] content; + private ByteVector cachedContent; /** * The next attribute in this attribute list (Attribute instances can be linked via this field to @@ -93,7 +93,9 @@ public boolean isCodeAttribute() { * * @return the labels corresponding to this attribute, or {@literal null} if this attribute is not * a Code attribute that contains labels. + * @deprecated no longer used by ASM. */ + @Deprecated protected Label[] getLabels() { return new Label[0]; } @@ -115,7 +117,9 @@ protected Label[] getLabels() { * attribute header bytes (attribute_name_index and attribute_length) are not taken into * account here. * @param labels the labels of the method's code, or {@literal null} if the attribute to be read - * is not a Code attribute. + * is not a Code attribute. Labels defined in the attribute must be created and added to this + * array, if not already present, by calling the {@link #readLabel} method (do not create + * {@link Label} instances directly). * @return a new {@link Attribute} object corresponding to the specified bytes. */ protected Attribute read( @@ -126,16 +130,99 @@ protected Attribute read( final int codeAttributeOffset, final Label[] labels) { Attribute attribute = new Attribute(type); - attribute.content = new byte[length]; - System.arraycopy(classReader.classFileBuffer, offset, attribute.content, 0, length); + attribute.cachedContent = new ByteVector(classReader.readBytes(offset, length)); return attribute; } + /** + * Reads an attribute with the same {@link #type} as the given attribute. This method returns a + * new {@link Attribute} object, corresponding to the 'length' bytes starting at 'offset', in the + * given ClassReader. + * + * @param attribute The attribute prototype that is used for reading. + * @param classReader the class that contains the attribute to be read. + * @param offset index of the first byte of the attribute's content in {@link ClassReader}. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param length the length of the attribute's content (excluding the 6 attribute header bytes). + * @param charBuffer the buffer to be used to call the ClassReader methods requiring a + * 'charBuffer' parameter. + * @param codeAttributeOffset index of the first byte of content of the enclosing Code attribute + * in {@link ClassReader}, or -1 if the attribute to be read is not a Code attribute. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param labels the labels of the method's code, or {@literal null} if the attribute to be read + * is not a Code attribute. Labels defined in the attribute are added to this array, if not + * already present. + * @return a new {@link Attribute} object corresponding to the specified bytes. + */ + public static Attribute read( + final Attribute attribute, + final ClassReader classReader, + final int offset, + final int length, + final char[] charBuffer, + final int codeAttributeOffset, + final Label[] labels) { + return attribute.read(classReader, offset, length, charBuffer, codeAttributeOffset, labels); + } + + /** + * Returns the label corresponding to the given bytecode offset by calling {@link + * ClassReader#readLabel}. This creates and adds the label to the given array if it is not already + * present. Note that this created label may be a {@link Label} subclass instance, if the given + * ClassReader overrides {@link ClassReader#readLabel}. Hence {@link #read(ClassReader, int, int, + * char[], int, Label[])} must not manually create {@link Label} instances. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. If a label already exists + * for bytecodeOffset this method does not create a new one. Otherwise it stores the new label + * in this array. + * @return a label for the given bytecode offset. + */ + public static Label readLabel( + final ClassReader classReader, final int bytecodeOffset, final Label[] labels) { + return classReader.readLabel(bytecodeOffset, labels); + } + + /** + * Calls {@link #write(ClassWriter,byte[],int,int,int)} if it has not already been called and + * returns its result or its (cached) previous result. + * + * @param classWriter the class to which this attribute must be added. This parameter can be used + * to add the items that corresponds to this attribute to the constant pool of this class. + * @param code the bytecode of the method corresponding to this Code attribute, or {@literal null} + * if this attribute is not a Code attribute. Corresponds to the 'code' field of the Code + * attribute. + * @param codeLength the length of the bytecode of the method corresponding to this code + * attribute, or 0 if this attribute is not a Code attribute. Corresponds to the 'code_length' + * field of the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to this Code attribute, or + * -1 if this attribute is not a Code attribute. + * @param maxLocals the maximum number of local variables of the method corresponding to this code + * attribute, or -1 if this attribute is not a Code attribute. + * @return the byte array form of this attribute. + */ + private ByteVector maybeWrite( + final ClassWriter classWriter, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals) { + if (cachedContent == null) { + cachedContent = write(classWriter, code, codeLength, maxStack, maxLocals); + } + return cachedContent; + } + /** * Returns the byte array form of the content of this attribute. The 6 header bytes * (attribute_name_index and attribute_length) must not be added in the returned * ByteVector. * + *

This method is only invoked once to compute the binary form of this attribute. Subsequent + * changes to the attribute after it was written for the first time will not be considered. + * * @param classWriter the class to which this attribute must be added. This parameter can be used * to add the items that corresponds to this attribute to the constant pool of this class. * @param code the bytecode of the method corresponding to this Code attribute, or {@literal null} @@ -156,7 +243,39 @@ protected ByteVector write( final int codeLength, final int maxStack, final int maxLocals) { - return new ByteVector(content); + return cachedContent; + } + + /** + * Returns the byte array form of the content of the given attribute. The 6 header bytes + * (attribute_name_index and attribute_length) are not added in the returned byte array. + * + * @param attribute The attribute that should be written. + * @param classWriter the class to which this attribute must be added. This parameter can be used + * to add the items that corresponds to this attribute to the constant pool of this class. + * @param code the bytecode of the method corresponding to this Code attribute, or {@literal null} + * if this attribute is not a Code attribute. Corresponds to the 'code' field of the Code + * attribute. + * @param codeLength the length of the bytecode of the method corresponding to this code + * attribute, or 0 if this attribute is not a Code attribute. Corresponds to the 'code_length' + * field of the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to this Code attribute, or + * -1 if this attribute is not a Code attribute. + * @param maxLocals the maximum number of local variables of the method corresponding to this code + * attribute, or -1 if this attribute is not a Code attribute. + * @return the byte array form of this attribute. + */ + public static byte[] write( + final Attribute attribute, + final ClassWriter classWriter, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals) { + ByteVector content = attribute.maybeWrite(classWriter, code, codeLength, maxStack, maxLocals); + byte[] result = new byte[content.length]; + System.arraycopy(content.data, 0, result, 0, content.length); + return result; } /** @@ -221,7 +340,7 @@ final int computeAttributesSize( Attribute attribute = this; while (attribute != null) { symbolTable.addConstantUtf8(attribute.type); - size += 6 + attribute.write(classWriter, code, codeLength, maxStack, maxLocals).length; + size += 6 + attribute.maybeWrite(classWriter, code, codeLength, maxStack, maxLocals).length; attribute = attribute.nextAttribute; } return size; @@ -308,7 +427,7 @@ final void putAttributes( Attribute attribute = this; while (attribute != null) { ByteVector attributeContent = - attribute.write(classWriter, code, codeLength, maxStack, maxLocals); + attribute.maybeWrite(classWriter, code, codeLength, maxStack, maxLocals); // Put attribute_name_index and attribute_length. output.putShort(symbolTable.addConstantUtf8(attribute.type)).putInt(attributeContent.length); output.putByteArray(attributeContent.data, 0, attributeContent.length); diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java index a2bdae3e1e..f5d846aaa2 100644 --- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java +++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java @@ -195,7 +195,7 @@ public ClassReader( this.b = classFileBuffer; // Check the class' major_version. This field is after the magic and minor_version fields, which // use 4 and 2 bytes respectively. - if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V23) { + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V24) { throw new IllegalArgumentException( "Unsupported class file major version " + readShort(classFileOffset + 6)); } @@ -3597,6 +3597,20 @@ public int readByte(final int offset) { return classFileBuffer[offset] & 0xFF; } + /** + * Reads several bytes in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the bytes to be read in this {@link ClassReader}. + * @param length the number of bytes to read. + * @return the read bytes. + */ + public byte[] readBytes(final int offset, final int length) { + byte[] result = new byte[length]; + System.arraycopy(classFileBuffer, offset, result, 0, length); + return result; + } + /** * Reads an unsigned short value in this {@link ClassReader}. This method is intended for * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassWriter.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassWriter.java index 0af80ce0e6..fa16ac9ddf 100644 --- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassWriter.java +++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassWriter.java @@ -264,13 +264,7 @@ public ClassWriter(final ClassReader classReader, final int flags) { super(/* latest api = */ Opcodes.ASM9); this.flags = flags; symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader); - if ((flags & COMPUTE_FRAMES) != 0) { - compute = MethodWriter.COMPUTE_ALL_FRAMES; - } else if ((flags & COMPUTE_MAXS) != 0) { - compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; - } else { - compute = MethodWriter.COMPUTE_NOTHING; - } + setFlags(flags); } // ----------------------------------------------------------------------------------------------- @@ -1020,6 +1014,28 @@ public int newNameType(final String name, final String descriptor) { return symbolTable.addConstantNameAndType(name, descriptor); } + /** + * Changes the computation strategy of method properties like max stack size, max number of local + * variables, and frames. + * + *

WARNING: {@link #setFlags(int)} method changes the behavior of new method visitors + * returned from {@link #visitMethod(int, String, String, String, String[])}. The behavior will be + * changed only after the next method visitor is returned. All the previously returned method + * visitors keep their previous behavior. + * + * @param flags option flags that can be used to modify the default behavior of this class. Must + * be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. + */ + public final void setFlags(final int flags) { + if ((flags & ClassWriter.COMPUTE_FRAMES) != 0) { + compute = MethodWriter.COMPUTE_ALL_FRAMES; + } else if ((flags & ClassWriter.COMPUTE_MAXS) != 0) { + compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; + } else { + compute = MethodWriter.COMPUTE_NOTHING; + } + } + // ----------------------------------------------------------------------------------------------- // Default method to compute common super classes when computing stack map frames // ----------------------------------------------------------------------------------------------- diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Constants.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Constants.java index 9e7b98c658..1c79ca7b41 100644 --- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Constants.java +++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Constants.java @@ -215,7 +215,7 @@ static void checkIsPreview(final InputStream classInputStream) { } if (minorVersion != 0xFFFF) { throw new IllegalStateException( - "ASM9_EXPERIMENTAL can only be used by classes compiled with --enable-preview"); + "ASM10_EXPERIMENTAL can only be used by classes compiled with --enable-preview"); } } } diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java index c8a482bc1b..e2f2c92b98 100644 --- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java +++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java @@ -34,15 +34,16 @@ * visitTypeAnnotation} | {@code visitAttribute} )* [ {@code visitCode} ( {@code visitFrame} | * {@code visitXInsn} | {@code visitLabel} | {@code visitInsnAnnotation} | {@code * visitTryCatchBlock} | {@code visitTryCatchAnnotation} | {@code visitLocalVariable} | {@code - * visitLocalVariableAnnotation} | {@code visitLineNumber} )* {@code visitMaxs} ] {@code visitEnd}. - * In addition, the {@code visitXInsn} and {@code visitLabel} methods must be called in the - * sequential order of the bytecode instructions of the visited code, {@code visitInsnAnnotation} - * must be called after the annotated instruction, {@code visitTryCatchBlock} must be called - * before the labels passed as arguments have been visited, {@code - * visitTryCatchBlockAnnotation} must be called after the corresponding try catch block has - * been visited, and the {@code visitLocalVariable}, {@code visitLocalVariableAnnotation} and {@code - * visitLineNumber} methods must be called after the labels passed as arguments have been - * visited. + * visitLocalVariableAnnotation} | {@code visitLineNumber} | {@code visitAttribute} )* {@code + * visitMaxs} ] {@code visitEnd}. In addition, the {@code visitXInsn} and {@code visitLabel} + * methods must be called in the sequential order of the bytecode instructions of the visited code, + * {@code visitInsnAnnotation} must be called after the annotated instruction, {@code + * visitTryCatchBlock} must be called before the labels passed as arguments have been + * visited, {@code visitTryCatchBlockAnnotation} must be called after the corresponding try + * catch block has been visited, and the {@code visitLocalVariable}, {@code + * visitLocalVariableAnnotation} and {@code visitLineNumber} methods must be called after the + * labels passed as arguments have been visited. Finally, the {@code visitAttribute} method must be + * called before {@code visitCode} for non-code attributes, and after it for code attributes. * * @author Eric Bruneton */ diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodWriter.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodWriter.java index bccc99717a..b49f443811 100644 --- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodWriter.java +++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodWriter.java @@ -1519,14 +1519,14 @@ public AnnotationVisitor visitLocalVariableAnnotation( return lastCodeRuntimeVisibleTypeAnnotation = new AnnotationWriter( symbolTable, - /* useNamedValues = */ true, + /* useNamedValues= */ true, typeAnnotation, lastCodeRuntimeVisibleTypeAnnotation); } else { return lastCodeRuntimeInvisibleTypeAnnotation = new AnnotationWriter( symbolTable, - /* useNamedValues = */ true, + /* useNamedValues= */ true, typeAnnotation, lastCodeRuntimeInvisibleTypeAnnotation); } @@ -1642,7 +1642,7 @@ private void computeAllFrames() { code.data[endOffset] = (byte) Opcodes.ATHROW; // Emit a frame for this unreachable block, with no local and a Throwable on the stack // (so that the ATHROW could consume this Throwable if it were reachable). - int frameIndex = visitFrameStart(startOffset, /* numLocal = */ 0, /* numStack = */ 1); + int frameIndex = visitFrameStart(startOffset, /* numLocal= */ 0, /* numStack= */ 1); currentFrame[frameIndex] = Frame.getAbstractTypeFromInternalName(symbolTable, "java/lang/Throwable"); visitFrameEnd(); diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java index f796c9430b..eeb3df7afb 100644 --- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java +++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java @@ -289,6 +289,7 @@ public interface Opcodes { int V21 = 0 << 16 | 65; int V22 = 0 << 16 | 66; int V23 = 0 << 16 | 67; + int V24 = 0 << 16 | 68; /** * Version flag indicating that the class is using 'preview' features. diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Symbol.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Symbol.java index f161884461..34f365322c 100644 --- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Symbol.java +++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Symbol.java @@ -178,7 +178,9 @@ abstract class Symbol { *

  • the symbol's value for {@link #CONSTANT_INTEGER_TAG},{@link #CONSTANT_FLOAT_TAG}, {@link * #CONSTANT_LONG_TAG}, {@link #CONSTANT_DOUBLE_TAG}, *
  • the CONSTANT_MethodHandle_info reference_kind field value for {@link - * #CONSTANT_METHOD_HANDLE_TAG} symbols, + * #CONSTANT_METHOD_HANDLE_TAG} symbols (or this value left shifted by 8 bits for + * reference_kind values larger than or equal to H_INVOKEVIRTUAL and if the method owner is + * an interface), *
  • the CONSTANT_InvokeDynamic_info bootstrap_method_attr_index field value for {@link * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, *
  • the offset of a bootstrap method in the BootstrapMethods boostrap_methods array, for diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/SymbolTable.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/SymbolTable.java index 9ceffb1c5b..e5e16be410 100644 --- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/SymbolTable.java +++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/SymbolTable.java @@ -221,7 +221,9 @@ final class SymbolTable { classReader.readByte(itemOffset), classReader.readClass(memberRefItemOffset, charBuffer), classReader.readUTF8(nameAndTypeItemOffset, charBuffer), - classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer)); + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer), + classReader.readByte(memberRefItemOffset - 1) + == Symbol.CONSTANT_INTERFACE_METHODREF_TAG); break; case Symbol.CONSTANT_DYNAMIC_TAG: case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: @@ -830,14 +832,15 @@ Symbol addConstantMethodHandle( final String descriptor, final boolean isInterface) { final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG; + final int data = getConstantMethodHandleSymbolData(referenceKind, isInterface); // Note that we don't need to include isInterface in the hash computation, because it is // redundant with owner (we can't have the same owner with different isInterface values). - int hashCode = hash(tag, owner, name, descriptor, referenceKind); + int hashCode = hash(tag, owner, name, descriptor, data); Entry entry = get(hashCode); while (entry != null) { if (entry.tag == tag && entry.hashCode == hashCode - && entry.data == referenceKind + && entry.data == data && entry.owner.equals(owner) && entry.name.equals(name) && entry.value.equals(descriptor)) { @@ -851,8 +854,7 @@ Symbol addConstantMethodHandle( constantPool.put112( tag, referenceKind, addConstantMethodref(owner, name, descriptor, isInterface).index); } - return put( - new Entry(constantPoolCount++, tag, owner, name, descriptor, referenceKind, hashCode)); + return put(new Entry(constantPoolCount++, tag, owner, name, descriptor, data, hashCode)); } /** @@ -866,16 +868,36 @@ Symbol addConstantMethodHandle( * @param owner the internal name of a class of interface. * @param name a field or method name. * @param descriptor a field or method descriptor. + * @param isInterface whether owner is an interface or not. */ private void addConstantMethodHandle( final int index, final int referenceKind, final String owner, final String name, - final String descriptor) { + final String descriptor, + final boolean isInterface) { final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG; - int hashCode = hash(tag, owner, name, descriptor, referenceKind); - add(new Entry(index, tag, owner, name, descriptor, referenceKind, hashCode)); + final int data = getConstantMethodHandleSymbolData(referenceKind, isInterface); + int hashCode = hash(tag, owner, name, descriptor, data); + add(new Entry(index, tag, owner, name, descriptor, data, hashCode)); + } + + /** + * Returns the {@link Symbol#data} field for a CONSTANT_MethodHandle_info Symbol. + * + * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link + * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param isInterface whether owner is an interface or not. + */ + private static int getConstantMethodHandleSymbolData( + final int referenceKind, final boolean isInterface) { + if (referenceKind > Opcodes.H_PUTSTATIC && isInterface) { + return referenceKind << 8; + } + return referenceKind; } /** diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java index f5384f8c6c..917d094026 100644 --- a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java +++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java @@ -309,7 +309,7 @@ private Class getClassForName(final String className) { private static class ClassReaderWrapper { private static final Logger LOGGER = Logger.getLogger(ClassReader.class.getName()); - private static final int WARN_VERSION = Opcodes.V23; + private static final int WARN_VERSION = Opcodes.V24; private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096; private final byte[] b; diff --git a/core-server/src/main/resources/META-INF/NOTICE.markdown b/core-server/src/main/resources/META-INF/NOTICE.markdown index 27c798d82b..2016cb4dde 100644 --- a/core-server/src/main/resources/META-INF/NOTICE.markdown +++ b/core-server/src/main/resources/META-INF/NOTICE.markdown @@ -36,7 +36,7 @@ org.glassfish.jersey.server.internal.monitoring.core * Copyright (c) 2015-2018 Oracle and/or its affiliates. All rights reserved. * Copyright 2010-2013 Coda Hale and Yammer, Inc. -org.objectweb.asm Version 9.7 +org.objectweb.asm Version 9.7.1 * License: Modified BSD (https://asm.ow2.io/license.html) * Copyright: (c) 2000-2011 INRIA, France Telecom. All rights reserved. diff --git a/etc/jenkins/Jenkinsfile_EE4J_build b/etc/jenkins/Jenkinsfile_EE4J_build index 65a8b56873..609d150c2d 100644 --- a/etc/jenkins/Jenkinsfile_EE4J_build +++ b/etc/jenkins/Jenkinsfile_EE4J_build @@ -5,6 +5,10 @@ pipeline { triggers { pollSCM('H H * * *') } + options { + disableConcurrentBuilds() + buildDiscarder(logRotator(numToKeepStr:'15', artifactNumToKeepStr: '2' )) + } tools { jdk 'oracle-jdk8-latest' maven 'apache-maven-latest' diff --git a/etc/jenkins/Jenkinsfile_ci_build b/etc/jenkins/Jenkinsfile_ci_build index cc3401fac9..276cc17535 100644 --- a/etc/jenkins/Jenkinsfile_ci_build +++ b/etc/jenkins/Jenkinsfile_ci_build @@ -3,6 +3,8 @@ pipeline { options { timeout(time: 30, activity: true, unit: 'HOURS') + buildDiscarder(logRotator(numToKeepStr:'15', artifactNumToKeepStr: '2' )) + disableConcurrentBuilds() } stages { diff --git a/etc/jenkins/Jenkinsfile_master_build b/etc/jenkins/Jenkinsfile_master_build index a4c5483d25..9b62947b64 100644 --- a/etc/jenkins/Jenkinsfile_master_build +++ b/etc/jenkins/Jenkinsfile_master_build @@ -5,6 +5,10 @@ pipeline { triggers { pollSCM('H H * * *') } + options { + disableConcurrentBuilds() + buildDiscarder(logRotator(numToKeepStr:'15', artifactNumToKeepStr: '2' )) + } tools { jdk 'oracle-jdk8-latest' maven 'apache-maven-latest' diff --git a/examples/NOTICE.md b/examples/NOTICE.md index 6f522b3e89..ed616c270a 100644 --- a/examples/NOTICE.md +++ b/examples/NOTICE.md @@ -71,7 +71,7 @@ Javassist Version 3.30.2-GA * Project: http://www.javassist.org/ * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. -Jackson JAX-RS Providers Version 2.17.2 +Jackson JAX-RS Providers Version 2.18.0 * License: Apache License, 2.0 * Project: https://github.com/FasterXML/jackson-jaxrs-providers * Copyright: (c) 2009-2023 FasterXML, LLC. All rights reserved unless otherwise indicated. @@ -96,7 +96,7 @@ KineticJS, v4.7.1 * Project: http://www.kineticjs.com, https://github.com/ericdrowell/KineticJS * Copyright: Eric Rowell -org.objectweb.asm Version 9.7 +org.objectweb.asm Version 9.7.1 * License: Modified BSD (https://asm.ow2.io/license.html) * Copyright (c) 2000-2011 INRIA, France Telecom. All rights reserved. diff --git a/examples/groovy/pom.xml b/examples/groovy/pom.xml index a921f41d6e..77d282157f 100644 --- a/examples/groovy/pom.xml +++ b/examples/groovy/pom.xml @@ -42,6 +42,10 @@ org.junit.jupiter junit-jupiter-api + + org.ow2.asm + asm + diff --git a/examples/helloworld-spring-annotations/pom.xml b/examples/helloworld-spring-annotations/pom.xml index e31a2d7e1a..711712d10e 100644 --- a/examples/helloworld-spring-annotations/pom.xml +++ b/examples/helloworld-spring-annotations/pom.xml @@ -73,7 +73,6 @@ commons-logging commons-logging - ${commons.logging.version} org.glassfish.jersey.test-framework.providers diff --git a/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/injector/CachedConstructorAnalyzerTest.java b/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/injector/CachedConstructorAnalyzerTest.java index 85bfe568d2..9a6d8da363 100644 --- a/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/injector/CachedConstructorAnalyzerTest.java +++ b/incubator/cdi-inject-weld/src/test/java/org/glassfish/jersey/inject/weld/internal/injector/CachedConstructorAnalyzerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests {@link CachedConstructorAnalyzer}. @@ -110,7 +111,8 @@ public void testBothAnnotatedConstructor() { Constructor constructor = analyzer.getConstructor(); assertEquals(1, constructor.getParameterCount()); - assertEquals(Integer.class, constructor.getParameterTypes()[0]); + Class parameterType = constructor.getParameterTypes()[0]; + assertTrue(parameterType.equals(String.class) || parameterType.equals(Integer.class)); } @Test diff --git a/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/injector/CachedConstructorAnalyzerTest.java b/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/injector/CachedConstructorAnalyzerTest.java index cc9e78a557..7e105aac00 100644 --- a/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/injector/CachedConstructorAnalyzerTest.java +++ b/inject/cdi2-se/src/test/java/org/glassfish/jersey/inject/cdi/se/injector/CachedConstructorAnalyzerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests {@link CachedConstructorAnalyzer}. @@ -110,7 +111,8 @@ public void testBothAnnotatedConstructor() { Constructor constructor = analyzer.getConstructor(); assertEquals(1, constructor.getParameterCount()); - assertEquals(Integer.class, constructor.getParameterTypes()[0]); + Class parameterType = constructor.getParameterTypes()[0]; + assertTrue(parameterType.equals(String.class) || parameterType.equals(Integer.class)); } @Test diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java index a209c27c9d..ac0b5c9dca 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.core.json.PackageVersion; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectReader; @@ -130,17 +132,22 @@ private void updateFactoryConstraints(JsonFactory jsonFactory) { if (maxStringLength != StreamReadConstraints.DEFAULT_MAX_STRING_LEN) { final StreamReadConstraints constraints = jsonFactory.streamReadConstraints(); - jsonFactory.setStreamReadConstraints( - StreamReadConstraints.builder() - // our - .maxStringLength(maxStringLength) - // customers - .maxDocumentLength(constraints.getMaxDocumentLength()) - .maxNameLength(constraints.getMaxNameLength()) - .maxNestingDepth(constraints.getMaxNestingDepth()) - .maxNumberLength(constraints.getMaxNumberLength()) - .build() - ); + StreamReadConstraints.Builder builder = StreamReadConstraints.builder() + // our + .maxStringLength(maxStringLength) + // customers + .maxDocumentLength(constraints.getMaxDocumentLength()) + .maxNameLength(constraints.getMaxNameLength()) + .maxNestingDepth(constraints.getMaxNestingDepth()) + .maxNumberLength(constraints.getMaxNumberLength()); + + if (PackageVersion.VERSION.getMinorVersion() >= 18) { + builder.maxTokenCount(constraints.getMaxTokenCount()); + } else { + LOGGER.warning(LocalizationMessages.ERROR_JACKSON_STREAMREADCONSTRAINTS_218("maxTokenCount")); + } + + jsonFactory.setStreamReadConstraints(builder.build()); } } } diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/base/ProviderBase.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/base/ProviderBase.java index bf365bbdfd..350be25fa3 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/base/ProviderBase.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/base/ProviderBase.java @@ -43,6 +43,7 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.util.LookupCache; import com.fasterxml.jackson.databind.util.LRUMap; import com.fasterxml.jackson.databind.type.TypeFactory; @@ -186,14 +187,12 @@ public abstract class ProviderBase< /** * Cache for resolved endpoint configurations when reading JSON data */ - protected final LRUMap _readers - = new LRUMap(16, 120); + protected final LookupCache _readers; /** * Cache for resolved endpoint configurations when writing JSON data */ - protected final LRUMap _writers - = new LRUMap(16, 120); + protected final LookupCache _writers; /* /********************************************************** @@ -202,8 +201,9 @@ public abstract class ProviderBase< */ protected ProviderBase(MAPPER_CONFIG mconfig) { - _mapperConfig = mconfig; - _jaxRSFeatures = JAXRS_FEATURE_DEFAULTS; + this(mconfig, + new LRUMap<>(16, 120), + new LRUMap<>(16, 120)); } /** @@ -214,8 +214,19 @@ protected ProviderBase(MAPPER_CONFIG mconfig) { */ @Deprecated // just to denote it should NOT be directly called; will NOT be removed protected ProviderBase() { - _mapperConfig = null; + this(null); + } + /** + * @since 2.17 + */ + protected ProviderBase(MAPPER_CONFIG mconfig, + LookupCache readerCache, + LookupCache writerCache) + { + _mapperConfig = mconfig; _jaxRSFeatures = JAXRS_FEATURE_DEFAULTS; + _readers = readerCache; + _writers = writerCache; } /* diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JacksonJaxbJsonProvider.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JacksonJaxbJsonProvider.java index eb7c072f0c..ac207b82f5 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JacksonJaxbJsonProvider.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JacksonJaxbJsonProvider.java @@ -25,7 +25,7 @@ */ @Provider @Consumes(MediaType.WILDCARD) // NOTE: required to support "non-standard" JSON variants -@Produces(MediaType.WILDCARD) +@Produces({MediaType.APPLICATION_JSON, "text/json", MediaType.WILDCARD}) public class JacksonJaxbJsonProvider extends JacksonJsonProvider { /** * Default annotation sets to use, if not explicitly defined during diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JacksonJsonProvider.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JacksonJsonProvider.java index 0871ece423..13f92758e3 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JacksonJsonProvider.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/jackson/jaxrs/json/JacksonJsonProvider.java @@ -52,7 +52,7 @@ */ @Provider @Consumes(MediaType.WILDCARD) // NOTE: required to support "non-standard" JSON variants -@Produces(MediaType.WILDCARD) +@Produces({MediaType.APPLICATION_JSON, "text/json", MediaType.WILDCARD}) public class JacksonJsonProvider extends ProviderBase3.0.1.Final - 9.7 + 9.7.1 1.9.22.1 1.70 2.16.1 1.16.1 - - 1.3.3 + + 1.3.4 1.7.0 1.6.4 2.8.4 @@ -2161,7 +2161,7 @@ 2.10.0 4.5.14 5.3.1 - 2.17.2 + 2.18.0 3.30.2-GA 1.3.7 3.3.2.Final @@ -2191,7 +2191,7 @@ 6.0.0 6.0.1 - 2.0.13 + 2.0.16 6.0.18 7.10.2 6.14.3 diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/server/wadl/NoJAXBNoWadlTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/server/wadl/NoJAXBNoWadlTest.java index 70ee14df95..e068287555 100644 --- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/server/wadl/NoJAXBNoWadlTest.java +++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/server/wadl/NoJAXBNoWadlTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -35,6 +35,9 @@ import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.PrintStream; +import java.util.List; +import java.util.Arrays; +import java.util.Collections; public class NoJAXBNoWadlTest extends JerseyTest { @@ -72,7 +75,9 @@ public void testOptionsNoWadl() { try (Response r = target("dummy").request(MediaTypes.WADL_TYPE).options()) { String headers = r.getHeaderString(HttpHeaders.ALLOW); - Assertions.assertEquals("OPTIONS,PUT", headers); + List methods = Arrays.asList(headers.split(",")); + Collections.sort(methods); + Assertions.assertEquals(Arrays.asList("OPTIONS", "PUT"), methods); } System.out.println(readableStream.toString()); Assertions.assertTrue( diff --git a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonJacksonTest.java b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonJacksonTest.java index 7aebd55951..785e883026 100644 --- a/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonJacksonTest.java +++ b/tests/osgi/functional/src/test/java/org/glassfish/jersey/osgi/test/basic/JsonJacksonTest.java @@ -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 @@ -51,6 +51,7 @@ public static Option[] configuration() { mavenBundle().groupId("com.fasterxml.jackson.core").artifactId("jackson-core").versionAsInProject(), mavenBundle().groupId("com.fasterxml.jackson.core").artifactId("jackson-databind").versionAsInProject(), mavenBundle().groupId("com.fasterxml.jackson.core").artifactId("jackson-annotations").versionAsInProject(), + mavenBundle().groupId("com.fasterxml.jackson.module").artifactId("jackson-module-jakarta-xmlbind-annotations") .versionAsInProject() )); diff --git a/tools/jersey-release-notes-maven-plugin/pom.xml b/tools/jersey-release-notes-maven-plugin/pom.xml index fa51932187..1522089169 100644 --- a/tools/jersey-release-notes-maven-plugin/pom.xml +++ b/tools/jersey-release-notes-maven-plugin/pom.xml @@ -74,12 +74,12 @@ commons-io commons-io - 2.11.0 + 2.17.0 org.apache.maven maven-compat - 3.8.1 + ${maven.version} test