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