Friday, July 13, 2012

Programmatic Facelet:Composite Component with Facet

If you use composite component directly in xhtml file, you can attach facet to component. However, the JSF 2.2 does not give a way to do. The standard API in FaceletFactory is like this
createComponent(String taglibURI, String tagName, Map<String,Object> attributes)

 You can only pass attributes to it.  Attach facet is a really neat feature  since you can insert arbitrary objects to the component  and use component as a template.

I digged into the reference implementation to see how RI implement this method. RI just creates a temporary file exactly like the static file you would write and asks FaceletFactory to create a component from the file.  I can definitely use this approach to include f:facet into the generated file. I verified this approach and it worked.  Through this approach, you can do anything programmatically if you can do it in a static approach. You just need to generate a file like the static file and let FaceletFactory to do the rest work.

There is a trick: If you have any "ui:include", you need to transfer the src path so it is relative to the temporary generated file.

Here I give an example how I do it.
private  File createTempFile(FacesContext context)
    {
        File tmpDir = (File) context.getExternalContext().getApplicationMap().get(
                "javax.servlet.context.tempdir");
        File tempFile;
        try
        {
            tempFile = File.createTempFile("faceletutils", ".xhtml", tmpDir);
        } catch (IOException e)
        {
            throw new RuntimeException(e);
        }
        return tempFile;
    }
/**
     *
     * return a string which navigates from fromfile to tofile.
     *
     * @param fromfile
     *            absolute file path
     * @param tofile
     *            absolute file path
     *
     * @return
     */
    public  String fileToFile(String fromfile, String tofile)
    {if (tofile.startsWith(File.separator))
        {
            tofile = tofile.substring(1);
        }
        if (fromfile.startsWith(File.separator))
        {
            fromfile = fromfile.substring(1);
        }
        String[] froms = fromfile.split(File.separator);
        String[] tos = tofile.split(File.separator);
        List<String> fs = new LinkedList<String>();
        int i = 0;
        for (i = 0; i < froms.length - 1; i++)
        {

            // strip common directory.
            if (!froms[i].equals(tos[i]))
            {
                break;
            }
        }

        if (i == froms.length - 1)
        {
            // reach the last file, all path are the same.
            fs.add(".");
        } else
        {
            for (int j = i; j < froms.length - 1; j++)
            {
                fs.add("..");
            }
        }
        for (int j = i; j < tos.length; j++)
        {
            fs.add(tos[j]);
        }

        return StringUtils.join(fs, "/");
    }
private  String getCompositeComponentString(String library,
            String name, Map<String, Object> attrs, FacesContext context,
            File tempFile, String tempId, Object... facets)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version='1.0' encoding='UTF-8' ?>");
        sb.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
        sb.append("<html xmlns=\"http://www.w3.org/1999/xhtml\"\n");
        sb.append(" xmlns:f=\"http://java.sun.com/jsf/core\"\n");
        sb.append(" xmlns:ui=\"http://java.sun.com/jsf/facelets\"\n");
        sb.append("      xmlns:comp=\"http://java.sun.com/jsf/composite/")
                .append(library).append("\">\n");
        sb.append("  <comp:").append(name).append(" ");

        // have all the attributes
        if (attrs != null && !attrs.isEmpty())
        {
            for (Map.Entry<String, Object> attr : attrs.entrySet())
            {
                sb.append(attr.getKey()).append("=\"")
                        .append(attr.getValue().toString()).append("\"");
            }
        }
        // give component An ID

        sb.append(" id=\"").append(tempId).append("\" >\n");

        // add the facet
        for (int i = 0; i < facets.length; i++)
        {

            sb.append("<f:facet name=\"").append(facets[i++].toString())
                    .append("\" >\n");
            sb.append("<ui:include src=\"")
                    .append(fileToFile(
                            tempFile.getAbsolutePath(),
                            context.getExternalContext().getRealPath(
                                    facets[i].toString()))).append("\" />\n");
            sb.append("</f:facet>\n");
        }

        // end the tag.
        sb.append("</comp:").append(name).append(">");
        sb.append("</html>");

        return sb.toString();
    }
/**
     *
     * Construct a composite comonent given attribute and context-relative facet
     * file.
     *
     * @param library
     *            library of the composite component
     * @param name
     *            name of the composite component
     * @param attrs
     *            a series of attrs value for the component.
     * @param facets
     *            a series of paired value. The first value in pair is facet
     *            name. The second value is CONTEXT-RELATIVE facet file.
     * @return UIComponent represent the constructed composite component
     * @throws IOException
     */
    public static UIComponent applyFacetFileToCompositeComponent(
            String library, String name, Map<String, Object> attrs,
            Object... facets)
    {
        UIComponent result = null;

        FaceletFactory faceletFactory = (FaceletFactory) FactoryFinder
                .getFactory(FactoryFinder.FACELET_FACTORY);
        FacesContext context = FacesContext.getCurrentInstance();
        Application app = context.getApplication();

        String tempId = context.getViewRoot().createUniqueId(context, name);
        File tempFile = createTempFile(context);
        String content = getCompositeComponentString(library, name, attrs,
                context, tempFile, tempId, facets);

        try
        {
            FileUtils.write(tempFile, content);
            URL furl = tempFile.toURI().toURL();
            Facelet f = faceletFactory.getFacelet(furl);
            UIForm form = (UIForm) app.createComponent(UIForm.COMPONENT_TYPE);
            form.setPrependId(false);
            form.setId(context.getViewRoot().createUniqueId());
            f.apply(context, form);
            result = form.findComponent(tempId);
            form.getChildren().clear();

            return result;
        } catch (IOException e)
        {
            throw new RuntimeException(e);
        } finally
        {
            tempFile.delete();
        }
    }


With these methods, I can create my composite with facet like this
applyFacetFileToCompositeComponent("test", "component", null, "firstfacet", "test/firstfragment.xhtml");
where "test" and "component" defines one composite component in resource syntax, and "test/firstfragment.xhtml" is a regular facelet file under web application root directory.






   


 

No comments:

Post a Comment