Unit-testing Java Server Faces backing beans

Thursday, 29 December 2011 22:37 GMT | Add a comment

Here’s a quick tip for testing JSF backing beans. If you need to run a unit-test for a bean that contains a validate(…) throws ValidatorException method, you may come across the following error message.

Absent Code attribute in method that is not native or abstract in class file javax/faces/validator/ValidatorException

From my cursory research, it appears that the test-runner expects actual implementations of ValidatorException, whereas your compiled tests only have access to the API [interfaces]. Many search results point to solutions along the line of “use an alternative JavaEE API that includes the implementation, e.g. Geronimoâ€.

However, my personal opinion is that basing the correctness of your code on specific implementations can do more harm than good, and that a better solution is to design your classes so that the code that actually needs testing is isolated into an abstract class.

For example, say I have an

<f:viewParam id=â€id†name=â€id†value=â€#{actionDetailsBean.id}â€>

with a validator bound to method #{actionDetailsBean.validate}.

The proper way  of implementing this is to completely move the validation code away from JSF, such that you end up with a delegate method that gets called by the bound validate(…) method.

The following code snippet shows how the validation method would be called.

@ManagedBean(name="actionDetailsBean")
@RequestScoped
public class ActionDetailsBean extends AbstractActionDetailsBean {

    private static final String COMPONENT_ID = "id";

    public ActionDetailsBean() {
        super();
    }

    public ActionDetailsBean(ActionDetailsBeanOut actionDetailsBeanOut) {
        super(actionDetailsBeanOut);
    }

    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException{
        switch (component.getId().toLowerCase()) {
            case COMPONENT_ID:
                validateId();
                break;
        }
    }

    private void validateId() {
        load();
        if (!isLoaded())
            throw new ValidatorException(new FacesMessage("Id not recognised."));
    }
}

In this example, validateId() is an extracted method that calls the load() method. The isLoaded() method returns a boolean to indicate whether the “action details†were loaded and, by extension, whether the id was valid.

Another principle of unit-testing is that methods should be tested for their ability to fulfil their responsibility without much concern to how they are implemented. In this case, there is no point of testing the validate(…) and validateId() methods. We should trust that validate(…) will get called by JSF and will throw a ValidatorException if there is a validation error, implying that testing of the load() method should be sufficient.

The following snippet shows the superclass for ActionDetailsBean.

public abstract class AbstractActionDetailsBean {

    @Inject
    protected ActionDetailsBeanOut actionDetailsBeanOut;

    private String id;
    private String title;
    private String description;
    private String context;
    private boolean loaded;

    public AbstractActionDetailsBean() {
    }

    public AbstractActionDetailsBean(ActionDetailsBeanOut actionDetailsBeanOut) {
        this.actionDetailsBeanOut = actionDetailsBeanOut;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getContext() {
        return context;
    }

    public void setContext(String context) {
        this.context = context;
    }

    protected boolean isLoaded() {
        return loaded;
    }

    public void load() {
        if (loaded) return;
        Map actionDetails = actionDetailsBeanOut.requestActionDetails(id);
        if (null == actionDetails) return;
        setId((String) actionDetails.get("id"));
        setTitle((String) actionDetails.get("title"));
        setDescription((String) actionDetails.get("description"));
        setContext((String) actionDetails.get("context"));
        loaded = true;
    }
}

Here, ActionDetailsBeanOut is an SPI interface, the implementation of which is injected at run-time to provide “action dataâ€. load() populates the bean and sets loaded to true, if successful. AbstractActionDetailsBean has no dependency other than the injection.

Finally, coming back to the initial premise of this post, the way to avoid the dependency on a concrete JavaEE class and make your unit-test runnable is to test an anonymous implementation of AbstractActionDetailsBean, as follows.

public class AbstractActionDetailsBeanTest {

    @Test
    public void testLoad() {
        ActionDetailsBeanOut actionDetailsBeanOut = new ActionDetailsBeanOut() {
            @Override
            public Map requestActionDetails(String id) {
                HashMap details = new HashMap<>();
                details.put("id", "id");
                details.put("title", "title");
                details.put("description", "description");
                details.put("context", "@context");
                return details;
            }
        };
        AbstractActionDetailsBean actionDetailsBean = new AbstractActionDetailsBean(actionDetailsBeanOut) {};
        actionDetailsBean.setId("id");
        actionDetailsBean.load();
        Assert.assertEquals("id", actionDetailsBean.getId());
        Assert.assertEquals("title", actionDetailsBean.getTitle());
        Assert.assertEquals("description", actionDetailsBean.getDescription());
        Assert.assertEquals("@context", actionDetailsBean.getContext());
        Assert.assertTrue(actionDetailsBean.isLoaded());
    }
}

How to process multipart/* requests in Java Server Faces

Tuesday, 13 December 2011 21:57 GMT | Add a comment

This post describes how multipart/form-data requests can be processed within a Java Server Faces managed bean in order to implement file uploads.

First, intercept the servlet request and wrap it in custom HttpServletRequestWrapper before passing it to FacesServlet for further processing. This is achieved via a web filter, as follows.

@WebFilter(urlPatterns = "/*")
public class MultipartRequestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest;
        if (request instanceof ServletRequest) {
            httpServletRequest = (HttpServletRequest) request;
        } else if (request instanceof ServletRequestWrapper) {
            httpServletRequest = (HttpServletRequest) ((ServletRequestWrapper)request).getRequest();
        } else {
                        throw new UnsupportedOperationException("Not implemented");
                }
        if (isMultipartRequest(httpServletRequest)) {
            request = new MultipartServletRequestWrapper(httpServletRequest);
        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    }

    private boolean isMultipartRequest(HttpServletRequest request) {
        return "POST".equalsIgnoreCase(request.getMethod()) &&
                request.getContentType() != null &&
                request.getContentType().toLowerCase().startsWith("multipart/");
    }
}

This code extract shows the request being checked, transformed into a MultipartServletRequestWrapper, and passed down the filter chain.

Next, implement MultipartServletRequestWrapper, as follows.

public class MultipartServletRequestWrapper extends HttpServletRequestWrapper {
    private Hashtable managedParts = new Hashtable<>();

    public MultipartServletRequestWrapper(HttpServletRequest request) throws IOException, ServletException {
                super(request);
                readParts(request);
    }

    @Override
    public String getParameter(String name) {
        String[] parameterValues = getParameterValues(name);
        return parameterValues.length > 0 ? parameterValues[0] : null;
    }

    @Override
    public Enumeration getParameterNames() {
        return managedParts.keys();
    }

    @Override
    public String[] getParameterValues(String name) {
        Part p = managedParts.get(name);
        if (null == p) return new String[0];
        byte[] b = new byte[(int) p.getSize()];
        try {
            p.getInputStream().read(b);
            return new String[]{new String(b)};
        } catch (IOException ex) {
            return new String[0];
        }
    }

    @Override
    public Map getParameterMap() {
        HashMap map = new HashMap<>();
        Enumeration keys = managedParts.keys();
        while (keys.hasMoreElements()) {
            String key = keys.nextElement();
            map.put(key, getParameterValues(key));
        }
        return map;
    }

    private void readParts(HttpServletRequest request) throws IOException, ServletException {
        for (Part part : request.getParts()) {
            managedParts.put(part.getName(), part);
        }
    }
}

Overriding the getParameter* methods is the crucial part of this class; without these, JSF is unable to restore views and by extension process the submission.

Finally, in your bean, obtain the part that contains the file upload and process it as desired.

@ManagedBean(name="fileUploadBean")
@RequestScoped
public class FileUploadBean {

    public String upload() throws FileNotFoundException, IOException, ServletException {
        ServletRequest servletRequest = (ServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
        if (servletRequest instanceof MultipartServletRequestWrapper) {
            Part part = ((MultipartServletRequestWrapper) servletRequest).getPart("fileUpload");
            String header = part.getHeader("Content-disposition");
            String filename = header.substring(header.indexOf("filename=") + "filename=".length());
            if ((null != filename) && (0 < filename.length())) {
                FileOutputStream fos = new FileOutputStream("C:\\Temp\\test.png");
                byte[] b = new byte[(int) part.getSize()];
                part.getInputStream().read(b);
                fos.write(b);
                fos.close();
            }
        }
        return null;
    }
}

This bean here reads the content of the part and writes it to file.

It’s turning into a monster!

Saturday, 22 October 2011 20:26 GMT | Add a comment

I may be getting over-enthusiastic about the JavaScript piece I published earlier, as it is now turning into something bigger and scarier than I envisaged before. The purpose of it remains educational, so I’ve started a small project (Java Server Faces; what else?) to put it through its pace, and here are a couple of screenshots showing the result.

The “Search for jobs†dialog is automatically registered for automatic positioning in the middle when it is shown for the first time, and events (onkeyup, dialog-specific events, etc.) are handled by the caller by a system of call-back.

I’m facing the dilemma of whether to block events on the page behind the dialog when it is shown.

image

Also notice the highlighting of the line where the mouse pointer is positioned.

image

Now, back to work.

Potentially useful JavaScript for floating DIV dialogs

Wednesday, 19 October 2011 20:59 GMT | Add a comment

I am not completely new to JavaScript (previous bits here, here, and there), but this is the first time I’m having to deal with floating DIVs as dialogs. So, from my little bit of experimenting came the following. If you are after something lighter than jQuery UI, this may prove useful to you.

<!DOCTYPE html>
<html>
    <head>
        <title>Dialogs Example</title>
        <script type="text/javascript">
            if (undefined === DialogJS) {
                var DialogJS = (function () {

                    var dialogs = {};

                    function showAsDialog(elementId, ownerElementId) {
                        var element = document.getElementById(elementId);
                        var ownerElement =
                            (null !== ownerElementId)
                                ? document.getElementById(ownerElementId)
                                : document.documentElement;
                        if ((null !== element) && (null !== ownerElement)) {
                            dialogs[elementId] = { element: element, ownerElement: ownerElement };
                            dialogs[elementId].element.style.display = "block";
                        }

                        centerDialogs();
                    }

                    function hide(elementId) {
                        if (undefined === dialogs[elementId]) {
                            return;
                        }
                        dialogs[elementId].element.style.display = "none";
                    }

                    function centerDialogs() {
                        for (var x in dialogs) {
                            centerDialog(dialogs[x]);
                        }
                    }

                    function centerDialog(dialog) {
                        if (null === dialog) {
                            return;
                        }

                        var parentWidth = dialog.ownerElement.clientWidth;
                        var parentLeft = dialog.ownerElement.offsetLeft;
                        if (parentWidth > document.documentElement.clientWidth) {
                            parentWidth = document.documentElement.clientWidth;
                            parentLeft = 0;
                        }

                        var parentHeight = dialog.ownerElement.clientHeight;
                        var parentTop = dialog.ownerElement.offsetTop;
                        if (parentHeight > document.documentElement.clientHeight) {
                            parentHeight = document.documentElement.clientHeight;
                            parentTop = 0;
                        }

                        var x = 0.5 * (parentWidth - dialog.element.clientWidth) + parentLeft;
                        var y = 0.5 * (parentHeight - dialog.element.clientHeight) + parentTop;

                        if (x < dialog.ownerElement.offsetLeft) {
                            x = dialog.ownerElement.offsetLeft;
                        }
                        if (y < dialog.ownerElement.offsetTop) {
                            y = dialog.ownerElement.offsetTop;
                        }

                        dialog.element.style.position = "absolute";
                        dialog.element.style.left = x + "px";
                        dialog.element.style.top = y + "px";
                    }

                    return {
                        showAsDialog: showAsDialog,
                        hide: hide,
                        centerDialogs: centerDialogs
                    };
                })();
            }

            window.onresize = DialogJS.centerDialogs;
        </script>
    </head>
    <body>
        <input type="button" value="Show Dialog" onclick="DialogJS.showAsDialog(&quot;dialog1&quot;, &quot;page&quot;)" />
        <input type="button" value="Hide Dialog" onclick="DialogJS.hide(&quot;dialog1&quot;)" />
        <div id="page" style="border: 1px solid gray; height: 700px; margin: auto; width: 600px;">
            THIS IS THE CONTAINER
            <div id="dialog1" style="background-color: antiquewhite; border: 1px solid black; display: none; height: 100px; width: 100px;">
            THIS IS THE DIALOG
            </div>
        </div>
    </body>
</html>

Taking the JavaScript pill

Sunday, 18 September 2011 19:56 GMT | Add a comment

image

They say that JavaScript and HTML5 are the future, so I took the pill.



Older »

Powered by WordPress and Eddy Young.

DISCLAIMER: This site is supported by advertising. As a result, cookies may be installed by advertisers in order to track usage and trends. If you do not want this, please disable cookies for this site.


You are viewing a mobilized version of this site...
View original page here

Mobilized by Mowser Mowser