diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java index e69801e03f..c152784301 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JacksonFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -29,8 +29,10 @@ import org.glassfish.jersey.jackson.internal.DefaultJacksonJaxbJsonProvider; import org.glassfish.jersey.jackson.internal.FilteringJacksonJaxbJsonProvider; import org.glassfish.jersey.jackson.internal.JacksonFilteringFeature; +import org.glassfish.jersey.jackson.internal.JaxrsFeatureBag; import org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.JsonMappingExceptionMapper; import org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.JsonParseExceptionMapper; +import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature; import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider; import org.glassfish.jersey.message.MessageProperties; import org.glassfish.jersey.message.filtering.EntityFilteringFeature; @@ -41,7 +43,7 @@ * @author Stepan Kopriva * @author Michal Gajdos */ -public class JacksonFeature implements Feature { +public class JacksonFeature extends JaxrsFeatureBag implements Feature { /** * Define whether to use Jackson's exception mappers ore not @@ -100,6 +102,16 @@ public JacksonFeature maxStringLength(int maxStringLength) { return this; } + /** + * Register {@link JaxRSFeature} with the Jackson providers. + * @param feature the {@link JaxRSFeature} to be enabled or disabled. + * @param state {@code true} for enabling the feature, {@code false} for disabling. + * @return JacksonFeature with {@link JaxRSFeature} registered to be set on a created Jackson provider. + */ + public JacksonFeature jaxrsFeature(JaxRSFeature feature, boolean state) { + return super.jaxrsFeature(feature, state); + } + private static final String JSON_FEATURE = JacksonFeature.class.getSimpleName(); @Override @@ -138,6 +150,10 @@ public boolean configure(final FeatureContext context) { context.property(MessageProperties.JSON_MAX_STRING_LENGTH, maxStringLength); } + if (hasJaxrsFeature()) { + context.property(JaxrsFeatureBag.JAXRS_FEATURE, this); + } + return true; } } diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JaxRSFeatureObjectMapper.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JaxRSFeatureObjectMapper.java new file mode 100644 index 0000000000..d759e56243 --- /dev/null +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/JaxRSFeatureObjectMapper.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.glassfish.jersey.jackson.internal.AbstractObjectMapper; +import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature; + + +/** + * The Jackson {@link ObjectMapper} supporting {@link JaxRSFeature}s. + */ +public class JaxRSFeatureObjectMapper extends AbstractObjectMapper { + + public JaxRSFeatureObjectMapper() { + super(); + } + + /** + * Method for changing state of an on/off {@link org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature} + * features. + */ + public ObjectMapper configure(JaxRSFeature f, boolean state) { + jaxrsFeatureBag.jaxrsFeature(f, state); + return this; + } + + /** + * Method for enabling specified {@link org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature}s + * for parser instances this object mapper creates. + */ + public ObjectMapper enable(JaxRSFeature... features) { + if (features != null) { + for (JaxRSFeature f : features) { + jaxrsFeatureBag.jaxrsFeature(f, true); + } + } + return this; + } + + /** + * Method for disabling specified {@link org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature}s + * for parser instances this object mapper creates. + */ + public ObjectMapper disable(JaxRSFeature... features) { + if (features != null) { + for (JaxRSFeature f : features) { + jaxrsFeatureBag.jaxrsFeature(f, false); + } + } + return this; + } +} diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/AbstractObjectMapper.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/AbstractObjectMapper.java new file mode 100644 index 0000000000..2f331cfe42 --- /dev/null +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/AbstractObjectMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jackson.internal; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Internal ObjectMapper with {@link JaxrsFeatureBag}. + */ +public abstract class AbstractObjectMapper extends ObjectMapper { + protected AbstractObjectMapper() { + + } + protected JaxrsFeatureBag jaxrsFeatureBag = new JaxrsFeatureBag(); +} diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java index bea071483a..29951abd94 100644 --- a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/DefaultJacksonJaxbJsonProvider.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.StreamReadConstraints; -import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.core.json.PackageVersion; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.Module; @@ -41,6 +40,7 @@ import javax.inject.Singleton; import javax.ws.rs.core.Configuration; import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; import javax.ws.rs.ext.Providers; /** @@ -53,8 +53,7 @@ public class DefaultJacksonJaxbJsonProvider extends JacksonJaxbJsonProvider { @Inject public DefaultJacksonJaxbJsonProvider(@Context Providers providers, @Context Configuration config) { - this.commonConfig = config; - _providers = providers; + this(providers, config, DEFAULT_ANNOTATIONS); } //do not register JaxbAnnotationModule because it brakes default annotations processing @@ -64,6 +63,20 @@ public DefaultJacksonJaxbJsonProvider(Providers providers, Configuration config, super(annotationsToUse); this.commonConfig = config; _providers = providers; + + Object jaxrsFeatureBag = config.getProperty(JaxrsFeatureBag.JAXRS_FEATURE); + if (jaxrsFeatureBag != null && (JaxrsFeatureBag.class.isInstance(jaxrsFeatureBag))) { + ((JaxrsFeatureBag) jaxrsFeatureBag).configureJaxrsFeatures(this); + } + } + + @Override + protected ObjectMapper _locateMapperViaProvider(Class type, MediaType mediaType) { + ObjectMapper mapper = super._locateMapperViaProvider(type, mediaType); + if (AbstractObjectMapper.class.isInstance(mapper)) { + ((AbstractObjectMapper) mapper).jaxrsFeatureBag.configureJaxrsFeatures(this); + } + return mapper; } @Override diff --git a/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/JaxrsFeatureBag.java b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/JaxrsFeatureBag.java new file mode 100644 index 0000000000..4711b56808 --- /dev/null +++ b/media/json-jackson/src/main/java/org/glassfish/jersey/jackson/internal/JaxrsFeatureBag.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jackson.internal; + +import org.glassfish.jersey.jackson.internal.jackson.jaxrs.base.ProviderBase; +import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Internal holder class for {@link JaxRSFeature} settings and their values. + */ +public class JaxrsFeatureBag { + protected static final String JAXRS_FEATURE = "jersey.config.jackson.jaxrs.feature"; + + private static class JaxRSFeatureState { + /* package */ final JaxRSFeature feature; + /* package */ final boolean state; + public JaxRSFeatureState(JaxRSFeature feature, boolean state) { + this.feature = feature; + this.state = state; + } + } + + private Optional> jaxRSFeature = Optional.empty(); + + public T jaxrsFeature(JaxRSFeature feature, boolean state) { + if (!jaxRSFeature.isPresent()) { + jaxRSFeature = Optional.of(new ArrayList<>()); + } + jaxRSFeature.ifPresent(list -> list.add(new JaxrsFeatureBag.JaxRSFeatureState(feature, state))); + return (T) this; + } + + protected boolean hasJaxrsFeature() { + return jaxRSFeature.isPresent(); + } + + /* package */ void configureJaxrsFeatures(ProviderBase providerBase) { + jaxRSFeature.ifPresent(list -> list.stream().forEach(state -> providerBase.configure(state.feature, state.state))); + } +} diff --git a/media/json-jackson/src/test/java/org/glassfish/jersey/jackson/internal/JaxRSFeatureTest.java b/media/json-jackson/src/test/java/org/glassfish/jersey/jackson/internal/JaxRSFeatureTest.java new file mode 100644 index 0000000000..431f8437a7 --- /dev/null +++ b/media/json-jackson/src/test/java/org/glassfish/jersey/jackson/internal/JaxRSFeatureTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jackson.internal; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.jersey.jackson.JaxRSFeatureObjectMapper; +import org.glassfish.jersey.jackson.internal.jackson.jaxrs.cfg.JaxRSFeature; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.Providers; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class JaxRSFeatureTest { + @Test + public void testJaxrsFeatureOnJacksonFeature() { + Client client = ClientBuilder.newClient() + .register(new JacksonFeature().jaxrsFeature(JaxRSFeature.READ_FULL_STREAM, false)) + .register(JaxrsFeatureFilter.class); + + try (Response r = client.target("http://xxx.yyy").request().get()) { + MatcherAssert.assertThat(r.getStatus(), Matchers.is(200)); + } + } + + @Test + public void testJaxrsFeatureOnContextResolver() { + Client client = ClientBuilder.newClient() + .register(JacksonFeature.class) + .register(JaxrsFetureContextResolver.class) + .register(JaxrsFeatureFilter.class); + + try (Response r = client.target("http://xxx.yyy").request().get()) { + MatcherAssert.assertThat(r.getStatus(), Matchers.is(200)); + } + } + + + public static class JaxrsFeatureFilter implements ClientRequestFilter { + private final DefaultJacksonJaxbJsonProvider jacksonProvider; + @Inject + public JaxrsFeatureFilter(Providers allProviders) { + jacksonProvider = (DefaultJacksonJaxbJsonProvider) + allProviders.getMessageBodyReader(Object.class, Object.class, null, MediaType.APPLICATION_JSON_TYPE); + try { + jacksonProvider.readFrom(Object.class, Object.class, null, MediaType.APPLICATION_JSON_TYPE, null, + new ByteArrayInputStream("{}".getBytes())); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + Response.Status status = jacksonProvider.isEnabled(JaxRSFeature.READ_FULL_STREAM) + ? Response.Status.FORBIDDEN + : Response.Status.OK; + requestContext.abortWith(Response.status(status).build()); + } + } + + public static class JaxrsFetureContextResolver implements ContextResolver { + + @Override + public ObjectMapper getContext(Class type) { + JaxRSFeatureObjectMapper objectMapper = new JaxRSFeatureObjectMapper(); + objectMapper.disable(JaxRSFeature.READ_FULL_STREAM); + return objectMapper; + } + } +}