org.apache.myfaces.renderkit.html.util.NonBufferingAddResource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tomahawk Show documentation
Show all versions of tomahawk Show documentation
JSF components and utilities that can be used with any JSF implementation.
This library is compatible with both JSF1.1 and JSF1.2; however for JSF1.2 users there
is an alternative build of Tomahawk available that takes advantage of JSF1.2 features to
offer some additional benefits.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.renderkit.html.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.shared_tomahawk.config.MyfacesConfig;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlResponseWriterImpl;
import org.apache.myfaces.shared_tomahawk.util.ClassUtils;
import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
/**
* @since 1.1.7
* @author Martin Marinschek
*/
public class NonBufferingAddResource implements AddResource {
protected static final String PATH_SEPARATOR = "/";
protected String _contextPath;
private static final String RESOURCES_CACHE_KEY = AddResource.class.getName() + ".CACHE_KEY";
private String resourceVirtualPath;
protected Log log = LogFactory.getLog(NonBufferingAddResource.class);
/**
* the context path for the web-app.
* You can set the context path only once, every subsequent set will throw an SecurityException
*/
public void setContextPath(String contextPath)
{
if (_contextPath != null)
{
throw new SecurityException("context path already set");
}
_contextPath = contextPath;
}
// Methods to add resources
/**
* Insert a [script src="url"] entry at the current location in the response.
* The resource is expected to be in the classpath, at the same location as the
* specified component + "/resource".
*
* Example: when customComponent is class example.Widget, and
* resourceName is script.js, the resource will be retrieved from
* "example/Widget/resource/script.js" in the classpath.
*/
public void addJavaScriptHere(FacesContext context, Class myfacesCustomComponent,
String resourceName) throws IOException
{
addJavaScriptHere(context, new MyFacesResourceHandler(myfacesCustomComponent, resourceName));
}
/**
* Insert a [script src="url"] entry at the current location in the response.
*
* @param uri is the location of the desired resource, relative to the base
* directory of the webapp (ie its contextPath).
*/
public void addJavaScriptHere(FacesContext context, String uri) throws IOException
{
writeJavaScriptReference(context, getResourceUri(context, uri), true, false);
}
protected static void writeJavaScriptReference(FacesContext context, String resourceUri, boolean encoding, boolean defer) throws IOException{
ResponseWriter writer = context.getResponseWriter();
String src = null;
if(encoding) {
src=context.getExternalContext().encodeResourceURL(resourceUri);
}
else {
src = resourceUri;
}
writeJavaScriptReference(defer, writer, src);
}
protected static void writeJavaScriptReference(HttpServletResponse response, ResponseWriter writer, String resourceUri, boolean encoding, boolean defer) throws IOException {
String src = null;
if(encoding) {
src=response.encodeURL(resourceUri);
}
else {
src = resourceUri;
}
writeJavaScriptReference(defer, writer, src);
}
private static void writeJavaScriptReference(boolean defer, ResponseWriter writer, String src) throws IOException {
writer.startElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_ELEM, null);
writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_TYPE_ATTR, org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
if(defer) {
writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_ELEM_DEFER_ATTR, "true", null);
}
writer.writeURIAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SRC_ATTR, src, null);
writer.endElement(HTML.SCRIPT_ELEM);
}
protected static void writeStyleReference(FacesContext context, String resourceUri) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement(HTML.LINK_ELEM, null);
writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.REL_ATTR, org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.STYLESHEET_VALUE, null);
writer.writeAttribute(HTML.HREF_ATTR, context.getExternalContext().encodeResourceURL(resourceUri), null);
writer.writeAttribute(HTML.TYPE_ATTR, HTML.STYLE_TYPE_TEXT_CSS, null);
writer.endElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.LINK_ELEM);
}
protected static void writeStyleReference(HttpServletResponse response, ResponseWriter writer, String resourceUri) throws IOException {
writer.startElement(HTML.LINK_ELEM, null);
writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.REL_ATTR, org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.STYLESHEET_VALUE, null);
writer.writeAttribute(HTML.HREF_ATTR, response.encodeURL(resourceUri), null);
writer.writeAttribute(HTML.TYPE_ATTR, HTML.STYLE_TYPE_TEXT_CSS, null);
writer.endElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.LINK_ELEM);
}
protected static void writeInlineScript(ResponseWriter writer, String inlineScript) throws IOException {
writer.startElement(HTML.SCRIPT_ELEM, null);
writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
writer.writeText(inlineScript, null);
writer.endElement(HTML.SCRIPT_ELEM);
}
protected static void writeInlineStylesheet(ResponseWriter writer, String inlineStyle) throws IOException {
writer.startElement(HTML.STYLE_ELEM, null);
writer.writeAttribute(HTML.REL_ATTR, HTML.STYLESHEET_VALUE, null);
writer.writeAttribute(HTML.TYPE_ATTR, HTML.STYLE_TYPE_TEXT_CSS, null);
writer.writeText(inlineStyle, null);
writer.endElement(HTML.STYLE_ELEM);
}
public void addJavaScriptHerePlain(FacesContext context, String uri) throws IOException
{
writeJavaScriptReference(context, getResourceUri(context, uri), false, false);
}
/**
* Insert a [script src="url"] entry at the current location in the response.
*
* @param context The current faces-context
* @param resourceHandler is an object which specifies exactly how to build the url
* that is emitted into the script tag. Code which needs to generate URLs in ways
* that this class does not support by default can implement a custom ResourceHandler.
* @throws IOException
*/
public void addJavaScriptHere(FacesContext context, ResourceHandler resourceHandler)
throws IOException
{
validateResourceHandler(resourceHandler);
writeJavaScriptReference(context, getResourceUri(context, resourceHandler), true, false);
}
public void addResourceHere(FacesContext context, ResourceHandler resourceHandler)
throws IOException
{
validateResourceHandler(resourceHandler);
String path = getResourceUri(context, resourceHandler);
ResponseWriter writer = context.getResponseWriter();
writer.write(context.getExternalContext().encodeResourceURL(path));
}
/**
* Verify that the resource handler is acceptable. Null is not
* valid, and the getResourceLoaderClass method must return a
* Class object whose instances implements the ResourceLoader
* interface.
*
* @param resourceHandler handler to check
*/
protected void validateResourceHandler(ResourceHandler resourceHandler)
{
if (resourceHandler == null)
{
throw new IllegalArgumentException("ResourceHandler is null");
}
validateResourceLoader(resourceHandler.getResourceLoaderClass());
}
/**
* Given a Class object, verify that the instances of that class
* implement the ResourceLoader interface.
*
* @param resourceloader loader to check
*/
protected void validateResourceLoader(Class resourceloader)
{
if (!ResourceLoader.class.isAssignableFrom(resourceloader))
{
throw new FacesException("Class " + resourceloader.getName() + " must implement "
+ ResourceLoader.class.getName());
}
}
public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler) {
try {
writeJavaScriptReference(context,getResourceUri(context,resourceHandler),true,false);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName) {
try {
writeJavaScriptReference(context,getResourceUri(context,new MyFacesResourceHandler(
myfacesCustomComponent, resourceName)),true,false);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName, boolean defer) {
try {
writeJavaScriptReference(context,getResourceUri(context,new MyFacesResourceHandler(
myfacesCustomComponent, resourceName)),true,true);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, String uri) {
try {
writeJavaScriptReference(context,getResourceUri(context,uri),true,false);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, String uri, boolean defer) {
try {
writeJavaScriptReference(context,getResourceUri(context,uri),true,true);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
/**
*
* @param context
* @param javascriptEventName
* @param addedJavaScript
*
* @deprecated
*/
public void addJavaScriptToBodyTag(FacesContext context, String javascriptEventName, String addedJavaScript) {
throw new UnsupportedOperationException("not supported anymore - use javascript to register your body-event-handler directly");
}
public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler, boolean defer) {
try {
writeJavaScriptReference(context,getResourceUri(context,resourceHandler),true,defer);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public void addJavaScriptAtPositionPlain(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName) {
try {
writeJavaScriptReference(context,getResourceUri(context,new MyFacesResourceHandler(myfacesCustomComponent, resourceName)),false,false);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public void addStyleSheet(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName) {
try {
writeStyleReference(context,getResourceUri(context,new MyFacesResourceHandler(myfacesCustomComponent, resourceName)));
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public void addStyleSheet(FacesContext context, ResourcePosition position, String uri) {
try {
writeStyleReference(context,getResourceUri(context,uri));
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public void addStyleSheet(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler) {
try {
writeStyleReference(context,getResourceUri(context,resourceHandler));
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public void addInlineStyleAtPosition(FacesContext context, ResourcePosition position, String inlineStyle) {
try {
writeInlineStylesheet(context.getResponseWriter(), inlineStyle);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public void addInlineScriptAtPosition(FacesContext context, ResourcePosition position, String inlineScript) {
try {
writeInlineScript(context.getResponseWriter(), inlineScript);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
public String getResourceUri(FacesContext context, Class myfacesCustomComponent,
String resource, boolean withContextPath)
{
return getResourceUri(context,
new MyFacesResourceHandler(myfacesCustomComponent, resource), withContextPath);
}
public String getResourceUri(FacesContext context, Class myfacesCustomComponent, String resource)
{
return getResourceUri(context, new MyFacesResourceHandler(myfacesCustomComponent, resource));
}
/**
* Get the Path used to retrieve an resource.
*/
public String getResourceUri(FacesContext context, ResourceHandler resourceHandler)
{
String uri = resourceHandler.getResourceUri(context);
if (uri == null)
{
return getResourceUri(context, resourceHandler.getResourceLoaderClass(), true);
}
return getResourceUri(context, resourceHandler.getResourceLoaderClass(), true) + uri;
}
/**
* Get the Path used to retrieve an resource.
*/
public String getResourceUri(FacesContext context, ResourceHandler resourceHandler,
boolean withContextPath)
{
String uri = resourceHandler.getResourceUri(context);
if (uri == null)
{
return getResourceUri(context, resourceHandler.getResourceLoaderClass(),
withContextPath);
}
return getResourceUri(context, resourceHandler.getResourceLoaderClass(), withContextPath)
+ uri;
}
/**
* Get the Path used to retrieve an resource.
*/
public String getResourceUri(FacesContext context, String uri)
{
return getResourceUri(context, uri, true);
}
/**
* Get the Path used to retrieve an resource.
*/
public String getResourceUri(FacesContext context, String uri, boolean withContextPath)
{
if (withContextPath)
{
return context.getApplication().getViewHandler().getResourceURL(context, uri);
}
return uri;
}
/**
* Get the Path used to retrieve an resource.
* @param context current faces-context
* @param resourceLoader resourceLoader
* @param withContextPath use the context-path of the web-app when accessing the resources
*
* @return the URI of the resource
*/
protected String getResourceUri(FacesContext context, Class resourceLoader,
boolean withContextPath)
{
StringBuffer sb = new StringBuffer(200);
sb.append(MyfacesConfig.getCurrentInstance(context.getExternalContext()).getResourceVirtualPath());
sb.append(PATH_SEPARATOR);
sb.append(resourceLoader.getName());
sb.append(PATH_SEPARATOR);
sb.append(getCacheKey(context));
sb.append(PATH_SEPARATOR);
return getResourceUri(context, sb.toString(), withContextPath);
}
/**
* Return a value used in the {cacheKey} part of a generated URL for a
* resource reference.
*
* Caching in browsers normally works by having files served to them
* include last-modified and expiry-time http headers. Until the expiry
* time is reached, a browser will silently use its cached version. After
* the expiry time, it will send a "get if modified since {time}" message,
* where {time} is the last-modified header from the version it has cached.
*
* Unfortunately this scheme only works well for resources represented as
* plain files on disk, where the webserver can easily and efficiently see
* the last-modified time of the resource file. When that query has to be
* processed by a servlet that doesn't scale well, even when it is possible
* to determine the resource's last-modified date from servlet code.
*
* Fortunately, for the AddResource class a static resource is only ever
* accessed because a URL was embedded by this class in a dynamic page.
* This makes it possible to implement caching by instead marking every
* resource served with a very long expiry time, but forcing the URL that
* points to the resource to change whenever the old cached version becomes
* invalid; the browser effectively thinks it is fetching a different
* resource that it hasn't seen before. This is implemented by embedding
* a "cache key" in the generated URL.
*
* Rather than using the actual modification date of a resource as the
* cache key, we simply use the webapp deployment time. This means that all
* data cached by browsers will become invalid after a webapp deploy (all
* the urls to the resources change). It also means that changes that occur
* to a resource without a webapp redeploy will not be seen by browsers.
*
* @param context the current faces-context
*
* @return the key for caching
*/
protected long getCacheKey(FacesContext context)
{
// cache key is hold in application scope so it is recreated on redeploying the webapp.
Map applicationMap = context.getExternalContext().getApplicationMap();
Long cacheKey = (Long) applicationMap.get(RESOURCES_CACHE_KEY);
if (cacheKey == null)
{
cacheKey = new Long(System.currentTimeMillis() / 100000);
applicationMap.put(RESOURCES_CACHE_KEY, cacheKey);
}
return cacheKey.longValue();
}
public boolean isResourceUri(ServletContext servletContext, HttpServletRequest request)
{
String path;
if (_contextPath != null)
{
path = _contextPath + getResourceVirtualPath(servletContext);
}
else
{
path = getResourceVirtualPath(servletContext);
}
//fix for TOMAHAWK-660; to be sure this fix is backwards compatible, the
//encoded context-path is only used as a first option to check for the prefix
//if we're sure this works for all cases, we can directly return the first value
//and not double-check.
try
{
if(request.getRequestURI().startsWith(URLEncoder.encode(path,"UTF-8")))
return true;
}
catch (UnsupportedEncodingException e)
{
log.error("Unsupported encoding UTF-8 used",e);
}
return request.getRequestURI().startsWith(path);
}
private String getResourceVirtualPath(ServletContext servletContext)
{
if(resourceVirtualPath == null)
{
resourceVirtualPath = servletContext.getInitParameter(MyfacesConfig.INIT_PARAM_RESOURCE_VIRTUAL_PATH);
if(resourceVirtualPath == null)
{
resourceVirtualPath = MyfacesConfig.INIT_PARAM_RESOURCE_VIRTUAL_PATH_DEFAULT;
}
}
return resourceVirtualPath;
}
private Class getClass(String className) throws ClassNotFoundException
{
Class clazz = ClassUtils.classForName(className);
validateResourceLoader(clazz);
return clazz;
}
public void serveResource(ServletContext context, HttpServletRequest request,
HttpServletResponse response) throws IOException
{
String pathInfo = request.getPathInfo();
String uri = request.getContextPath() + request.getServletPath()
+ (pathInfo == null ? "" : pathInfo);
String classNameStartsAfter = getResourceVirtualPath(context) + '/';
int posStartClassName = uri.indexOf(classNameStartsAfter) + classNameStartsAfter.length();
int posEndClassName = uri.indexOf(PATH_SEPARATOR, posStartClassName);
String className = uri.substring(posStartClassName, posEndClassName);
int posEndCacheKey = uri.indexOf(PATH_SEPARATOR, posEndClassName + 1);
String resourceUri = null;
if (posEndCacheKey + 1 < uri.length())
{
resourceUri = uri.substring(posEndCacheKey + 1);
}
try
{
Class resourceLoader = getClass(className);
validateResourceLoader(resourceLoader);
((ResourceLoader) resourceLoader.newInstance()).serveResource(context, request,
response, resourceUri);
// Do not call response.flushBuffer buffer here. There is no point, as if there
// ever were header data to write, this would fail as we have already written
// the response body. The only point would be to flush the output stream, but
// that will happen anyway when the servlet container closes the socket.
//
// In addition, flushing could fail here; it appears that Microsoft IE
// hasthe habit of hard-closing its socket as soon as it has received a complete
// gif file, rather than letting the server close it. The container will hopefully
// silently ignore exceptions on close.
}
catch (ResourceLoader.ClosedSocketException e)
{
// The ResourceLoader was unable to send the data because the client closed
// the socket on us; just ignore.
}
catch (ClassNotFoundException e)
{
log.error("Could not find class for name: " + className, e);
response.sendError(HttpServletResponse.SC_NOT_FOUND,
"Could not find resourceloader class for name: " + className);
}
catch (InstantiationException e)
{
log.error("Could not instantiate class for name: " + className, e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Could not instantiate resourceloader class for name: " + className);
}
catch (IllegalAccessException e)
{
log.error("Could not access class for name: " + className, e);
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Could not access resourceloader class for name: " + className);
}
catch (Throwable e)
{
log.error("Error while serving resource: " + resourceUri + ", message : " + e.getMessage(), e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
public void parseResponse(HttpServletRequest request, String bufferedResponse, HttpServletResponse response) throws IOException {
throw new UnsupportedOperationException("non-buffering add resource is not buffering.");
}
public void writeMyFacesJavascriptBeforeBodyEnd(HttpServletRequest request, HttpServletResponse response) throws IOException {
throw new UnsupportedOperationException("non-buffering add resource is not buffering.");
}
public void writeWithFullHeader(HttpServletRequest request, HttpServletResponse response) throws IOException {
throw new UnsupportedOperationException("non-buffering add resource is not buffering.");
}
public void writeResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
throw new UnsupportedOperationException("non-buffering add resource is not buffering.");
}
public boolean requiresBuffer() {
return false;
}
public void responseStarted() {
}
public void responseFinished() {
}
public boolean hasHeaderBeginInfos() {
return false;
}
}