Wednesday, October 27, 2010

Tests with PowerMock and MyFaces Test frameworks

In one of projects we use Mockito as test framework. It's a great framework having some limitations. We also use different mock objects for JSF environment which come from various sources. A not comfortable mishmash. I have decided to introduce PowerMock and MyFaces Test frameworks. This changing was very effective. First I want to compare two test approaches:
  • Old approach: Utilization of Mockito and JSF / Servlet mocks from "dead" Apache Shale, Spring, ebtam FacesContext.
  • Disadvantages: Limitations, no mocking of static, final, private classes / methods. Mix of different mocks causes an unconsistent environment. Hacks for the production code.
  • New approach: Utilization of PowerMock and MyFaces Test frameworks.
  • Advantages: Mocking of static, final, private classes / methods and more is possible. Unified mock objects and consistent weaving for all fundamental JSF / Servlet pendants.
PowerMock extends Mockito by using a custom classloader and bytecode manipulation. It enables mocking of static methods, constructors, final classes and methods, private methods and more. MyFaces Test Framework provides mock object libraries and base classes for creating own test cases. Mocks for the following APIs are provided:
  • JavaServer Faces
  • Servlet
The mock netting is as real. Created FacesContext instance will have been registered in the appropriate thread local variable, to simulate what a servlet container would do. The following section shows how to write power JSF 2 tests. For easing use of the new approach there is a class JsfServletMock which provides an access to JSF 2 and Servlet mocks.
package com.xyz.webapp.basemock;

import org.apache.myfaces.test.base.junit4.AbstractJsfTestCase;
import org.apache.myfaces.test.mock.*;
import org.apache.myfaces.test.mock.lifecycle.MockLifecycle;

public class JsfServletMock extends AbstractJsfTestCase
{
    public MockApplication20 mockApplication;
    public MockExternalContext20 mockExternalContext;
    public MockFacesContext20 mockFacesContext;
    public MockLifecycle mockLifecycle;
    public MockServletContext mockServletContext;
    public MockServletConfig mockServletConfig;
    public MockHttpSession mockHttpSession;
    public MockHttpServletRequest mockHttpServletRequest;
    public MockHttpServletResponse mockHttpServletResponse;

    public JsfServletMock() {
        try {
            super.setUp();
        } catch (Exception e) {
            throw new IllegalStateException("JSF / Servlet mocks could not be initialized");
        }

        // JSF API
        mockApplication = (MockApplication20) application;
        mockExternalContext = (MockExternalContext20) externalContext;
        mockFacesContext = (MockFacesContext20) facesContext;
        mockLifecycle = lifecycle;

        // Servlet API
        mockServletContext = servletContext;
        mockServletConfig = config;
        mockHttpSession = session;
        mockHttpServletRequest = request;
        mockHttpServletResponse = response;
    }
}
An object of this class can be used in tests per delegation pattern. Define the PowerMockRunner with @RunWith at the beginning. Define classes which static, final, private methods will be mocked / tested with @PrepareForTest. Take an example.
import com.xyz.webapp.basemock.JsfServletMock;
import com.xyz.webapp.util.FacesUtils;
import com.xyz.webapp.util.Observer;
import com.xyz.webapp.util.ObserverUtil;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.*;

@RunWith(PowerMockRunner.class)
@PrepareForTest({FacesUtils.class, ObserverUtil.class})
public class DataExportWorkflowPageTest
{
    private JsfServletMock jsfServletMock;

    @Mock
    private StatusProfileFinder statusProfileFinder;

    @Mock
    private Collection<Observer> observers;

    private DataExportWorkflowPage dewp = new DataExportWorkflowPage();

    @Before
    public void before() {
        jsfServletMock = new JsfServletMock();

        dewp.setObservers(observers);
        dewp.setStatusProfileFinder(statusProfileFinder);
    }

    @Test
    public void initialize() {
        mockStatic(FacesUtils.class);

        DataExportHistoryForm dehf = mock(DataExportHistoryForm.class);
        DataExportContentForm decf = mock(DataExportContentForm.class);

        WorkflowData workflowData = mock(WorkflowData.class);

        when(FacesUtils.accessManagedBean("dataExportHistoryForm")).thenReturn(dehf);
        when(FacesUtils.accessManagedBean("dataExportContentForm")).thenReturn(decf);

        List<StatusProfile> spList = new ArrayList<StatusProfile>();
        StatusProfile sp1 = new StatusProfile(GenericObjectType.DOCUMENT);
        sp1.setBusinessKey("document");
        spList.add(sp1);
        StatusProfile sp2 = new StatusProfile(GenericObjectType.ACTIVITY);
        sp2.setBusinessKey("activity");
        spList.add(sp2);
        StatusProfile sp3 = new StatusProfile(GenericObjectType.EXPENSE_DOCUMENT_ITEM);
        sp3.setBusinessKey("expense");
        spList.add(sp3);

        jsfServletMock.mockHttpSession.setAttribute(Constants.SESSION_CLIENT, new Client());

        when(statusProfileFinder.getAllLimitBy(any(Client.class), eq(Constants.MAX_RESULTS_DEFAULT))).thenReturn(spList);
        when(FacesUtils.accessManagedBean("workflowData")).thenReturn(workflowData);

        dewp.initialize();

        verify(observers).add(dehf);
        verify(observers).add(decf);
        verifyNoMoreInteractions(observers);

        assertEquals(GenericObjectType.DOCUMENT.getId(), dewp.getStatusProfile());
        assertEquals(workflowData, dewp.getWorkflowData());
    }

    ...
}
After JsfServletMock instantiation in a method annotated with @Before we have a full access to mock objects. E.g. MockHttpSession can be accessed by jsfServletMock.mockHttpSession. In the example I want to mock the static method FacesUtils.accessManagedBean(String) in the static class. We have to write first
mockStatic(FacesUtils.class);
to achieve that. And then quite normally
when(FacesUtils.accessManagedBean(...)).thenReturn(...);
In another small example I use the call
verifyStatic();
to verify if the subsequent static call got really called. In the example below we verify if
ObserverUtil.notifyAll(depd);
was called within
depd.setProcedureStep2(null);
@RunWith(PowerMockRunner.class)
@PrepareForTest(ObserverUtil.class)
public class DataExportProcedureDataTest
{
    private DataExportProcedureData depd;

    @Mock
    private Collection<Observer> observers;

    @Before
    public void before() {
        depd = new DataExportProcedureData();
        depd.setObservers(observers);
    }

    @Test
    public void setProcedureStep2() {
        mockStatic(ObserverUtil.class);

        depd.setProcedureStep2(null);

        verifyStatic();
        ObserverUtil.notifyAll(depd);
    }

    ...
}
You see we can do both - mock static methods and verify behaviors.

Bring power to JSF tests! No need for workarounds any more! Keep your production code clean from the test driven stuff!

Tuesday, October 19, 2010

Global handling of all unchecked / unexpected exceptions in JSF 2

JSF 2 specification introduced a new ExceptionHandler API. All exceptions can be handled globally by an ExceptionHandler instance. Advantage of this centralization is that it allows developers to devise more error handling strategies. I was inspired by the great article of Ed Burns "Dealing Gracefully with ViewExpiredException in JSF2" and developed my own central Exception Handler.
Firstly, we need a factory class that creates and returns a new Exception Handler instance.
public class DefaultExceptionHandlerFactory extends ExceptionHandlerFactory {
    private ExceptionHandlerFactory parent;

    public DefaultExceptionHandlerFactory(ExceptionHandlerFactory parent) {
        this.parent = parent;
    }

    @Override
    public ExceptionHandler getExceptionHandler() {
        ExceptionHandler eh = parent.getExceptionHandler();
        eh = new DefaultExceptionHandler(eh);

        return eh;
    }
}
This factory class has to be registered in faces-config.xml
<factory>
    <exception-handler-factory>
        ip.client.jsftoolkit.commons.DefaultExceptionHandlerFactory
    </exception-handler-factory>
</factory>
Secondly, we need a class DefaultExceptionHandler. This is the default implementation of the exception handler to catch unchecked / unexpected exceptions in order to proper display.
public class DefaultExceptionHandler extends ExceptionHandlerWrapper {
    private static final Log LOG = LogFactory.getLog(DefaultExceptionHandler.class);

    /** key for session scoped message detail */
    public static final String MESSAGE_DETAIL_KEY = "ip.client.jsftoolkit.messageDetail";

    private ExceptionHandler wrapped;

    public DefaultExceptionHandler(ExceptionHandler wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public ExceptionHandler getWrapped() {  
        return this.wrapped;
    }

    @Override
    public void handle() throws FacesException {
        if (fc.isProjectStage(ProjectStage.Development)) {
            // Code for development mode. E.g. let the parent handle exceptions
            getWrapped().handle();
        } else {
            for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) {
                ExceptionQueuedEvent event = i.next();
                ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();

                String redirectPage = null;
                FacesContext fc = FacesContext.getCurrentInstance();
                Throwable t = context.getException();

                try {
                    if (t instanceof AbortProcessingException) {
                        // about AbortProcessingException see JSF 2 spec.
                        LOG.error("An unexpected exception has occurred by event listener(s)", t);
                        redirectPage = "/views/error.jsf?statusCode=jsftoolkit.exception.UncheckedException";
                        fc.getExternalContext().getSessionMap()
                            .put(DefaultExceptionHandler.MESSAGE_DETAIL_KEY, t.getLocalizedMessage());
                    } else if (t instanceof ViewExpiredException) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("View '" + ((ViewExpiredException) t).getViewId() + "' is expired", t);
                        }

                        ApplicationConfiguration appConfiguration =
                            (ApplicationConfiguration) FacesAccessor.accessManagedBean(
                                ApplicationConfiguration.BEAN_NAME_APPLICATION_CONFIGURATION);
                        HttpSession session = (HttpSession) fc.getExternalContext().getSession(false);
                        if (session != null) {
                            // should not happen
                            session.invalidate();
                        }

                        if (appConfiguration.getBoolean(ConfigKeys.KEY_LOGOFF_2_LOGOUT_PAGE, false)) {
                            // redirect to the specified logout page
                            redirectPage = "/views/logout.jsf";
                        } else {
                            // redirect to the login page
                            redirectPage = "";
                        }
                    } else if (t instanceof ServiceNotAvailableException) {
                        LOG.error("'" + ((ServiceNotAvailableException) t).getServiceName() + "' is not available", t);
                            redirectPage = "/views/error.jsf?statusCode=jsftoolkit.exception.ServiceNotAvailableException";
                    } else {
                        // custom handling of unexpected exceptions can be done in the method handleUnexpected
                        String messageKey = handleUnexpected(fc, t);
                        redirectPage = "/views/error.jsf?statusCode=" + messageKey;
                        fc.getExternalContext().getSessionMap()
                            .put(DefaultExceptionHandler.MESSAGE_DETAIL_KEY, t.getLocalizedMessage());
                    }
                } finally {
                    i.remove();
                }

                SecurityPhaseListener spl = new SecurityPhaseListener();
                spl.doRedirect(fc, redirectPage);

                break;
        }
    }

    protected String handleUnexpected(FacesContext facesContext, final Throwable t) {
        LOG.error("An unexpected internal error has occurred", t);

        return "jsftoolkit.exception.UncheckedException";
    }
}
The method handleUnexpected(FacesContext, Throwable) can be overwritten in derived classes in order to customization. Handling of various unexpected exceptions can be done there. An example of using:
protected String handleUnexpected(FacesContext facesContext, final Throwable t) {
    if (t instanceof IllegalStateException) {
        // some special handling here
        ...
        return "key.exception.IllegalStateException";
    } else {
        return super.handleUnexpected(facesContext, t);
    }
}
SecurityPhaseListener is the phase listener from my last post allowing JSF redirects.

JSF Ajax redirect after session timeout

JSF 2 has a facility to be able to do Ajax redirects. The specification says - an element <redirect url="redirect url"/> found in the response causes a redirect to the URL "redirect url". JSF component libraries often write other "redirect" elements into response, but the end result is the same - a redirect happens. It works great in case of JSF Ajax redirects.
A web application normally has a security filter - an own solution or from Spring framework or something else. Such security filter does a redirect if user is not authenticated to use requested web resources. This is not an JSF redirect. The filter doesn't pass current request to the FacesServlet at all. Here is an example:
public class SecurityFilter implements Filter {

    private Authenticator authenticator;
    private String loginPage;
    
    public void init(FilterConfig config) throws ServletException {
        ...
        // create an Authenticator  
        authenticator = AuthenticatorFactory.createAuthenticator(config);
        loginPage = getLoginPage(config);
        ...
    }

    public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
        HttpServletRequest hReq = (HttpServletRequest) request;
        HttpServletResponse hRes = (HttpServletResponse) response;
        ...
        // get user roles
        Collection roles = ... 
        // get principal
        Principal principal = hReq.getUserPrincipal();
        
        if (!roles.isEmpty() && principal == null) {
            // user needs to be authenticated
            boolean bRet = authenticator.showLogin(hReq, hRes);
            if (!bRet) {
         // redirect the a login page or error sending
                ...
            }
        }
        ...
    }
}
Problem: indeed an Ajax request is redirected to a specified page after session timeout, but the response can not be proper treated on the client-side. We have a quite regular redirect in this case and the specified page in the response. Client-side Ajax code doesn't expect the entire page coming back.
A possible solution would be to pass "not authenticated" request through FacesServlet. We need to write an Authenticator class which stores the page URL we want to be redirected to in the request scope.
public class FormAuthenticator {
    ...

    public boolean showLogin(HttpServletRequest request, 
        HttpServletResponse response, String loginPage) throws IOException {
        ...
        request.setAttribute("web.secfilter.authenticator.showLogin", loginPage);
 
        return true;
    }
}
The method showLogin returns true in order to avoid a redirect by filter. Let us execute the redirect by JSF! The call gets now a third parameter "loginPage".
authenticator.showLogin(hReq, hRes, loginPage);
If you use Spring security you should have an entry point in your XML configuration file like this one
<beans:bean id="authenticationProcessingFilterEntryPoint"
       class="com.xyz.webapp.security.AuthenticationProcessingFilterEntryPoint">
    <beans:property name="loginFormUrl" value="/login.html"/>
    <beans:property name="serverSideRedirect" value="true"/>
</beans:bean>
The class AuthenticationProcessingFilterEntryPoint is derived from Spring's one
/**
 * Extended Spring AuthenticationProcessingFilterEntryPoint for playing together with JSF Ajax redirects.
 */
public class AuthenticationProcessingFilterEntryPoint extends
    org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint
{
    public static final String ATTRIBUTE_LOGIN_PAGE = "web.secfilter.authenticator.showLogin";

    public void commence(ServletRequest request, ServletResponse response, AuthenticationException authException)
        throws IOException, ServletException {

        request.setAttribute(ATTRIBUTE_LOGIN_PAGE, getLoginFormUrl());
        super.commence(request, response, authException);
    }
}
On the JSF side we need a special phase listener to do the redirect by ExternalContext. We check at first the buffered page URL in the beforePhase. If it was set - an JSF redirect is needed and will be done.
/**
 * Phase listener for the Restore View Phase which manages display of login page.
 */
public class SecurityPhaseListener implements PhaseListener
{
    private static final Log LOG = LogFactory.getLog(SecurityPhaseListener.class);

    public void afterPhase(PhaseEvent event) { ; }

    public void beforePhase(PhaseEvent event) {
        FacesContext fc = event.getFacesContext();

        String loginPage = (String) fc.getExternalContext().getRequestMap().
            get("web.secfilter.authenticator.showLogin");
        if (StringUtils.isNotBlank(loginPage)) {
            doRedirect(fc, loginPage);
        }
    }

    public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }

    /**
     * Does a regular or ajax redirect.
     */
    public void doRedirect(FacesContext fc, String redirectPage) 
        throws FacesException {
        ExternalContext ec = fc.getExternalContext();

        try {
            if (ec.isResponseCommitted()) {
                // redirect is not possible
                return;
            }

            // fix for renderer kit (Mojarra's and PrimeFaces's ajax redirect)
            if ((RequestContext.getCurrentInstance().isAjaxRequest()
                || fc.getPartialViewContext().isPartialRequest())
                && fc.getResponseWriter() == null
                && fc.getRenderKit() == null) {
                    ServletResponse response = (ServletResponse) ec.getResponse();
                    ServletRequest request = (ServletRequest) ec.getRequest();
                    response.setCharacterEncoding(request.getCharacterEncoding());

                    RenderKitFactory factory = (RenderKitFactory)  
                     FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);

                    RenderKit renderKit = factory.getRenderKit(fc,
                     fc.getApplication().getViewHandler().calculateRenderKitId(fc));

                    ResponseWriter responseWriter =
                        renderKit.createResponseWriter(
                        response.getWriter(), null, request.getCharacterEncoding());
                        fc.setResponseWriter(responseWriter);
            }

            ec.redirect(ec.getRequestContextPath() + 
                (redirectPage != null ? redirectPage : ""));
        } catch (IOException e) {
            LOG.error("Redirect to the specified page '" + 
                redirectPage + "' failed");
            throw new FacesException(e);
        }
    }
}
It works in both cases - for Ajax and regular request.

Sunday, October 3, 2010

Install Dropbox under OpenSuse 11.3

Dropbox is a great online tool to share data between computers. The software for OpenSuse can be found either here http://software.opensuse.org/113/en and installed directly or you have to add this repository
http://download.opensuse.org/repositories/openSUSE:/11.3:/Contrib/standard/
to the YAST and install it by YAST. After installation open a terminal and type
dropbox start -i
Just follow the prompts to create an account and choose a dropbox folder on your PC.