Great news Sergey; thanks once again! On Tue, Oct 28, 2014 at 1:00 PM, Sergey Beryozkin <[email protected]> wrote:
> You are right, the issue was resolved as part of > https://issues.apache.org/jira/browse/CXF-6067 > > Many thanks for your help with identifying the issue > Please try 2.7.14-SNAPSHOT a bit later > Cheers, Sergey > > On 24/10/14 15:16, Lambert, Michael wrote: > >> Correction the problem method is ProviderFactory.getGenericInterfaces. >> Its >> not the comparator. >> >> On Thu, Oct 23, 2014 at 4:55 PM, Lambert, Michael < >> [email protected]> wrote: >> >> Fleshed it out a bit more with comment. It looks like the >>> ProviderFactory.Comparator will choose whichever class in the >>> parent/child >>> lineage which implements ExceptionMapper and base its ordering on that. >>> This means that a parent class cannot implement ExceptionMapper unless >>> the >>> child ALSO implments ExceptionMapper. >>> >>> package foo.test; >>> >>> import static org.junit.Assert.*; >>> >>> import javax.ws.rs.core.Response; >>> import javax.ws.rs.ext.ExceptionMapper; >>> import javax.ws.rs.ext.Provider; >>> >>> import org.apache.cxf.jaxrs.provider.ProviderFactory; >>> import org.apache.cxf.message.MessageImpl; >>> >>> public class ExceptionMapperTest { >>> @org.junit.Before >>> public void setup() { >>> ProviderFactory.getInstance().clearProviders(); >>> } >>> @org.junit.Test >>> public void testBadCustomExceptionMappersHierarchyWithGenerics() throws >>> Exception { >>> ProviderFactory pf = ProviderFactory.getInstance(); >>> BadExceptionMapperA badExceptionMapperA = new BadExceptionMapperA(); >>> pf.registerUserProvider(badExceptionMapperA); >>> BadExceptionMapperB badExceptionMapperB = new BadExceptionMapperB(); >>> pf.registerUserProvider(badExceptionMapperB); >>> Object mapperResponse1 = >>> pf.createExceptionMapper(RuntimeExceptionA.class, new MessageImpl()); >>> assertSame(badExceptionMapperA, mapperResponse1); >>> Object mapperResponse2 = >>> pf.createExceptionMapper(RuntimeExceptionB.class, new MessageImpl()); >>> assertSame(badExceptionMapperB, mapperResponse2); >>> Object mapperResponse3 = >>> pf.createExceptionMapper(RuntimeExceptionAA.class, new MessageImpl()); >>> assertSame(badExceptionMapperA, mapperResponse3); >>> Object mapperResponse4 = >>> pf.createExceptionMapper(RuntimeExceptionBB.class, new MessageImpl()); >>> assertSame(badExceptionMapperB, mapperResponse4); >>> } >>> /** >>> * To fix the problem the mapper must NOT extend from a class that >>> implements the ExceptionMapper interface. The parent class should be >>> * a normal java class and the mapper should implement the >>> ExceptionMapper >>> interface DIRECTLY! >>> * >>> * @throws Exception >>> */ >>> @org.junit.Test >>> public void testFixedCustomExceptionMappersHierarchyWithGenerics() >>> throws >>> Exception { >>> ProviderFactory pf = ProviderFactory.getInstance(); >>> FixedExceptionMapperA exceptionMapperA = new FixedExceptionMapperA(); >>> pf.registerUserProvider(exceptionMapperA); >>> FixedExceptionMapperB exceptionMapperB = new FixedExceptionMapperB(); >>> pf.registerUserProvider(exceptionMapperB); >>> Object mapperResponse1 = >>> pf.createExceptionMapper(RuntimeExceptionA.class, new MessageImpl()); >>> assertSame(exceptionMapperA, mapperResponse1); >>> Object mapperResponse2 = >>> pf.createExceptionMapper(RuntimeExceptionB.class, new MessageImpl()); >>> assertSame(exceptionMapperB, mapperResponse2); >>> Object mapperResponse3 = >>> pf.createExceptionMapper(RuntimeExceptionAA.class, new MessageImpl()); >>> assertSame(exceptionMapperA, mapperResponse3); >>> Object mapperResponse4 = >>> pf.createExceptionMapper(RuntimeExceptionBB.class, new MessageImpl()); >>> assertSame(exceptionMapperB, mapperResponse4); >>> } >>> >>> >>> @org.junit.Test >>> public void testGoodExceptionMappersHierarchyWithGenerics() throws >>> Exception { >>> ProviderFactory pf = ProviderFactory.getInstance(); >>> GoodRuntimeExceptionAMapper runtimeExceptionAMapper = new >>> GoodRuntimeExceptionAMapper(); >>> pf.registerUserProvider(runtimeExceptionAMapper); >>> GoodRuntimeExceptionBMapper runtimeExceptionBMapper = new >>> GoodRuntimeExceptionBMapper(); >>> pf.registerUserProvider(runtimeExceptionBMapper); >>> Object mapperResponse1 = >>> pf.createExceptionMapper(RuntimeExceptionA.class, new MessageImpl()); >>> assertSame(runtimeExceptionAMapper, mapperResponse1); >>> Object mapperResponse2 = >>> pf.createExceptionMapper(RuntimeExceptionB.class, new MessageImpl()); >>> assertSame(runtimeExceptionBMapper, mapperResponse2); >>> Object mapperResponse3 = >>> pf.createExceptionMapper(RuntimeExceptionAA.class, new MessageImpl()); >>> assertSame(runtimeExceptionAMapper, mapperResponse3); >>> Object mapperResponse4 = >>> pf.createExceptionMapper(RuntimeExceptionBB.class, new MessageImpl()); >>> assertSame(runtimeExceptionBMapper, mapperResponse4); >>> } >>> private class RuntimeExceptionA extends RuntimeException { >>> private static final long serialVersionUID = 1L; >>> } >>> private class RuntimeExceptionAA extends RuntimeExceptionA { >>> private static final long serialVersionUID = 1L; >>> } >>> private class RuntimeExceptionB extends RuntimeException { >>> private static final long serialVersionUID = 1L; >>> } >>> private class RuntimeExceptionBB extends RuntimeExceptionB { >>> private static final long serialVersionUID = 1L; >>> } >>> private class GoodRuntimeExceptionAMapper implements >>> ExceptionMapper<RuntimeExceptionA> { >>> >>> @Override >>> public Response toResponse(RuntimeExceptionA exception) { >>> // TODO Auto-generated method stub >>> return null; >>> } >>> } >>> private class GoodRuntimeExceptionBMapper implements >>> ExceptionMapper<RuntimeExceptionB> { >>> >>> @Override >>> public Response toResponse(RuntimeExceptionB exception) { >>> // TODO Auto-generated method stub >>> return null; >>> } >>> } >>> /** >>> * The parent implements ExceptionMapper and as a result will be what >>> the >>> ProviderFactory.Comparator class incorrectly chooses when sort ordering >>> * the mappers rather than its children >>> * >>> */ >>> private abstract class BadParentExceptionMapper<T extends Throwable> >>> implements ExceptionMapper<T> { >>> } >>> /** >>> * This does not implement ExceptionMapper and so its parent will be >>> incorrectly selected by he ProviderFactory.Comparator class for sort >>> ordering >>> * >>> */ >>> @Provider >>> private class BadExceptionMapperA extends >>> BadParentExceptionMapper<RuntimeExceptionA> { >>> >>> @Override >>> public Response toResponse(RuntimeExceptionA exception) { >>> return null; >>> } >>> } >>> /** >>> * This does not implement ExceptionMapper and so its parent will be >>> incorrectly selected by he ProviderFactory.Comparator class for sort >>> ordering >>> * >>> */ >>> @Provider >>> private class BadExceptionMapperB extends >>> BadParentExceptionMapper<RuntimeExceptionB> { >>> >>> @Override >>> public Response toResponse(RuntimeExceptionB exception) { >>> return null; >>> } >>> } >>> /** >>> * This does NOT implement ExceptionMapper >>> * >>> */ >>> private abstract class FixedParentExceptionMapper { >>> } >>> /** >>> * The mapper implements ExceptionMapper directly >>> * >>> */ >>> @Provider >>> private class FixedExceptionMapperA extends FixedParentExceptionMapper >>> implements ExceptionMapper<RuntimeExceptionA> { >>> >>> @Override >>> public Response toResponse(RuntimeExceptionA exception) { >>> return null; >>> } >>> } >>> /** >>> * The mapper implements ExceptionMapper directly >>> * >>> */ >>> @Provider >>> private class FixedExceptionMapperB extends FixedParentExceptionMapper >>> implements ExceptionMapper<RuntimeExceptionB> { >>> >>> @Override >>> public Response toResponse(RuntimeExceptionB exception) { >>> return null; >>> } >>> } >>> } >>> >>> >>> On Thu, Oct 23, 2014 at 4:30 PM, Lambert, Michael < >>> [email protected]> wrote: >>> >>> Sergei, >>>> >>>> Here is a test case that demonstrates the problem. Note that the only >>>> difference between the good test and the bad test is that the mapper >>>> itself >>>> is derived from a parent class. This test is based on cxf 2.7.11 but i >>>> presume it will also work with 3.0 by swapping out the providerfactory >>>> class with serviceproviderfactory. >>>> >>>> >>>> package foo.test; >>>> >>>> import static org.junit.Assert.*; >>>> >>>> import javax.ws.rs.core.Response; >>>> import javax.ws.rs.ext.ExceptionMapper; >>>> import javax.ws.rs.ext.Provider; >>>> >>>> import org.apache.cxf.jaxrs.provider.ProviderFactory; >>>> import org.apache.cxf.message.MessageImpl; >>>> >>>> public class ExceptionMapperTest { >>>> @org.junit.Before >>>> public void setup() { >>>> ProviderFactory.getInstance().clearProviders(); >>>> } >>>> @org.junit.Test >>>> public void testBadCustomExceptionMappersHierarchyWithGenerics() throws >>>> Exception { >>>> ProviderFactory pf = ProviderFactory.getInstance(); >>>> BadExceptionMapperA badExceptionMapperA = new BadExceptionMapperA(); >>>> pf.registerUserProvider(badExceptionMapperA); >>>> BadExceptionMapperB badExceptionMapperB = new BadExceptionMapperB(); >>>> pf.registerUserProvider(badExceptionMapperB); >>>> Object mapperResponse1 = >>>> pf.createExceptionMapper(RuntimeExceptionA.class, new MessageImpl()); >>>> assertSame(badExceptionMapperA, mapperResponse1); >>>> Object mapperResponse2 = >>>> pf.createExceptionMapper(RuntimeExceptionB.class, new MessageImpl()); >>>> assertSame(badExceptionMapperB, mapperResponse2); >>>> Object mapperResponse3 = >>>> pf.createExceptionMapper(RuntimeExceptionAA.class, new MessageImpl()); >>>> assertSame(badExceptionMapperA, mapperResponse3); >>>> Object mapperResponse4 = >>>> pf.createExceptionMapper(RuntimeExceptionBB.class, new MessageImpl()); >>>> assertSame(badExceptionMapperB, mapperResponse4); >>>> } >>>> >>>> @org.junit.Test >>>> public void testGoodExceptionMappersHierarchyWithGenerics() throws >>>> Exception { >>>> ProviderFactory pf = ProviderFactory.getInstance(); >>>> GoodRuntimeExceptionAMapper runtimeExceptionAMapper = new >>>> GoodRuntimeExceptionAMapper(); >>>> pf.registerUserProvider(runtimeExceptionAMapper); >>>> GoodRuntimeExceptionBMapper runtimeExceptionBMapper = new >>>> GoodRuntimeExceptionBMapper(); >>>> pf.registerUserProvider(runtimeExceptionBMapper); >>>> Object mapperResponse1 = >>>> pf.createExceptionMapper(RuntimeExceptionA.class, new MessageImpl()); >>>> assertSame(runtimeExceptionAMapper, mapperResponse1); >>>> Object mapperResponse2 = >>>> pf.createExceptionMapper(RuntimeExceptionB.class, new MessageImpl()); >>>> assertSame(runtimeExceptionBMapper, mapperResponse2); >>>> Object mapperResponse3 = >>>> pf.createExceptionMapper(RuntimeExceptionAA.class, new MessageImpl()); >>>> assertSame(runtimeExceptionAMapper, mapperResponse3); >>>> Object mapperResponse4 = >>>> pf.createExceptionMapper(RuntimeExceptionBB.class, new MessageImpl()); >>>> assertSame(runtimeExceptionBMapper, mapperResponse4); >>>> } >>>> private class RuntimeExceptionA extends RuntimeException { >>>> private static final long serialVersionUID = 1L; >>>> } >>>> private class RuntimeExceptionAA extends RuntimeExceptionA { >>>> private static final long serialVersionUID = 1L; >>>> } >>>> private class RuntimeExceptionB extends RuntimeException { >>>> private static final long serialVersionUID = 1L; >>>> } >>>> private class RuntimeExceptionBB extends RuntimeExceptionB { >>>> private static final long serialVersionUID = 1L; >>>> } >>>> private class GoodRuntimeExceptionAMapper implements >>>> ExceptionMapper<RuntimeExceptionA> { >>>> >>>> @Override >>>> public Response toResponse(RuntimeExceptionA exception) { >>>> // TODO Auto-generated method stub >>>> return null; >>>> } >>>> } >>>> private class GoodRuntimeExceptionBMapper implements >>>> ExceptionMapper<RuntimeExceptionB> { >>>> >>>> @Override >>>> public Response toResponse(RuntimeExceptionB exception) { >>>> // TODO Auto-generated method stub >>>> return null; >>>> } >>>> } >>>> public abstract class BadParentExceptionMapper<T extends Throwable> >>>> implements ExceptionMapper<T> { >>>> } >>>> @Provider >>>> public class BadExceptionMapperA extends >>>> BadParentExceptionMapper<RuntimeExceptionA> { >>>> >>>> @Override >>>> public Response toResponse(RuntimeExceptionA exception) { >>>> return null; >>>> } >>>> } >>>> @Provider >>>> public class BadExceptionMapperB extends >>>> BadParentExceptionMapper<RuntimeExceptionB> { >>>> >>>> @Override >>>> public Response toResponse(RuntimeExceptionB exception) { >>>> return null; >>>> } >>>> } >>>> } >>>> >>>> >>>> >>>> On Wed, Oct 22, 2014 at 8:06 AM, Lambert, Michael < >>>> [email protected]> wrote: >>>> >>>> Good to know Sergey we will look at it again today. Definitely >>>>> experiencing the behavior I described so we will look for some delta >>>>> between our code and the test >>>>> On Oct 22, 2014 5:50 AM, "Sergey Beryozkin" <[email protected]> >>>>> wrote: >>>>> >>>>> ProviderFactoryTest has the following: >>>>>> >>>>>> private static class RuntimeExceptionMapper1 >>>>>> extends AbstractTestExceptionMapper<RuntimeException> { >>>>>> >>>>>> } >>>>>> >>>>>> private static class RuntimeExceptionMapper2 >>>>>> extends AbstractTestExceptionMapper<WebApplicationException> >>>>>> { >>>>>> >>>>>> } >>>>>> >>>>>> private static class AbstractTestExceptionMapper<T extends >>>>>> RuntimeException> >>>>>> implements ExceptionMapper<T> { >>>>>> >>>>>> public Response toResponse(T arg0) { >>>>>> // TODO Auto-generated method stub >>>>>> return null; >>>>>> } >>>>>> >>>>>> } >>>>>> >>>>>> This is identical to your example where you say the bug is expected, >>>>>> also note WebApplicationException is RuntimeException. >>>>>> >>>>>> @Test >>>>>> public void testExceptionMappersHierarchyWithGenerics() throws >>>>>> Exception { >>>>>> ServerProviderFactory pf = ServerProviderFactory. >>>>>> getInstance(); >>>>>> RuntimeExceptionMapper1 exMapper1 = new >>>>>> RuntimeExceptionMapper1(); >>>>>> pf.registerUserProvider(exMapper1); >>>>>> RuntimeExceptionMapper2 exMapper2 = new >>>>>> RuntimeExceptionMapper2(); >>>>>> pf.registerUserProvider(exMapper2); >>>>>> assertSame(exMapper1, pf.createExceptionMapper( >>>>>> RuntimeException.class, >>>>>> new MessageImpl())); >>>>>> Object webExMapper = pf.createExceptionMapper( >>>>>> WebApplicationException.class, >>>>>> new MessageImpl()); >>>>>> assertSame(exMapper2, webExMapper); >>>>>> } >>>>>> >>>>>> When RuntimeExceptionMapper2 is registered, RuntimeExceptionMapper1 is >>>>>> not picked for WebApplicationException. Updating the test not to >>>>>> register >>>>>> RuntimeExceptionMapper2 leads to RuntimeExceptionMapper1 being >>>>>> selected for >>>>>> WebApplicationException. >>>>>> >>>>>> Looks like it is all correct to me, may be the issue is there in >>>>>> 2.7.11 >>>>>> but def not on the trunk/3.0.2 >>>>>> >>>>>> Cheers, Sergey >>>>>> >>>>>> On 21/10/14 20:04, Lambert, Michael wrote: >>>>>> >>>>>> To be clear: >>>>>>> >>>>>>> This bug will only manifest itself if two exception mapper are both >>>>>>> derived >>>>>>> from the same interface: >>>>>>> >>>>>>> public class AbstractMapper<T extends Throwable> implements >>>>>>> ExceptionMapper<T> { >>>>>>> ... >>>>>>> } >>>>>>> >>>>>>> public class FooMapper extends AbstractMapper<FooException> { >>>>>>> ... >>>>>>> } >>>>>>> >>>>>>> // ProviderFactory.ClassComparator.compare will return "0" >>>>>>> when >>>>>>> BarMapper is compared to FooMapper >>>>>>> public class BarMapper extends AbstractMapper<BarException> { >>>>>>> ... >>>>>>> } >>>>>>> >>>>>>> If each exception mapper instance is derived from a DIFFERENT >>>>>>> INTERFACE >>>>>>> everything works fine: >>>>>>> >>>>>>> public interface IFooMapper extends >>>>>>> ExceptionMapper<FooException> >>>>>>> { >>>>>>> ... >>>>>>> } >>>>>>> >>>>>>> public class FooMapper extends implements IFooMapper { >>>>>>> ... >>>>>>> } >>>>>>> >>>>>>> public interface IBarMapper extends >>>>>>> ExceptionMapper<BarException> >>>>>>> { >>>>>>> ... >>>>>>> } >>>>>>> >>>>>>> // ProviderFactory.ClassComparator.compare will return "1" or >>>>>>> -1 >>>>>>> when >>>>>>> BarMapper is compared to FooMapper >>>>>>> public class BarMapper extends implements IBarMapper { >>>>>>> ... >>>>>>> } >>>>>>> >>>>>>> >>>>>>> >>>>>>> On Tue, Oct 21, 2014 at 2:34 PM, Lambert, Michael < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>> Guys... it looks like there is a bug in ProviderFactory$ >>>>>>> >>>>>>>> ClassComparator. >>>>>>>> Given two mappers it always ends up comparing them as if both >>>>>>>> mappers >>>>>>>> were >>>>>>>> ExceptionMapper<Throwable>. There must be a problem in the way the >>>>>>>> interfaces and "actual types" are derived (which is done in the >>>>>>>> InjectionUtils class). >>>>>>>> >>>>>>>> -Mike >>>>>>>> >>>>>>>> On Tue, Oct 21, 2014 at 1:24 PM, Lambert, Michael < >>>>>>>> [email protected]> wrote: >>>>>>>> >>>>>>>> Hello gentleman! >>>>>>>> >>>>>>>>> >>>>>>>>> My impression was that when implementing ExceptionMapper that the >>>>>>>>> mapper >>>>>>>>> of the nearest superclass of an exception would be used if an exact >>>>>>>>> exceptionmapper match to the exception didnt exist. >>>>>>>>> >>>>>>>>> For example if I created a FooException which extended BarException >>>>>>>>> I >>>>>>>>> would only have to create a BarExceptionMapper to catch and map >>>>>>>>> both >>>>>>>>> types >>>>>>>>> of exceptions; the BarExceptionMapper would be used when a >>>>>>>>> FooException was >>>>>>>>> thrown. >>>>>>>>> >>>>>>>>> The red hat docs seem to reinforce this opinion: >>>>>>>>> >>>>>>>>> >>>>>>>>> - >>>>>>>>> >>>>>>>>> When any exception other than a WebApplicationException >>>>>>>>> exception, or >>>>>>>>> one of its subclasses, is thrown, the runtime will check for >>>>>>>>> an >>>>>>>>> appropriate >>>>>>>>> exception mapper. An exception mapper is selected if it >>>>>>>>> handles >>>>>>>>> the >>>>>>>>> specific exception thrown. If there is not an exception mapper >>>>>>>>> for the >>>>>>>>> specific exception that was thrown, the exception mapper for >>>>>>>>> the >>>>>>>>> nearest >>>>>>>>> superclass of the exception is selected. >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> https://access.redhat.com/documentation/en-US/Red_Hat_ >>>>>>>>> JBoss_Fuse/6.1/html/Apache_CXF_Development_Guide/files/ >>>>>>>>> RESTExceptionMapper.html >>>>>>>>> >>>>>>>>> The reality is different for us however. In fact, if we create a >>>>>>>>> custom >>>>>>>>> WebApplicationExceptionMapper ClientErrorExceptions are not caught >>>>>>>>> by the >>>>>>>>> mapper. CXF seems to default to attempt to use whichever Mapper is >>>>>>>>> the >>>>>>>>> first one we registered (e.g. if we defined a FooExceptionMapper >>>>>>>>> and >>>>>>>>> registered it, CXF would attempt to use that and throw an error >>>>>>>>> rather than >>>>>>>>> try to use the WebApplicationExceptionMapper). >>>>>>>>> >>>>>>>>> Can someone tell me what the expected behavior is? Is my question >>>>>>>>> clear? >>>>>>>>> >>>>>>>>> We are using CXF version 2.7.11. >>>>>>>>> >>>>>>>>> Thanks! >>>>>>>>> >>>>>>>>> Mike >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>> >>>>>>>> >>>>>>> >>>>>> -- >>>>>> Sergey Beryozkin >>>>>> >>>>>> Talend Community Coders >>>>>> http://coders.talend.com/ >>>>>> >>>>>> Blog: http://sberyozkin.blogspot.com >>>>>> >>>>>> >>>>> >>>> >>> >> >
