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 >>>> >>> >> >
