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

Reply via email to