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