阻止在页面加载时将后缀添加到资源

时间:2013-02-19 17:30:37

标签: java jsf jsf-2 akamai

我有一个JSF2应用程序运行并且没有问题。我与JSF的问题在于资源包。所有资源都附加了.xhtml后缀。因此,main.css在浏览器中加载后变为main.css.xhtml。我希望拥有它,以便.xhtml不会依赖资源(不要介意页面本身)。

有没有办法让.xhtml附加到资源上?

理想情况下,我不需要更改网站的内部工作方式。我在下面列出了一些想法,但我不得不说我不喜欢这些。希望在某处找到解决方案?

我在Glassfish 3.1.2.2上使用Majorra v.2.1.17。

Current Faces Servlet加载,如web.xml(已更新)

<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>

为什么这个问题与其他问题不同

推理

当然,你可能会问我为什么需要这个。好吧,我们正在将我们的应用程序移至Akamai CDN。

我们对网站集成的问题是我们正在尝试在边缘服务器上缓存静态内容。这是通过匹配文件扩展名(即:.js,.doc,.png,css等)来完成的。我们无法匹配xhtml,因为这会缓存所有网页以及静态内容。这会导致会话等问题。

尝试解决方案

根据BalusC的回答,我按照建议实现了资源处理程序。我不会在这里重写代码,因为它在下面回答。

但是,我在加载复合组件时遇到错误。我这样收到错误:

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)
...

正确加载复合组件,因为如果我&#34;取消注册&#34;我们刚创建的新ResourceHandler将加载。堆栈跟踪让我相信它正试图在java类中找到这个组件,而不是在资源中找到它。根据{{​​1}},这将发生错误发生的最后一行(975):

grepcode

意味着String packageName = componentResource.getLibraryName(); String className = componentResource.getResourceName(); className = packageName + '.' + className.substring(0, className.lastIndexOf('.')); ,又名resourceNameclassName,因为我得到的错误是null。我似乎无法弄清java.lang.NullPointerException相对于复合组件的调用方式/位置。有没有帮助搞清楚这最后一期?

2 个答案:

答案 0 :(得分:13)

这适用于自定义ResourceHandler,该createResource()会在ResourceResource#getRequestPath()中返回,后者又会在isResourceRequest()上返回“未映射”的网址。您只需要将默认的JSF资源前缀/javax.faces.resource/*添加到<url-pattern>映射的FacesServlet列表中,以便无论如何都能触发它。

此外,您需要覆盖handleResourceRequest()以检查URL是以JSF资源前缀开头,还是{{3}}来查找和流式传输适当的资源。

总而言之,这应该做到:

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

}

faces-config.xml

中注册如下
<application>
    <resource-handler>com.example.UnmappedResourceHandler</resource-handler>
</application>

使用FacesServlet扩展ResourceHandler.RESOURCE_IDENTIFIER网址格式:

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

答案 1 :(得分:3)

您可以查看Rewrite。重写允许修改呈现给页面的URL并以您想要的任何方式修改它们。您可以执行以下操作将CDN添加到您的站点:

.addRule(CDN.relocate("{p}foo-{version}.css")
         .where("p").matches(".*")
         .where("version").matches(".*")
         .to("http://mycdn.com/foo-{version}.css"));

我认为使用Rewrite实现您的要求应该很容易。

查看example configurations以了解重写的功能。