Fix for the issue 3796 Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/inject/ProviderBinder.java b/core-common/src/main/java/org/glassfish/jersey/internal/inject/ProviderBinder.java index 0d302b4..1e10bea 100644 --- a/core-common/src/main/java/org/glassfish/jersey/internal/inject/ProviderBinder.java +++ b/core-common/src/main/java/org/glassfish/jersey/internal/inject/ProviderBinder.java
@@ -72,8 +72,53 @@ * @param providerClass provider class. * @param model contract provider model. */ - public static void bindProvider(Class<?> providerClass, ContractProvider model, InjectionManager injectionManager) { - injectionManager.register(CompositeBinder.wrap(createProviderBinders(providerClass, model))); + public static <T> void bindProvider(Class<T> providerClass, ContractProvider model, InjectionManager injectionManager) { + final T instance = injectionManager.getInstance(providerClass); + if (instance != null) { + injectionManager.register(createInstanceBinder(instance, model)); + } else { + injectionManager.register(createClassBinder(model)); + } + } + + private static <T> Binder createInstanceBinder(T instance, ContractProvider model) { + + return new AbstractBinder() { + + @Override + protected void configure() { + final InstanceBinding binding = bind(instance) + .in(model.getScope()) + .qualifiedBy(CustomAnnotationLiteral.INSTANCE); + binding.to(model.getContracts()); + int priority = model.getPriority(model.getImplementationClass()); + if (priority > ContractProvider.NO_PRIORITY) { + binding.ranked(priority); + } + + } + }; + + } + + private static Binder createClassBinder(ContractProvider model) { + + return new AbstractBinder() { + + @Override + protected void configure() { + final ClassBinding binding = bind(model.getImplementationClass()) + .in(model.getScope()) + .qualifiedBy(CustomAnnotationLiteral.INSTANCE); + binding.to(model.getContracts()); + int priority = model.getPriority(model.getImplementationClass()); + if (priority > ContractProvider.NO_PRIORITY) { + binding.ranked(priority); + } + + } + }; + } private static Collection<Binder> createProviderBinders(Class<?> providerClass, ContractProvider model) { @@ -110,7 +155,7 @@ * @param model contract provider model. */ public static void bindProvider(Object providerInstance, ContractProvider model, InjectionManager injectionManager) { - injectionManager.register(CompositeBinder.wrap(createProviderBinders(providerInstance, model))); + injectionManager.register(createInstanceBinder(providerInstance, model)); } private static Collection<Binder> createProviderBinders(Object providerInstance, ContractProvider model) {
diff --git a/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java b/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java index a979b37..a084891 100644 --- a/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java +++ b/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java
@@ -49,6 +49,7 @@ import org.glassfish.jersey.internal.inject.Binder; import org.glassfish.jersey.internal.inject.CompositeBinder; import org.glassfish.jersey.internal.inject.InjectionManager; +import org.glassfish.jersey.internal.inject.ProviderBinder; import org.glassfish.jersey.internal.spi.AutoDiscoverable; import org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable; import org.glassfish.jersey.internal.util.PropertiesHelper; @@ -570,7 +571,8 @@ * @param forcedOnly defines whether all or only forced auto-discoverables should be configured. */ public void configureAutoDiscoverableProviders(final InjectionManager injectionManager, - final Collection<AutoDiscoverable> autoDiscoverables, final boolean forcedOnly) { + final Collection<AutoDiscoverable> autoDiscoverables, + final boolean forcedOnly) { // Check whether meta providers have been initialized for a config this config has been loaded from. if (!disableMetaProviderConfiguration) { final Set<AutoDiscoverable> providers = new TreeSet<>((o1, o2) -> { @@ -603,7 +605,7 @@ autoDiscoverable.configure(this); } catch (final Exception e) { LOGGER.log(Level.FINE, - LocalizationMessages.AUTODISCOVERABLE_CONFIGURATION_FAILED(autoDiscoverable.getClass()), e); + LocalizationMessages.AUTODISCOVERABLE_CONFIGURATION_FAILED(autoDiscoverable.getClass()), e); } } } @@ -652,10 +654,10 @@ } private void configureExternalObjects(InjectionManager injectionManager) { - componentBag.getInstances(model -> ComponentBag.EXTERNAL_ONLY.test(model, injectionManager)) - .forEach(injectionManager::register); - componentBag.getClasses(model -> ComponentBag.EXTERNAL_ONLY.test(model, injectionManager)) - .forEach(injectionManager::register); + componentBag.getInstances(model -> ComponentBag.EXTERNAL_ONLY.test(model, injectionManager)) + .forEach(injectionManager::register); + componentBag.getClasses(model -> ComponentBag.EXTERNAL_ONLY.test(model, injectionManager)) + .forEach(injectionManager::register); } private void configureFeatures(InjectionManager injectionManager, @@ -701,6 +703,10 @@ if (success) { processed.add(registration); + final ContractProvider providerModel = componentBag.getModel(feature.getClass()); + if (providerModel != null) { + ProviderBinder.bindProvider(feature, providerModel, injectionManager); + } configureFeatures(injectionManager, processed, resetRegistrations(), managedObjectsFinalizer); enabledFeatureClasses.add(registration.getFeatureClass()); enabledFeatures.add(feature);
diff --git a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/CdiSeInjectionManager.java b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/CdiSeInjectionManager.java index a75f946..7d1e88d 100644 --- a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/CdiSeInjectionManager.java +++ b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/CdiSeInjectionManager.java
@@ -21,6 +21,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -96,7 +97,7 @@ @Override public <T> T createAndInitialize(Class<T> createMe) { - if (container != null) { + if (isInitialized()) { Unmanaged.UnmanagedInstance<T> unmanaged = new Unmanaged<>(createMe).newInstance(); return unmanaged.produce() .inject() @@ -149,7 +150,7 @@ @SuppressWarnings("unchecked") private <T> T getInstanceInternal(Type contractOrImpl, Annotation... qualifiers) { - Set<Bean<?>> beans = beanManager.getBeans(contractOrImpl, qualifiers); + Set<Bean<?>> beans = isInitialized() ? beanManager.getBeans(contractOrImpl, qualifiers) : Collections.emptySet(); if (beans.isEmpty()) { return null; } @@ -159,6 +160,17 @@ return (T) beanManager.getReference(bean, contractOrImpl, ctx); } + /** + * checks if being invoked before #completeRegistration + * there are 2 signs for that - container is null (not initialized) + * and beanManager is null (because it appears from initialized container) + * + * @return true if completeRegistration was already invoked. False if it's not yet initialized + */ + private boolean isInitialized() { + return container != null && beanManager != null; + } + @Override @SuppressWarnings("unchecked") public Object getInstance(ForeignDescriptor foreignDescriptor) { @@ -178,7 +190,7 @@ } else { throw new RuntimeException( org.glassfish.jersey.internal.LocalizationMessages - .UNKNOWN_DESCRIPTOR_TYPE(binding.getClass().getSimpleName())); + .UNKNOWN_DESCRIPTOR_TYPE(binding.getClass().getSimpleName())); } Set<Bean<?>> beans = beanManager.getBeans(clazz); @@ -206,7 +218,7 @@ @Override @SuppressWarnings("unchecked") public void inject(Object instance) { - if (beanManager != null) { + if (isInitialized()) { AnnotatedType annotatedType = beanManager.createAnnotatedType((Class) instance.getClass()); InjectionTarget injectionTarget = beanManager.createInjectionTarget(annotatedType); CreationalContext context = beanManager.createCreationalContext(null);
diff --git a/tests/integration/jersey-3796/pom.xml b/tests/integration/jersey-3796/pom.xml new file mode 100644 index 0000000..02045c9 --- /dev/null +++ b/tests/integration/jersey-3796/pom.xml
@@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Copyright (c) 2018 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 + +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.glassfish.jersey.tests.integration</groupId> + <artifactId>project</artifactId> + <version>2.28-SNAPSHOT</version> + </parent> + + <artifactId>jersey-3796</artifactId> + <packaging>war</packaging> + <name>jersey-tests-integration-jersey-3796</name> + + <description> + Reproducer of JERSEY-3796. + </description> + + <dependencies> + <dependency> + <groupId>org.glassfish.jersey.containers</groupId> + <artifactId>jersey-container-servlet</artifactId> + </dependency> + + <dependency> + <groupId>org.glassfish.jersey.media</groupId> + <artifactId>jersey-media-json-jackson</artifactId> + </dependency> + + <dependency> + <groupId>org.glassfish.jersey.test-framework.providers</groupId> + <artifactId>jersey-test-framework-provider-external</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.glassfish.jersey.test-framework.providers</groupId> + <artifactId>jersey-test-framework-provider-bundle</artifactId> + <type>pom</type> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.mortbay.jetty</groupId> + <artifactId>jetty-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project>
diff --git a/tests/integration/jersey-3796/src/main/java/org/glassfish/jersey/tests/integration/jersey3796/MyApplication.java b/tests/integration/jersey-3796/src/main/java/org/glassfish/jersey/tests/integration/jersey3796/MyApplication.java new file mode 100644 index 0000000..59b19b0 --- /dev/null +++ b/tests/integration/jersey-3796/src/main/java/org/glassfish/jersey/tests/integration/jersey3796/MyApplication.java
@@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 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.integration.jersey3796; + +import org.glassfish.jersey.server.ResourceConfig; + +import javax.ws.rs.ApplicationPath; + +/** + * The very basic instance of application to init the test + */ + + +@ApplicationPath("/") +public class MyApplication + extends ResourceConfig { + + public MyApplication() { + register(MyResource.class); + register(MyProvider.class); + } +} +
diff --git a/tests/integration/jersey-3796/src/main/java/org/glassfish/jersey/tests/integration/jersey3796/MyProvider.java b/tests/integration/jersey-3796/src/main/java/org/glassfish/jersey/tests/integration/jersey3796/MyProvider.java new file mode 100644 index 0000000..bf93908 --- /dev/null +++ b/tests/integration/jersey-3796/src/main/java/org/glassfish/jersey/tests/integration/jersey3796/MyProvider.java
@@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018 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.integration.jersey3796; + +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.core.Feature; +import javax.ws.rs.core.FeatureContext; +import javax.ws.rs.ext.Provider; +import java.util.HashMap; +import java.util.Map; + +/** + * This is the purpose of the whole test. + * Here we test if initialized providers (by interfaces) are initialized only once per scope. + * This means the one class per all implemented provider interfaces. + */ +@Provider +@Priority(Priorities.USER + 2) +public class MyProvider implements Feature, ContainerRequestFilter, ContainerResponseFilter { + + /** + * common map which in case of wrong initialization could cause NPE or not contain some expected element + */ + private Map<String, String> sameInstance; + + /** + * Feature method which is invoked as the first and initializes the common map. + * It also puts the toString value of the implementing class into the map + * + * @param context feature context (here just skipped) + * @return true - always success + */ + @Override + public boolean configure(FeatureContext context) { + sameInstance = new HashMap<>(); + sameInstance.put("Feature", this.toString()); + return true; + } + + /** + * Request filter provider - uses the common map (if it's not initialized the NPE is thrown). + * Puts the toString value of the implementing class into the map + * + * @param requestContext request context (here just skipped) + */ + @Override + public void filter(ContainerRequestContext requestContext) { + sameInstance.put("Request", this.toString()); + } + + /** + * Response filter provider - uses the common map (and is called as the last in the providers sequence) + * if map is not initialized the NPE is thrown. + * Puts the toString value of the implementing class into the map + * + * maps all elements of the map to the response entity (which is map is well) + * + * @param requestContext request context (here just skipped) + * @param responseContext response context (here just skipped) + */ + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { + sameInstance.put("Response", this.toString()); + ((Map) responseContext.getEntity()).putAll(sameInstance); + } + +} \ No newline at end of file
diff --git a/tests/integration/jersey-3796/src/main/java/org/glassfish/jersey/tests/integration/jersey3796/MyResource.java b/tests/integration/jersey-3796/src/main/java/org/glassfish/jersey/tests/integration/jersey3796/MyResource.java new file mode 100644 index 0000000..2a5ea74 --- /dev/null +++ b/tests/integration/jersey-3796/src/main/java/org/glassfish/jersey/tests/integration/jersey3796/MyResource.java
@@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 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.integration.jersey3796; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import java.util.HashMap; +import java.util.Map; + +/** + * Root resource (exposed at "myresource" path) + */ +@Path("/myresource") +public class MyResource { + + /** + * Method handling HTTP GET requests. The returned object will be sent + * to the client as "application/json" media type. + * + * @return Empty map to be willed by providers. + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + public Map<String, String> getIt() { + return new HashMap<>(); + } +} \ No newline at end of file
diff --git a/tests/integration/jersey-3796/src/main/webapp/WEB-INF/web.xml b/tests/integration/jersey-3796/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..3ec7e3a --- /dev/null +++ b/tests/integration/jersey-3796/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Copyright (c) 2015, 2018 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 + +--> + +<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> + <servlet> + <servlet-name>jersey3796Servlet</servlet-name> + <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> + <init-param> + <param-name>javax.ws.rs.Application</param-name> + <param-value>org.glassfish.jersey.tests.integration.jersey3796.MyApplication</param-value> + </init-param> + <load-on-startup>1</load-on-startup> + </servlet> + <servlet-mapping> + <servlet-name>jersey3796Servlet</servlet-name> + <url-pattern>/*</url-pattern> + </servlet-mapping> +</web-app> \ No newline at end of file
diff --git a/tests/integration/jersey-3796/src/test/java/org/glassfish/jersey/tests/integration/jersey3796/Jersey3796ITCase.java b/tests/integration/jersey-3796/src/test/java/org/glassfish/jersey/tests/integration/jersey3796/Jersey3796ITCase.java new file mode 100644 index 0000000..7d552e8 --- /dev/null +++ b/tests/integration/jersey-3796/src/test/java/org/glassfish/jersey/tests/integration/jersey3796/Jersey3796ITCase.java
@@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 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.integration.jersey3796; + +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.external.ExternalTestContainerFactory; +import org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory; +import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; +import org.glassfish.jersey.test.spi.TestContainerException; +import org.glassfish.jersey.test.spi.TestContainerFactory; +import org.junit.Assert; +import org.junit.Test; + +import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; +import java.util.Map; + +/** + * Tests the one class initialization per all declared provider interfaces + */ +public class Jersey3796ITCase extends JerseyTest { + + @Override + protected Application configure() { + return new MyApplication(); + } + + @Override + protected TestContainerFactory getTestContainerFactory() throws TestContainerException { + return new ExternalTestContainerFactory(); + } + + @Test + public void testSameInstanceForProviders() { + final Map response = target("/myresource").request(MediaType.APPLICATION_JSON_TYPE).get(Map.class); + //Map shall not be null + Assert.assertNotNull(response); + //Map shall contain exactly three elements + Assert.assertEquals(3, response.size()); + //Map shall contain ony keys Feature, Request and Response + //Values of that keys shall be equals. + //Equality of all values indicates the class is only one per all tested providers + Assert.assertEquals(response.get("Feature"), response.get("Request")); + Assert.assertEquals(response.get("Feature"), response.get("Response")); + Assert.assertEquals(response.get("Response"), response.get("Request")); + } +}
diff --git a/tests/integration/pom.xml b/tests/integration/pom.xml index 77d9154..530de45 100644 --- a/tests/integration/pom.xml +++ b/tests/integration/pom.xml
@@ -85,6 +85,7 @@ <module>jersey-2846</module> <module>jersey-2878</module> <module>jersey-2892</module> + <module>jersey-3796</module> <module>jersey-780</module> <module>jersey-3992</module> <module>portability-jersey-1</module>