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