Monthly Archives: January 2014

Guava: Elegant Caching Mechanisms

There are many different ways to implement a caching mechanism. Google’s Guava provides very simple and elegant solutions to do so.

OPTION 1: Caching using Supplier

If you want to cache just one thing, Supplier should satisfy that requirement.

private Supplier<Collection<Person>> allEmployeesCache = Suppliers.memoizeWithExpiration(
        new Supplier<Collection<Person>>() {
            public Collection<Person> get() {
                return makeExpensiveCallToGetAllEmployees();
            }
        }, 1, TimeUnit.DAYS);

public Collection<Person> getAllEmployees() {
    return allEmployeesCache.get();
}

In the above example, we cache all employees for a day.

OPTION 2: Caching using LoadingCache

If you want to cache multiple things (ex: think Hashmap-like), LoadingCache should satisfy that requirement.

private LoadingCache<String, Collection<Person>> employeeRoleLoadingCache = CacheBuilder.newBuilder()
        .expireAfterWrite(1, TimeUnit.DAYS)
        .maximumSize(1000)
        .build(new CacheLoader<String, Collection<Person>>() {
            @Override
            public Collection<Person> load(String roleName) throws Exception {
                return makeExpensiveCallToGetAllEmployeesByRoleName(roleName);
            }
        });

public Collection<Person> getAllEmployeesByRole(String roleName) {
    return employeeRoleLoadingCache.getUnchecked(roleName);
}

In the above example, we cache all employees for each given role for a day. Further, we configure the cache to store up to 1000 elements before evicting the older cached element.

Advertisements

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"));
    }
}

MockMvc + Mockito = Epic Tests

Spring Framework 3.2 introduces a very elegant way to test Spring MVC controller using MockMvc.

Based on the documentation, there are two ways to configure MockMvc:-

  • MockMvcBuilders.webAppContextSetup(webApplicationContext).build()
  • MockMvcBuilders.standaloneSetup(controller).build()

The first approach will automatically load the Spring configuration and inject WebApplicationContext into the test. The second approach does not load the Spring configuration.

While both options work, my preference is to use the second approach that doesn’t load the Spring configuration. Rather, I use Mockito to mock out all the dependencies within the controller.

EXAMPLE

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

@Controller
@RequestMapping(value = "/comment/{uuid}")
public class CommentController {

    @Autowired
    private RequestService requestService;

    @Autowired
    private CommentValidator validator;

    @InitBinder("commentForm")
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(validator);
    }

    @RequestMapping(method = RequestMethod.POST)
    public String saveComment(@PathVariable String uuid,
                              @Valid @ModelAttribute CommentForm commentForm,
                              BindingResult result,
                              Model model) {

        RequestComment requestComment = requestService.getRequestCommentByUUID(uuid);

        if (requestComment == null) {
            return "redirect:/dashboard";
        }

        if (result.hasErrors()) {
            return "comment";
        }

        return "ok";
    }
}

SETTING UP TEST FILE

To test /comment/{uuid} POST, we need three tests:-

  • requestComment is null, which returns redirect:/dashboard view.
  • Form validation contains error, which returns comment view.
  • Everything works fine, which returns ok view.

The test file looks something like this:-

public class CommentControllerTest {

    @Mock
    private RequestService requestService;

    @Mock
    private CommentValidator validator;

    @InjectMocks
    private CommentController commentController;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);

        mockMvc = MockMvcBuilders.standaloneSetup(commentController).build();

        when(validator.supports(any(Class.class))).thenReturn(true);
    }

    @Test
    public void testSaveComment_RequestCommentNotFound() throws Exception {
		...
    }

    @Test
    public void testSaveComment_FormError() throws Exception {
		...
    }

    @Test
    public void testSaveComment_NoError() throws Exception {
		...
    }
}

Because the controller has two dependencies ( RequestService and CommentValidator ) injected into it through Spring autowiring, we are going to create these two mocks and inject them into the controller by annotating them with Mockito’s @Mock and @InjectMocks accordingly.

In setup(...) method, the first line initializes objects annotated with @Mock. The second line initializes MockMvc without loading Spring configuration because we want the flexibility to mock the dependencies out using Mockito. The third line instructs the validator to return true when validator.support(...) is invoked. If the third line is left out, you will get this exception when binder.setValidator(validator) in the controller.initBinder(...) is invoked:-

org.springframework.web.util.NestedServletException: Request processing failed; 
nested exception is java.lang.IllegalStateException: Invalid target for Validator 
[validator]: com.choonchernlim.epicapp.form.CommentForm@1a80b973

Finally, we have three stubs to test the conditions defined earlier.

TEST CASE 1: requestComment is null

public class CommentControllerTest {

	...

    @Test
    public void testSaveComment_RequestCommentNotFound() throws Exception {
        when(requestService.getRequestCommentByUUID("123")).thenReturn(null);

        mockMvc.perform(post("/comment/{uuid}", "123"))
                .andExpect(status().isMovedTemporarily())
                .andExpect(view().name("redirect:/dashboard"));
    }

}

The first test is rather easy. All we need to do is to instruct requestService.getRequestCommentByUUID(...) to return null when it is invoked.

TEST CASE 2: Form validation contains error

public class CommentControllerTest {

	...

    @Test
    public void testSaveComment_FormError() throws Exception {
        when(requestService.getRequestCommentByUUID("123")).thenReturn(new RequestComment());

        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                Errors errors = (Errors) invocationOnMock.getArguments()[1];
                errors.reject("forcing some error");
                return null;
            }
        }).when(validator).validate(anyObject(), any(Errors.class));

        mockMvc.perform(post("/comment/{uuid}", "123"))
                .andExpect(status().isOk())
                .andExpect(view().name("comment"));
    }
}

The second test is a little complicated. We need to instruct requestService.getRequestCommentByUUID(...) to return a RequestComment object. Then, we use Mockito’s doAnswer(...) to set a dummy error value to Errors object, which is the second argument of validator.validate(...). Setting this value will cause result.hasErrors() to evaluate to true.

TEST CASE 3: Everything works fine

public class CommentControllerTest {

	...

    @Test
    public void testSaveComment_NoError() throws Exception {
        when(requestService.getRequestCommentByUUID("123")).thenReturn(new RequestComment());

        mockMvc.perform(post("/comment/{uuid}", "123"))
                .andExpect(status().isOk())
                .andExpect(view().name("ok"));
    }
}

The third test is rather easy too. We need to instruct requestService.getRequestCommentByUUID(...) to return a RequestComment object… and that’s it.

CONCLUSION

See… it’s not really that hard. The combination of Mockito and Spring Test Framework 3.2 provides us a great flexibility to test our Spring MVC controllers. Granted, we should already have our unit tests for the two dependencies ( RequestService and CommentValidator ) already. So, it is safe to alter the behavior of these dependencies using Mockito to test every possible path in the controller.

Guava: Elegant Collection Ordering

PROBLEM

Java provides a built-in API to sort a list, but it is flat out ugly and prone to error especially when dealing with Comparator. Here’s an example:-

List<Request> allRequests = ...;

Collections.sort(allRequests, new Comparator<Request>() {
    @Override
    public int compare(Request lhs, Request rhs) {
        if (lhs.getSubmissionDatetime() == null) {
            return -1;
        }
        else if (rhs.getSubmissionDatetime() == null) {
            return 1;
        }

        return lhs.getSubmissionDatetime().compareTo(rhs.getSubmissionDatetime());
    }
});

Number of puppies killed = 3 … from the if-else statement.

SOLUTION

A better way to do this is to use Ordering from Guava:-

List<Request> allRequests = ...;

List<Request> sortedRequests = Ordering.natural()
        .nullsFirst()
        .onResultOf(new Function<Request, LocalDateTime>() {
            @Override
            public LocalDateTime apply(Request request) {
                return request.getSubmissionDatetime();
            }
        })
        .immutableSortedCopy(allRequests);

In this example, we want all the null elements to appear on top of the list. We can also use nullsLast() if we want them to be at the bottom of the list.

Number of puppies killed = 0.

Developing against H2 Database: Lesson Learned

While my production database is MS SQL Server, I have been using H2 database for rapid local development. I wrote my DDL scripts as “Transact-SQL” as possible so that I don’t need to maintain multiple DDL scripts for each database. So far, it has been working out great. My web development runs against H2’s file-based database because I want my data to persist until I manually wipe them off.

That said, I also realized that H2 does behavior a little differently from MS SQL Server. For example, I have the following Hibernate’s HQL query:-

public Collection<Request> getRequestsWithMatchingStatus(Collection<String> statusNames) {
    return sessionFactory.getCurrentSession()
            .createQuery("from Request r where r.status.name in (:statusNames)")
            .setParameterList("statusNames", statusNames)
            .list();
}

If statusNames is an empty collection, H2 will work just fine. However, MS SQL Server will throw the following exception:-

Caused by: java.sql.SQLException: Incorrect syntax near ')'.
	at net.sourceforge.jtds.jdbc.SQLDiagnostic.addDiagnostic(SQLDiagnostic.java:368)
	at net.sourceforge.jtds.jdbc.TdsCore.tdsErrorToken(TdsCore.java:2816)
	at net.sourceforge.jtds.jdbc.TdsCore.nextToken(TdsCore.java:2254)

My test cases didn’t catch this error because they run against H2’s in-memory database.

Of course, the fix is as easy as performing a check on the collection size first:-

public Collection<Request> getRequestsWithMatchingStatus(Collection<String> statusNames) {
    if (statusNames.isEmpty()) {
        return new ArrayList<Request>();
    }

    return sessionFactory.getCurrentSession()
            .createQuery("from Request r where r.status.name in (:statusNames)")
            .setParameterList("statusNames", statusNames)
            .list();
}

That said, H2 database allows me to do local development really fast, but I also need to make sure I test it against MS SQL Server from time to time before deploying into production.

HTML: Creating a Default Placeholder for Image that Fails to Load

PROBLEM

Let’s assume our web application relies on an external system to provide the image link, for example: employee photo link of a company.

<img src="http://external/server/employeeId.jpg"/>

If the image link is invalid, we will get the ugly looking “x” on certain browsers.

SOLUTION

To fix this, we can load an image placeholder from our own web application if the external image link fails to load for some reason:-

<img src="http://external/server/employeeId.jpg" 
     onerror="this.src='http://server/app/resources/img/avatar.jpg'"/>