Tag Archives: Java

Custom JSON views with Spring MVC and Jackson

The Problem

Spring MVC provides amazing out-of-the-box support for returning your domain model in JSON, using Jackson under the covers.

However, often you may find that you want to return different views of the data, depending on the method that is invoked.

For example, consider the following two methods:

List<Book> findBooks() {}
Book getBook(Long bookId); {}

It’s reasonable to say that from the findBooks method, you only want to return a summarized view of the books, and then allow users to fetch the full object graph for a specific book.

Unfortunately, SpringMVC doesn’t support this natively, as the type serialization is annotated directly on the entity class (Book).

Jackson has support for the concept using it’s ResponseView annotation, but there’s no way to hook that into Spring methods.

You would be required instead to generate different VO’s for serializing, which is ultimately just annoying boilerplate code.

Introducing @ResponseView

@ResponseView is a new tag that solves this issue, allowing you to register custom views on a per-method basis.

First up, let’s annotate our domain model with Jackson’s @JsonView annotation:

@Data
class Book extends BaseEntity
{
    @JsonView(SummaryView.class)
    private String title;
    @JsonView(SummaryView.class)
    private String author;
    private String review;

    public static interface SummaryView extends BaseView {}
}

@Data
public class BaseEntity
{
    @JsonView(BaseView.class)
    private Long id;    
}

public interface BaseView {}

Note that here we’ve defined public static interface as a marker, and annotated the properties we want returned from our summary method.  (Note, I’m using Lombok’s @Data annotation to keep this blogpost short.  It’s not a requirement of this approach)

Now, let’s define the annotation, and mark up our methods:

@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseView {
    public Class<? extends BaseView> value();
}

@Controller
public class BookService
{
    @RequestMapping("/books")
    @ResponseView(SummaryView.class)
    public @ResponseBody List<Book> getBookSummaries() {}

    @RequestMapping("/books/{bookId}")
    public @ResponseBody Book getBook(@PathVariable("bookId") Long BookId) {}
}

This indicates that we want the response from getBookSummaries serialized using our SummaryView annotation.

Here’s the wiring to make it work:

/**
 * Decorator that detects a declared {@link ResponseView}, and 
 * injects support if required
 * @author martypitt
 *
 */
public class ViewInjectingReturnValueHandler implements
        HandlerMethodReturnValueHandler {

    private final HandlerMethodReturnValueHandler delegate;

    public ViewInjectingReturnValueHandler(HandlerMethodReturnValueHandler delegate)
    {
        this.delegate = delegate;
    }
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return delegate.supportsReturnType(returnType);
    }

    @Override
    public void handleReturnValue(Object returnValue,
            MethodParameter returnType, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest) throws Exception {

        Class<? extends BaseView> viewClass = getDeclaredViewClass(returnType);
        if (viewClass != null)
        {
            returnValue = wrapResult(returnValue,viewClass);    
        }

        delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }
    /**
     * Returns the view class declared on the method, if it exists.
     * Otherwise, returns null.
     * @param returnType
     * @return
     */
    private Class<? extends BaseView> getDeclaredViewClass(MethodParameter returnType) {
        ResponseView annotation = returnType.getMethodAnnotation(ResponseView.class);
        if (annotation != null)
        {
            return annotation.value();
        } else {
            return null;
        }
    }
    private Object wrapResult(Object result, Class<? extends BaseView> viewClass) {
        PojoView response = new PojoView(result, viewClass);
        return response;
    }
}
@Data
public class PojoView {
	private final Object pojo;
	private final Class<? extends BaseView> view;
	@Override
	public boolean hasView() {
		return true;
	}
}
/**
 * Adds support for Jackson's JsonView on methods
 * annotated with a {@link ResponseView} annotation
 * @author martypitt
 *
 */
public class ViewAwareJsonMessageConverter extends
        MappingJacksonHttpMessageConverter {

    public ViewAwareJsonMessageConverter()
    {
        super();
        setObjectMapper(JacksonConfiguration.newObjectMapper());
    }

    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        if (object instanceof DataView && ((DataView) object).hasView())
        {
            writeView((DataView) object, outputMessage);
        } else {
            super.writeInternal(object, outputMessage);
        }
    }
    protected void writeView(DataView view, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
        ObjectMapper mapper = getMapperForView(view.getView());
        JsonGenerator jsonGenerator =
                mapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
        try {
            mapper.writeValue(jsonGenerator, view);
        }
        catch (IOException ex) {
            throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
        }
    }

    private ObjectMapper getMapperForView(Class<?> view) {
        ObjectMapper mapper = JacksonConfiguration.newObjectMapper();
        mapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
        mapper.setSerializationConfig(mapper.getSerializationConfig().withView(view));
        return mapper;
    }

}

/**
 * Modified Spring 3.1's internal Return value handlers, and wires up a decorator
 * to add support for @JsonView
 * 
 * @author martypitt
 *
 */
@Slf4j
public class JsonViewSupportFactoryBean implements InitializingBean {

    @Autowired
    private RequestMappingHandlerAdapter adapter;
    @Override
    public void afterPropertiesSet() throws Exception {
        HandlerMethodReturnValueHandlerComposite returnValueHandlers = adapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> handlers = Lists.newArrayList(returnValueHandlers.getHandlers());
        decorateHandlers(handlers);
        adapter.setReturnValueHandlers(handlers);
    }
    private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {
        for (HandlerMethodReturnValueHandler handler : handlers) {
            if (handler instanceof RequestResponseBodyMethodProcessor)
            {
                ViewInjectingReturnValueHandler decorator = new ViewInjectingReturnValueHandler(handler);
                int index = handlers.indexOf(handler);
                handlers.set(index, decorator);
                log.info("JsonView decorator support wired up");
                break;
            }
        }        
    }

}

Those two classes are responsible for the heavy lifting.   ViewInjectingReturnValueHandler identifies methods with our @ResponseView annotation, and wrap them so they can be formatted correctly.

JsonViewSupportFactoryBean modifies Spring’s internal wiring, wrapping the existing RequestResponseBodyMethodProcessor with our decorator.

From here, it’s just a matter of updating the Spring configuration:

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="com.mangofactory.concorde.api.ViewAwareJsonMessageConverter" />
        </mvc:message-converters>
    </mvc:annotation-driven>

That’s it.

Now, calls to any methods annotated with @ResponseView will have the appropriate json view rendered.

Update: Example available on Github

There’s a working example of this implementation now available on github, here.

Final thought:  Interfaces vs Classes

I’d recommend using Interfaces for declaring the view markers, as opposed to Classes as shown elsewhere.  Using Interfaces will allow you to build fairly fine-grained views, using multiple inheritance   As you never have to worry about the implementation details, there’s none of the normal multiple-inheritance nasties lurking.

Advertisements
Tagged ,

Spring Data Cross-Store support without AspectJ

Spring Data provides great Cross-store support between JPA and MongoDB.  However, one of the requirements is that you use AspectJ to compile your project.

I’ve been using Project Lombok heavily recently, which unfortunately is incompatible with AspectJ.

So, I’ve added a small project up on GitHub which provides Cross-Store support without the requirement of AspectJ.

It should be noted that the Spring Data version is better, so if you can use it – please do so.

There’s a getting started page over on Github that describes how to get going.

Please, take it for a spin, and let me know how you get on!

Tagged , ,

dpHibernate and Spring transactions

I’ve recently been getting my feet wet in Java, and starting integrating with dpHibernate to facilitate lazy loading across to Flex.

Here’s what my stack looks like:

  • Spring 2.5.6
  • Hibernate 3.3 (including annotations, and transaction management)

Trying to get dpHibernate wired in, I was getting lots of errors along the lines of the following:

No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here

 

The problem here is that using transaction management, things get a lot tighter on how and when you can get access to a hibernate session.  dpHibernate tries to get it’s session by essentially calling sessionManager.getCurrentSession();

However, as far as Spring’s transaction manager (which is wired into the SessionManager) is concerned, the thread which is executing has no session, and has no business trying to create one — specifically, outside of a transaction.

After spending several hours trying to work out how to sidestep Springs transaction management, I realized the trick was simply to wire dpHibernate closer to spring.  This is tricky, because dpHibernate is using an extension of the flex’s JavaAdapter, and exists completely outside of the Spring infrastructure.

HibernateSerializer makes a call to a dpHibernate object called SessionManager, which returns the current session outside of a valid Spring transaction.

So, I ditched the SessionManager completely, and replaced it with a good old fashionsed SessionFactory object, as follows:

    @Resource
    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

The @Resource tag there tells Spring to give me a wired up sessionFactory instance.

I also decorated the HibernateSerializer with a @Transactional tag, so that Spring knows the context in which this will operate:

@Transactional(readOnly=true)
public class HibernateSerializer implements ISerializer
{

Now, if I add the HibernateSerializer as a bean, Spring knows to give it access to a sessionManager, and to do so in the context of an active transaction.

Add the bean declaration as follows:

<bean id="hibernateSerializerBean" class="net.digitalprimates.persistence.translators.hibernate.HibernateSerializer" />

Nearly there. Finally, I just need to tell dpHibernate to go and fetch this wired up Serializer from Spring, rather than instantiate it’s own. Inside SerializationFactory, I modified the getSerializer() call to read as follows:

    public static ISerializer getSerializer(String type)
    {
        if( HIBERNATESERIALIZER.equals(type) )
        {
            ServletContext ctx = FlexContext.getServletContext();
            WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(ctx);
            ISerializer serializer = (ISerializer) springContext.getBean("hibernateSerializerBean");
            if (serializer == null)
            {
                throw new RuntimeException("bean named hibernateSerializerBean not found");
            }
            return serializer;
        }

        throw new RuntimeException("unsupport serialization type: " +type);
    }

Note that I’ve hard coded the name of the bean here for simplicity sakes. Chances are you’ll want to paramaterize that.

That’s it! Now, calls that pass through dpHibernate happen with a Spring managed transactional session. Great!

 

Edit: Espen came up with a simpler alternative over on his blog — check it out : http://espenskogen.wordpress.com/2009/06/04/more-dphibernate/

Tagged , , ,