Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CXF-7403 ability to override through a CDI producer the provider assiociated mediatypes #281

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,23 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.BiFunction;

import static java.util.Arrays.asList;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
Expand All @@ -41,6 +49,7 @@
import javax.enterprise.inject.spi.ProcessProducerField;
import javax.enterprise.inject.spi.ProcessProducerMethod;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.Consumes;
import javax.ws.rs.Path;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.MessageBodyReader;
Expand All @@ -52,6 +61,7 @@
import org.apache.cxf.cdi.extension.JAXRSServerFactoryCustomizationExtension;
import org.apache.cxf.feature.Feature;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.provider.ConfigurableProviderWrapper;
import org.apache.cxf.jaxrs.utils.ResourceUtils;

/**
Expand All @@ -63,7 +73,7 @@ public class JAXRSCdiResourceExtension implements Extension {

private final Set< Bean< ? > > applicationBeans = new LinkedHashSet< Bean< ? > >();
private final List< Bean< ? > > serviceBeans = new ArrayList< Bean< ? > >();
private final List< Bean< ? > > providerBeans = new ArrayList< Bean< ? > >();
private final Map< Bean<?>, Annotated > providerBeans = new HashMap<>();
private final List< Bean< ? extends Feature > > featureBeans = new ArrayList< Bean< ? extends Feature > >();
private final List< CreationalContext< ? > > disposableCreationalContexts =
new ArrayList< CreationalContext< ? > >();
Expand Down Expand Up @@ -109,9 +119,9 @@ public <T> void collect(@Observes final ProcessBean< T > event) {
} else if (event.getAnnotated().isAnnotationPresent(Path.class)) {
serviceBeans.add(event.getBean());
} else if (event.getAnnotated().isAnnotationPresent(Provider.class)) {
providerBeans.add(event.getBean());
providerBeans.put(event.getBean(), event.getAnnotated());
} else if (event.getBean().getTypes().contains(javax.ws.rs.core.Feature.class)) {
providerBeans.add((Bean< ? extends Feature >)event.getBean());
providerBeans.put(event.getBean(), event.getAnnotated());
} else if (event.getBean().getTypes().contains(Feature.class)) {
featureBeans.add((Bean< ? extends Feature >)event.getBean());
} else if (CdiBusBean.CXF.equals(event.getBean().getName())
Expand Down Expand Up @@ -285,7 +295,49 @@ private List< Object > loadExternalProviders() {
* @return the references for all discovered JAX-RS resources
*/
private List< Object > loadProviders(final BeanManager beanManager, Collection<Class<?>> limitedClasses) {
return loadBeans(beanManager, limitedClasses, providerBeans);
return loadBeans(beanManager, limitedClasses, providerBeans.keySet(), (bean, provider) -> {
final Annotated annotatedType = providerBeans.get(bean);
if ((AnnotatedMethod.class.isInstance(annotatedType) || AnnotatedField.class.isInstance(annotatedType))
&& (annotatedType.isAnnotationPresent(Consumes.class)
|| annotatedType.isAnnotationPresent(javax.ws.rs.Produces.class))) {
final boolean reader = MessageBodyReader.class.isInstance(provider);
final boolean writer = MessageBodyWriter.class.isInstance(provider);
final ConfigurableProviderWrapper wrapper;
if (reader && writer) {
wrapper = new ConfigurableProviderWrapper.ReaderWriter<>(
MessageBodyReader.class.cast(provider), MessageBodyWriter.class.cast(provider));
} else if (reader) {
wrapper = new ConfigurableProviderWrapper.Reader<>(MessageBodyReader.class.cast(provider));
} else if (writer) {
wrapper = new ConfigurableProviderWrapper.Writer<>(MessageBodyWriter.class.cast(provider));
} else {
return provider;
}

if (reader) {
Consumes consumes = annotatedType.getAnnotation(Consumes.class);
if (consumes == null) {
consumes = provider.getClass().getAnnotation(Consumes.class);
}
if (consumes != null) {
wrapper.setConsumeMediaTypes(asList(consumes.value()));
}
}

if (writer) {
javax.ws.rs.Produces produces = annotatedType.getAnnotation(javax.ws.rs.Produces.class);
if (produces == null) {
produces = provider.getClass().getAnnotation(javax.ws.rs.Produces.class);
}
if (produces != null) {
wrapper.setConsumeMediaTypes(asList(produces.value()));
}
}

return wrapper;
}
return provider;
});
}

/**
Expand All @@ -295,7 +347,7 @@ private List< Object > loadProviders(final BeanManager beanManager, Collection<C
* @return the references for all discovered JAX-RS providers
*/
private List< Object > loadServices(final BeanManager beanManager, Collection<Class<?>> limitedClasses) {
return loadBeans(beanManager, limitedClasses, serviceBeans);
return loadBeans(beanManager, limitedClasses, serviceBeans, (a, b) -> b);
}

/**
Expand All @@ -306,16 +358,18 @@ private List< Object > loadServices(final BeanManager beanManager, Collection<Cl
* @return the references for all discovered JAX-RS providers
*/
private List< Object > loadBeans(final BeanManager beanManager, Collection<Class<?>> limitedClasses,
Collection<Bean<?>> beans) {
Collection<Bean<?>> beans, final BiFunction<Bean<?>, Object, Object> processor) {
final List< Object > instances = new ArrayList<>();

for (final Bean< ? > bean: beans) {
if (limitedClasses.isEmpty() || limitedClasses.contains(bean.getBeanClass())) {
instances.add(
beanManager.getReference(
bean,
Object.class,
createCreationalContext(beanManager, bean)
processor.apply(
bean,
beanManager.getReference(
bean,
Object.class,
createCreationalContext(beanManager, bean))
)
);
}
Expand Down Expand Up @@ -392,11 +446,10 @@ private <T> void processProducer(final ProcessBean<T> event, final Type baseType
if (clazz.isAnnotationPresent(Path.class)) {
serviceBeans.add(event.getBean());
} else if (clazz.isAnnotationPresent(Provider.class)) {
providerBeans.add(event.getBean());
providerBeans.put(event.getBean(), event.getAnnotated());
} else if (clazz.isAnnotationPresent(ApplicationPath.class)) {
applicationBeans.add(event.getBean());
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cxf.jaxrs.provider;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;

// set of wrappers allowing to override mediatype for the wrapped provider.
// a real life sample is jackson which abuses */*.
public abstract class ConfigurableProviderWrapper extends AbstractConfigurableProvider {
public static class Reader<T> extends ConfigurableProviderWrapper implements MessageBodyReader<T> {
private final MessageBodyReader<T> delegate;

public Reader(final MessageBodyReader<T> delegate) {
this.delegate = delegate;
}

@Override
public boolean isReadable(final Class<?> aClass, final Type type, final Annotation[] annotations,
final MediaType mediaType) {
return delegate.isReadable(aClass, type, annotations, mediaType);
}

@Override
public T readFrom(final Class<T> aClass, final Type type, final Annotation[] annotations,
final MediaType mediaType, final MultivaluedMap<String, String> multivaluedMap,
final InputStream inputStream) throws IOException, WebApplicationException {
return delegate.readFrom(aClass, type, annotations, mediaType, multivaluedMap, inputStream);
}
}

public static class Writer<T> extends ConfigurableProviderWrapper implements MessageBodyWriter<T> {
private final MessageBodyWriter<T> delegate;

public Writer(final MessageBodyWriter<T> delegate) {
this.delegate = delegate;
}

@Override
public boolean isWriteable(final Class<?> aClass, final Type type, final Annotation[] annotations,
final MediaType mediaType) {
return delegate.isWriteable(aClass, type, annotations, mediaType);
}

@Override
public long getSize(final T t, final Class<?> type, final Type genericType, final Annotation[] annotations,
final MediaType mediaType) {
return delegate.getSize(t, type, genericType, annotations, mediaType);
}

@Override
public void writeTo(final T t, final Class<?> aClass, final Type type, final Annotation[] annotations,
final MediaType mediaType, final MultivaluedMap<String, Object> multivaluedMap,
final OutputStream outputStream) throws IOException, WebApplicationException {
delegate.writeTo(t, aClass, type, annotations, mediaType, multivaluedMap, outputStream);
}
}

public static class ReaderWriter<T> extends ConfigurableProviderWrapper implements
MessageBodyWriter<T>, MessageBodyReader<T> {
private final MessageBodyReader<T> reader;
private final MessageBodyWriter<T> writer;

public ReaderWriter(final MessageBodyReader<T> reader, final MessageBodyWriter<T> writer) {
this.reader = reader;
this.writer = writer;
}

@Override
public boolean isWriteable(final Class<?> aClass, final Type type, final Annotation[] annotations,
final MediaType mediaType) {
return writer.isWriteable(aClass, type, annotations, mediaType);
}

@Override
public long getSize(final T t, final Class<?> type, final Type genericType, final Annotation[] annotations,
final MediaType mediaType) {
return writer.getSize(t, type, genericType, annotations, mediaType);
}

@Override
public void writeTo(final T t, final Class<?> aClass, final Type type, final Annotation[] annotations,
final MediaType mediaType, final MultivaluedMap<String, Object> multivaluedMap,
final OutputStream outputStream) throws IOException, WebApplicationException {
writer.writeTo(t, aClass, type, annotations, mediaType, multivaluedMap, outputStream);
}

@Override
public boolean isReadable(final Class<?> aClass, final Type type, final Annotation[] annotations,
final MediaType mediaType) {
return reader.isReadable(aClass, type, annotations, mediaType);
}

@Override
public T readFrom(final Class<T> aClass, final Type type, final Annotation[] annotations,
final MediaType mediaType, final MultivaluedMap<String, String> multivaluedMap,
final InputStream inputStream) throws IOException, WebApplicationException {
return reader.readFrom(aClass, type, annotations, mediaType, multivaluedMap, inputStream);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,26 @@
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;

import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.systests.cdi.base.provider.Custom1ReaderWriter;
import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
import org.junit.Test;

public abstract class AbstractCdiSingleAppTest extends AbstractBusClientServerTestBase {
@Test
public void testOverridenMediaTypeForProducerSupport() {
assertStatusAndPayload(
createWebClient(getBasePath().replace("bookstore", "custom/1"), "custom1/default").get(),
Response.Status.OK.getStatusCode(),
Custom1ReaderWriter.class.getName());
assertStatusAndPayload(
createWebClient(getBasePath().replace("bookstore", "custom/1/override"), "custom1/default").get(),
406, null);
assertStatusAndPayload(
createWebClient(getBasePath().replace("bookstore", "custom/1/override"), "custom1/overriden").get(),
Response.Status.OK.getStatusCode(),
Custom1ReaderWriter.class.getName());
}

@Test
public void testInjectedVersionIsProperlyReturned() {
Response r = createWebClient(getBasePath() + "/version", MediaType.TEXT_PLAIN).get();
Expand Down Expand Up @@ -93,4 +109,11 @@ protected String getBasePath() {
}

protected abstract int getPort();

private void assertStatusAndPayload(final Response response, final int statusCode, final String payload) {
assertEquals(statusCode, response.getStatus());
if (payload != null) {
assertEquals(payload, response.readEntity(String.class));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cxf.systests.cdi.base;

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

import org.apache.cxf.systests.cdi.base.provider.Custom1ReaderWriter;

@ApplicationScoped
@Path("/custom/1")
public class Custom1Resource {
@GET
@Produces("custom1/default")
public Payload custom1Default() {
return new Payload();
}

@GET
@Path("override")
@Produces("custom1/overriden")
public Payload custom1Overriden() {
return new Payload();
}

@javax.enterprise.inject.Produces
@Consumes("custom1/overriden")
@Produces("custom1/overriden")
public Custom1ReaderWriter overriden() {
return new Custom1ReaderWriter();
}

public static class Payload {
}
}
Loading