Customizations with CXF Interceptors

Version 2

    Background

     

    As part of the Gamefication Extension development, we needed a way to replace the status level data in the Browse People page:

     

    Screen+Shot+2012-04-11+at+10.00.16+AM.png

     

    When you first visit the browse people page, the page is rendered by a normal soy action so we had no problem modifying the response using a regular struts interceptor (see How To: Add Struts Interceptors at Runtime).

     

    However, if you want to toggle the display between "card" and "list" view:

     

    Screen Shot 2012-04-11 at 10.04.23 AM.png

     

    The data is retrieved from a web service and then re-rendered on the page.

     

    Problem


    We wanted a way to modify the response from the web service without resorting to an overlay or having to modify the internals of any of the classes responsible for generating the response.

     

    Solution

     

    CXF Interceptors to the rescue.  By adding a CXF interceptor to the web service in question, we could modify the response before it was sent to the user.  Here is how we did it

     

    Create an Interceptor

     

    public class UserServiceInterceptor extends AbstractPhaseInterceptor<Message> {
    
        public UserServiceInterceptor() {
            super(Phase.SETUP);
        }
    
        @Override
        public void handleMessage(Message message) throws Fault {
            if (isInvokingGetUsers(message) && viewingAllPeople(message)) {
                decorateResponse(message);
            }
        }
    }
    
    
    
    

     

    Add the Interceptor to the web service definition.

     

    For this, we have to resort to using a spring BeanFactoryPostProcessor (See Modifying Prototype Spring Beans) to inject the interceptor in to the outInterceptors property of the web service definition we want (in our case, the internalRestServer defined in spring-wsInternalContext.xml):

     

    This is necessary because Spring doesn't give us a chance to modify the <jaxrs:server/> definition for internalRestServer in any other meaningful way.

     

    public class UserServiceInterceptorConfigurator implements BeanFactoryPostProcessor {
    
        private UserServiceInterceptor interceptor;
    
        public void setInterceptor(UserServiceInterceptor interceptor) {
            this.interceptor = interceptor;
        }
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            BeanDefinition definition = beanFactory.getBeanDefinition("internalRestServer");
            MutablePropertyValues properties = definition.getPropertyValues();
            PropertyValue outInterceptorsProperty = properties.getPropertyValue("outInterceptors");
        
            if (outInterceptorsProperty == null) {
                properties.addPropertyValue("outInterceptors", Arrays.asList(interceptor));
            } else {
                @SuppressWarnings("unchecked")
                List<Object> propertyValue = (List<Object>) outInterceptorsProperty.getValue();
                propertyValue.add(interceptor);
            }
        }
    
    }
    
    
    
    

     

    This is then defined in your spring config:

     

    <bean class="xxx.UserServiceInterceptorConfigurator">
        <property name="interceptor">
        ....
        </property>
    </bean>
    
    
    
    

     

     

    Result

     

    Screen+Shot+2012-04-11+at+10.21.12+AM.png

     

    (someone reset the points on my the PS instance, grrr )

     

    Notes

     

    You'll notice that the Interceptor has two methods, isInvokingGetUsers and viewingAllPeople.  These are necessary to ensure that the interceptor runs on the right service and method.  You'll notice that we added the interceptor to the internalRestServer service which is actually a number of different services all under the /__services/v2/rest base path.  Below is the code used to ensure that we're running in the right spot:

     

    protected boolean isInvokingGetUsers(Message message) {
        return "UserServiceImpl#getUsers".equals(message.getExchange().get("org.apache.cxf.resource.operation.name"));
    }
    
    protected boolean viewingAllPeople(Message message) {
        Message inMessage = message.getExchange().getInMessage();
        // The list of arguments that the method was invoked with.
        List<?> list = inMessage.getContent(List.class);
        if (list != null && list.size() > 3) {
            return "people".equals(list.get(3));
        }
        return false;
    }
    
    
    
    

     

    The decorateResponse method pulls the response object from the CXF message and modifies it accordingly:

     

    protected void decorateResponse(Message message) {
        ItemsViewBean<UserItemBean> view = getResponseObjectFrom(message);
        // Do something with view
    }
    
    @SuppressWarnings("unchecked")
    protected ItemsViewBean<UserItemBean> getResponseObjectFrom(Message message) {
        List<?> list = message.getContent(List.class);
        if (list != null && list.size() == 1) {
            Object o = list.get(0);
            if (o instanceof ItemsViewBean) {
                return (ItemsViewBean<UserItemBean>) o;
            }
        }
        return null;
    }
    
    
    
    

     

    Thanks to Kevin.Conaway for the great write-up!