blo0p3r blo0p3r - 1 month ago 12
Java Question

Prevent suffix from being added to resources when page loads

I have a JSF2 application running and working no problem. The issue I am having with JSF is with the resource bundle. All resources have the

.xhtml
suffix appended to it. So
main.css
becomes
main.css.xhtml
when loaded in the browser. I would like to have it so the
.xhtml
isn't apended to the resources (don't mind about the pages themselves).

Is there a way where we can NOT have
.xhtml
appended to resources?

I would ideally not have to change the internal workings of the site. I have listed ideas below, but I have to say I don't really like these. Hoping for a solution somewhere?

I am using Majorra v.2.1.17 on Glassfish 3.1.2.2.

Current Faces Servlet loading as in web.xml (updated)



<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/javax.faces.resource/*</url-pattern>
</servlet-mapping>


Why this questions is different from others





Reasoning



Sure you might be asking me why I need this. Well, we are moving our application to be served by the Akamai CDN.

The issue we are having with the integration of the site is that we are trying to cache static content on the edge servers. This is done by matching file extensions (ie: .js, .doc, .png, css, etc). We cannot match
xhtml
because this would be caching all pages as well as static content. Which by that would cause problems with sessions and such.

Attempted Solution



In line with the answer by BalusC, I have implemented the resource handler as suggested. I will not rewrite code here, since it is in answer below.

However, I am getting an error when loading composite components. I am getting an error as such :

WARNING: StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
java.lang.NullPointerException
at com.sun.faces.application.ApplicationImpl.createComponent(ApplicationImpl.java:975)
at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler.createComponent(CompositeComponentTagHandler.java:162)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.createComponent(ComponentTagHandlerDelegateImpl.java:494)
at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:169)
...


Composite component is loaded correctly because if I "unregister" the new
ResourceHandler
we just created it will load. The stack trace leads me to believe that it is trying to find this component in a java class, instead of finding it in the resources. According to
grepcode
this would be at this last line (975) where the error happens :

String packageName = componentResource.getLibraryName();
String className = componentResource.getResourceName();
className = packageName + '.' + className.substring(0, className.lastIndexOf('.'));


Meaning that the
resourceName
, aka
className
is
null
since the error I am getting is
java.lang.NullPointerException
. I can't seem to figure out how/where the
ResourceHandler
is called vis-a-vis a composite component. Any help figuring out this last issue?

Answer

This is doable with a custom ResourceHandler which returns in createResource() a Resource which in turn returns an "unmapped" URL on Resource#getRequestPath(). You only need to add the default JSF resource prefix /javax.faces.resource/* to the <url-pattern> list of the FacesServlet mapping in order to get it to be triggered anyway.

Further, you need to override isResourceRequest() to check if the URL starts with the JSF resource prefix and also the handleResourceRequest() to locate and stream the proper resource.

All with all, this should do:

public class UnmappedResourceHandler extends ResourceHandlerWrapper {

    private ResourceHandler wrapped;

    public UnmappedResourceHandler(ResourceHandler wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public Resource createResource(final String resourceName, final String libraryName) {
        final Resource resource = super.createResource(resourceName, libraryName);

        if (resource == null) {
            return null;
        }

        return new ResourceWrapper() {

            @Override
            public String getRequestPath() {
                ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
                String mapping = externalContext.getRequestServletPath();

                if (externalContext.getRequestPathInfo() == null) {
                    mapping = mapping.substring(mapping.lastIndexOf('.'));
                }

                String path = super.getRequestPath();

                if (mapping.charAt(0) == '/') {
                    return path.replaceFirst(mapping, "");
                }
                else if (path.contains("?")) {
                    return path.replace(mapping + "?", "?");
                }
                else {
                    return path.substring(0, path.length() - mapping.length());
                }
            }

            @Override // Necessary because this is missing in ResourceWrapper (will be fixed in JSF 2.2).
            public String getResourceName() {
                return resource.getResourceName();
            }

            @Override // Necessary because this is missing in ResourceWrapper (will be fixed in JSF 2.2).
            public String getLibraryName() {
                return resource.getLibraryName();
            }

            @Override // Necessary because this is missing in ResourceWrapper (will be fixed in JSF 2.2).
            public String getContentType() {
                return resource.getContentType();
            }

            @Override
            public Resource getWrapped() {
                return resource;
            }
        };
    }

    @Override
    public boolean isResourceRequest(FacesContext context) {
        return ResourceHandler.RESOURCE_IDENTIFIER.equals(context.getExternalContext().getRequestServletPath());
    }

    @Override
    public void handleResourceRequest(FacesContext context) throws IOException {
        ExternalContext externalContext = context.getExternalContext();
        String resourceName = externalContext.getRequestPathInfo();
        String libraryName = externalContext.getRequestParameterMap().get("ln");
        Resource resource = context.getApplication().getResourceHandler().createResource(resourceName, libraryName);

        if (resource == null) {
            super.handleResourceRequest(context);
            return;
        }

        if (!resource.userAgentNeedsUpdate(context)) {
            externalContext.setResponseStatus(HttpServletResponse.SC_NOT_MODIFIED);
            return;
        }

        externalContext.setResponseContentType(resource.getContentType());

        for (Entry<String, String> header : resource.getResponseHeaders().entrySet()) {
            externalContext.setResponseHeader(header.getKey(), header.getValue());
        }

        ReadableByteChannel input = null;
        WritableByteChannel output = null;

        try {
            input = Channels.newChannel(resource.getInputStream());
            output = Channels.newChannel(externalContext.getResponseOutputStream());

            for (ByteBuffer buffer = ByteBuffer.allocateDirect(10240); input.read(buffer) != -1; buffer.clear()) {
                output.write((ByteBuffer) buffer.flip());
            }
        }
        finally {
            if (output != null) try { output.close(); } catch (IOException ignore) {}
            if (input != null) try { input.close(); } catch (IOException ignore) {}
        }
    }

    @Override
    public ResourceHandler getWrapped() {
        return wrapped;
    }

}

Register it as follows in faces-config.xml:

<application>
    <resource-handler>com.example.UnmappedResourceHandler</resource-handler>
</application>

Extend the FacesServlet URL pattern with ResourceHandler.RESOURCE_IDENTIFIER:

<servlet-mapping>
    <servlet-name>facesServlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
    <url-pattern>/javax.faces.resource/*</url-pattern>
</servlet-mapping>
Comments