Category Archives: Spring MVC

Spring MVC: Handling Joda Data Types as JSON

PROBLEM

Let’s assume we have the following bean that contains Joda’s LocalDate and LocalDateTime objects:-

public class MyBean {
    private LocalDate date;
    private LocalDateTime dateTime;

    public LocalDate getDate() {
        return date;
    }

    public void setDate(LocalDate date) {
        this.date = date;
    }

    public LocalDateTime getDateTime() {
        return dateTime;
    }

    public void setDateTime(LocalDateTime dateTime) {
        this.dateTime = dateTime;
    }
}

This simple Spring MVC rest controller creates this bean and returns the JSON data back to the client:-

@RequestMapping(value = "/joda", method = RequestMethod.GET)
public ResponseEntity joda() {
    MyBean myBean = new MyBean();
    myBean.setDate(LocalDate.now());
    myBean.setDateTime(LocalDateTime.now());
    
    return new ResponseEntity<MyBean>(myBean, HttpStatus.OK);
}

By default, the generated JSON looks like this:-

{
   "date":
   [
       2015,
       3,
       28
   ],
   "dateTime":
   [
       2015,
       3,
       28,
       18,
       12,
       58,
       992
   ]
}

How do we nicely format these values and still retain the correct data types (LocalDate and LocalDateTime) in MyBean instead of writing our custom formatter and store the values as String?

SOLUTION

First, add a dependency for jackson-datatype-joda.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.5.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.5.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.5.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-joda</artifactId>
    <version>2.5.1</version>
</dependency>

Next, instruct MappingJackson2HttpMessageConverter to accept a custom ObjectMapper.

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
      p:indentOutput="true" p:simpleDateFormat="yyyy-MM-dd'T'HH:mm:ss.SSSZ">
</bean>

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" p:targetObject-ref="objectMapper"
      p:targetMethod="registerModule">
    <property name="arguments">
        <list>
            <bean class="com.fasterxml.jackson.datatype.joda.JodaModule"/>
        </list>
    </property>
</bean>

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
        <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

The generated JSON output now looks like this:-

{
   "date": "2015-03-28",
   "dateTime": "2015-03-28T18:11:16.348"
}

Spring Security: Handling 403 Error Page

If you are already using Spring, then you might want to use Spring Security to secure your web resources.

To do that, we specify the URI to be secured with <security:intercept-url/> tag:-

<beans ...>
    <!-- Error pages don't need to be secured -->
    <security:http pattern="/error/**" security="none"/>

    <security:http auto-config="true">
        <security:form-login ... />
        <security:logout ... />
        <security:intercept-url pattern="/top-secrets/**" access="ROLE_TOPSECRET"/>
    </security:http>
		
		...
</beans>

When users without role ROLE_TOPSECRET access /top-secrets/kfc-secret, they will see this default error page:-

This proves that Spring Security is doing its job. However, the default error page looks rather F.U.G.L.Y. Further, the error page may reveal too much information about the application server. The above error page shows the application runs on Jetty. If I’m a motherhacker, I would research all the possible vulnerabilities on this particular application server in attempt to hack it.

A better solution is to provide a friendly error page when the user access is denied. This can be done by specifying <security:access-denied-handler/> tag:-

<beans ...>
    <!-- Error pages don't need to be secured -->
    <security:http pattern="/error/**" security="none"/>

    <security:http auto-config="true">
        <security:form-login ... />
        <security:logout ... />
        <security:access-denied-handler error-page="/error/access-denied"/>
        <security:intercept-url pattern="/top-secrets/**" access="ROLE_TOPSECRET"/>
    </security:http>
		
		...
</beans>

Then, we create a simple error controller that returns the error page:-

@Controller
@RequestMapping(value = "/error")
public class ErrorController {
    @RequestMapping(value = "/access-denied", method = RequestMethod.GET)
    public String accessDenied() {
        return "error-access-denied";
    }
}

Now, the user will see this custom error page:-

This solution is better than the previous one. However, SiteMesh doesn’t have the opportunity to decorate this error page before it gets rendered.

To fix this, we can create a simple redirect to allow the request to make a full-round trip to the server so that SiteMesh can decorate the error page:-

@Controller
@RequestMapping(value = "/error")
public class ErrorController {
    @RequestMapping(value = "/router", method = RequestMethod.GET)
    public String errorRouter(@RequestParam("q") String resource) {
        return "redirect:/error/" + resource;
    }

    @RequestMapping(value = "/access-denied", method = RequestMethod.GET)
    public String accessDenied() {
        return "error-access-denied";
    }
}

Then, we tweak the Spring Security to use the error router URI:-

<beans ...>
    <!-- Error pages don't need to be secured -->
    <security:http pattern="/error/**" security="none"/>

    <security:http auto-config="true">
        <security:form-login ... />
        <security:logout ... />
        <security:access-denied-handler error-page="/error/router?q=access-denied"/>
        <security:intercept-url pattern="/top-secrets/**" access="ROLE_TOPSECRET"/>
    </security:http>
		
		...
</beans>

Now, the user will see this nice beautiful error page:-

Spring MVC: Centralizing Common Configurations using @ControllerAdvice

Handling Exceptions using HandlerExceptionResolver

Once upon a time, Spring 2.0 introduced HandlerExceptionResolver to handle thrown exceptions. While this approach works, it is like throwing a big net to catch all types of fish. If we are only interested in catching piranhas and clownfish, this approach becomes a little tedious because we need to weed out the types of fish we don’t care.

@Controller
public class ErrorController implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest,
                                         HttpServletResponse httpServletResponse,
                                         Object o,
                                         Exception e) {

        ModelAndView modelAndView = new ModelAndView("error");

        if (e instanceof PiranhaException) {
            // stir fry it
        }
        else if (e instanceof ClownfishException) {
            // make me laugh first
        }
        else {
            // don't care...
        }

        return modelAndView;
    }

    @RequestMapping(value = "/error", method = RequestMethod.GET)
    public String error() {
        return "error";
    }
}

Handling Exceptions using @ExceptionHandler

Then, Spring 3.0 introduced a more elegant way to handle exceptions using @ExceptionHandler. Now, we can easily catch the types of fish we need and handle accordingly.

@Controller
@RequestMapping(value = "/fish")
public class FishController {

    @ExceptionHandler(PiranhaException.class)
    public String piranhaException(Exception e) {
        // stir fry it
        return "error";
    }

    @ExceptionHandler(ClownfishException.class)
    public String clownfishException(Exception e) {
        // make me laugh first
        return "error";
    }

    @RequestMapping(method = RequestMethod.GET)
    public String home() {
        return "home";
    }
}

While this is a more elegant solution than the previous approach, @ExceptionHandler has to be defined within the Controller class. If we want to apply the same exception handling across multiple controllers, we either have to define @ExceptionHandler in every Controller class or create something “clever” such as extending a parent controller that contains the common configurations.

Handling Exceptions using @ControllerAdvice

Then, Spring 3.2 introduced an even better approach to centralize common configurations using @ControllerAdvice. Instead of defining @ExceptionHandler all over places or extending a parent controller, we can create a separate class that contains these common configurations and annotate it with @ControllerAdvice.

@ControllerAdvice
public class FishControllerAdvice {

    @ExceptionHandler(PiranhaException.class)
    public String piranhaException(Exception e) {
        // stir fry it
        return "error";
    }

    @ExceptionHandler(ClownfishException.class)
    public String clownfishException(Exception e) {
        // make me laugh first
        return "error";
    }
}

… and now, the controller will look as simple as this:-

@Controller
@RequestMapping(value = "/fish")
public class FishController {

    @RequestMapping(method = RequestMethod.GET)
    public String home() {
        return "home";
    }
}

In fact, @ControllerAdvice can be used to define @ExceptionHandler, @ModelAttribute and @InitBinder that apply to all @RequestMapping methods.

Here’s an example:-

@ControllerAdvice
public class MyAdviceToAllFish {

    @ExceptionHandler(PiranhaException.class)
    public String piranhaException(Exception e) {
        // stir fry it
        return "error";
    }

    @ExceptionHandler(ClownfishException.class)
    public String clownfishException(Exception e) {
        // make me laugh first
        return "error";
    }

    // Catch all other exceptions
    @ExceptionHandler(Exception.class)
    public String exception(Exception e) {
        // when shit happens
        return "error";
    }

    // Return a list of tech support emails
    @ModelAttribute("techSupportEmails")
    public List<String> getTechSupportEmails() {
        return Arrays.asList("generaltso@chicken.com", "bat@man.com");
    }

    // Handles `String` and Joda Time's `LocalDate` object
    @InitBinder
    private void initBinder(WebDataBinder binder) {
        // Trim string and set empty string as `null` value
        binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));

        // Convert Joda Time's `LocalDate` object to text, vice versa
        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            private final DateTimeFormatter pattern = DateTimeFormat.forPattern("MM/dd/yyyy");

            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(StringUtils.isBlank(text) ? null : pattern.parseLocalDate(text));
            }

            @Override
            public String getAsText() throws IllegalArgumentException {
                LocalDate localDate = (LocalDate) getValue();
                return localDate != null ? pattern.print(localDate) : "";
            }
        });
    }
}

MockMvc : Circular view path [view]: would dispatch back to the current handler URL [/view] again

PROBLEM

Let’s assume we want to test this controller:-

@Controller
@RequestMapping(value = "/help")
public class HelpController {

    @RequestMapping(method = RequestMethod.GET)
    public String main() {
        return "help";
    }
}

Here’s the test file:-

public class HelpControllerTest {

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(new HelpController()).build();
    }

    @Test
    public void main() throws Exception {
        mockMvc.perform(get("/help"))
                .andExpect(status().isOk())
                .andExpect(view().name("help"));
    }
}

When executing this test, we get the following error:-

javax.servlet.ServletException: Circular view path [help]: would dispatch 
back to the current handler URL [/help] again. Check your ViewResolver 
setup! (Hint: This may be the result of an unspecified view, due to default 
view name generation.)
	at org.springframework.web.servlet.view.InternalResourceView.prepareForRendering(InternalResourceView.java:263)
	at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:186)
	at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:266)

SOLUTION

The reason this is happening is because the uri “/help” matches the returned view name “help” and we didn’t set a ViewResolver when contructing the standalone MockMvc. Since MockMvcBuilders.standaloneSetup(...) doesn’t load Spring configuration, the Spring MVC configuration under WEB-INF/spring-servlet.xml will not get loaded too.

A typical WEB-INF/spring-servlet.xml looks something like this:-

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

    <context:component-scan base-package="edu.mayo.requestportal.controller"/>

    <mvc:annotation-driven/>

    <mvc:resources location="/resources/" mapping="/resources/**"/>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/view/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="messages"/>
    </bean>
</beans>

To fix this, we need to defined a ViewResolver that mimics the configuration defined under WEB-INF/spring-servlet.xml in the test file:-

public class HelpControllerTest {

    private MockMvc mockMvc;

    @Before
    public void setup() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/jsp/view/");
        viewResolver.setSuffix(".jsp");

        mockMvc = MockMvcBuilders.standaloneSetup(new HelpController())
                                 .setViewResolvers(viewResolver)
                                 .build();
    }

    @Test
    public void main() throws Exception {
        mockMvc.perform(get("/help"))
                .andExpect(status().isOk())
                .andExpect(view().name("help"));
    }
}

Backbone: model.destroy() Always Call “error” Callback Function

PROBLEM

Let’s assume we invoke person.destroy() to delete a person:-

var person = Backbone.Model.extend( { ... } );

person.destroy( {
    contentType : 'application/json',

    success : function () {
        console.log('success');
    },

    error : function () {
        console.log('error');
    }
} );

… and let’s assume when person.destroy() is invoked, it will call /person/{personId} DELETE. Here’s how the Spring MVC controller API might look like:-

@Controller
public class PersonController {
    ...

    @RequestMapping(value = "/person/{personId}", method = RequestMethod.DELETE)
    public ResponseEntity delete(@PathVariable Long personId) {

        Person person = personService.getPerson(personId);

        if (person == null) {
            return new ResponseEntity<UnexpectedErrorBean>(new UnexpectedErrorBean("Invalid person ID"),
                                                           HttpStatus.BAD_REQUEST);
        }

        personService.remove(person);

        return new ResponseEntity(HttpStatus.OK);
    }
}

When we execute person.destroy(), the error callback function will always get called even though the HTTP status is a 200 OK.

Why?

SOLUTION

The reason is because Backbone expects a JSON payload from the web service call. In the example above, we return a 200 OK but without a JSON payload. Thus, Backbone will treat this as an error and invoke the error callback function instead of success callback function regardless of the HTTP status.

There are three solutions to this problem.

Solution 1: Add a JSON Payload

@Controller
public class PersonController {
    ...

    @RequestMapping(value = "/person/{personId}", method = RequestMethod.DELETE)
    public ResponseEntity delete(@PathVariable Long personId) {

        Person person = personService.getPerson(personId);

        if (person == null) {
            return new ResponseEntity<UnexpectedErrorBean>(new UnexpectedErrorBean("Invalid person ID"),
                                                           HttpStatus.BAD_REQUEST);
        }

        personService.remove(person);

        return new ResponseEntity<Long>(personId, HttpStatus.OK);
    }
}

In this approach, we add a JSON payload to satisfy Backbone. In the above example, we return the value of personId back to the client. It can be an empty object too.

I don’t like this approach because that piece of information is useless to the client side. Even if I return an empty object, the solution seems very brittle because the next team member inheriting my code will not understand why there’s a need to return an empty object back to the client.

Solution 2: Accept Payload as Text

var person = Backbone.Model.extend( { ... } );

person.destroy( {
    contentType : 'application/json',
		
    dataType : 'text',
		
    success : function () {
        console.log('success');
    },

    error : function () {
        console.log('error');
    }
} );

Another approach is to accept the data as text from the server. This solution works fine if we are not expecting any complex data structure back from the server.

Solution 3: Return a Different HTTP Status

@Controller
public class PersonController {
    ...

    @RequestMapping(value = "/person/{personId}", method = RequestMethod.DELETE)
    public ResponseEntity delete(@PathVariable Long personId) {

        Person person = personService.getPerson(personId);

        if (person == null) {
            return new ResponseEntity<UnexpectedErrorBean>(new UnexpectedErrorBean("Invalid person ID"),
                                                           HttpStatus.BAD_REQUEST);
        }

        personService.remove(person);

        return new ResponseEntity(HttpStatus.NO_CONTENT);
    }
}

The approach that I truly recommend is to return a 204 No Content instead of 200 OK. This way, the client side will not expect any payload from the server, and thus, Backbone will invoke the success callback function.