All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.component.html.util.StreamingAddResource Maven / Gradle / Ivy

Go to download

JSF components and utilities that can be used with any JSF implementation. This library is based on the JSF1.1 version of Tomahawk, but with minor source code and build changes to take advantage of JSF2.1 features. A JSF2.1 implementation is required to use this version of the Tomahawk library.

The newest version!
/*
 * 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.component.html.util;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

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 org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.component.html.util.StreamingThreadManager.HeaderInfoEntry;
import org.apache.myfaces.renderkit.html.util.AddResource;
import org.apache.myfaces.renderkit.html.util.AddResource2;
import org.apache.myfaces.renderkit.html.util.MyFacesResourceHandler;
import org.apache.myfaces.renderkit.html.util.ResourceHandler;
import org.apache.myfaces.renderkit.html.util.ResourceLoader;
import org.apache.myfaces.renderkit.html.util.ResourcePosition;
import org.apache.myfaces.shared_tomahawk.config.MyfacesConfig;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
import org.apache.myfaces.shared_tomahawk.util.ClassUtils;
import org.apache.myfaces.tomahawk.util.ExternalContextUtils;
import org.apache.myfaces.webapp.filter.PortletUtils;

/**
 * This is a utility class to render link to resources used by custom components.
 * 

* This enables a JSF component that is within a page to register scripts and * css stylesheets that it needs, and have them added to the page output. * When multiple components in a page registers a need for the same script or * stylesheet multiple times, only one reference is output in the page. *

* The default DefaultAddResources implementation achieves this by buffering the * complete page in memory until after rendering is complete, then post-processing * the page; this implementation does not require buffering the output but does * have some limitations. *

* To enable the use of this implementation, the web.xml of the application must * set property ???? *

* For references to external resources (javascript files or css files), the url * rendered into the page has the special format: *

 * {contextPath}/faces/myFacesExtensionResource/
 *    {resourceLoaderName}/{cacheKey}/{resourceURI}
 * 
* Where: *
    *
  • {contextPath} is the context path of the current webapp *
  • {resourceLoaderName} is the fully-qualified name of a class which * implements the ResourceLoader interface. When a browser app sends a request * for the specified resource, an instance of the specified ResourceLoader class * will be created and passed the resourceURI part of the URL for resolving to the * actual resource to be served back. The standard MyFaces ResourceLoader * implementation only serves resources for files stored beneath path * org/apache/myfaces/custom in the classpath but non-myfaces code can provide their * own ResourceLoader implementations. *
  • {cacheKey} is a value provided by the ResourceLoader. For the standard ResourceLoader * that returns resources from the tomahawk jarfile, this timestamp is the datetime at which the * tomahawk library was built. This means that browsers will cache these resources for efficiency but * hen a new version of the tomahawk jarfile is deployed then the url changes, so that new versions * of the resources are picked up. Where a "build timestamp" is not available, the startup time of * the webserver is a reasonable alternative; that means that webbrowsers will cache resources until * the webserver is restarted. *
*

* As specified in the base AddResource interface, most methods come in two flavours: one that takes * an explicit ResourceHandler parameter (so can be used by "user" code) and one that implicitly * uses a ResourceHandler that serves only Tomahawk resources (ie is intended for use only by * Tomahawk components). For the tomahawk-specific methods, the standard MyFacesResourceHandler is * used, which in turn uses the standard MyFacesResourceLoader. However for resources that must be * cached and served in a separate request (see below) the custom StreamingResourceHandler is used, * which uses StreamingResourceLoader. *

* The DefaultAddResource implementation inserts javascript file references into the head section of * the generated page. This streaming implementation cannot do that, so it inserts them into the * body of the page instead. This is not technically valid; according to the HTML spec references * to external javascript files should be in the HEAD only. However all modern browsers do support this. * There may be some corner cases where this does result in different behaviour of the page. *

* The DefaultAddResource implementation inserts css file references into the head section of the * generated page by post-processing the page output after all components have finished rendering. * This streaming implementation cannot do that, and no browser supports references to stylesheets * from within the HTML page body. Therefore this class implements a workaround: it expects that * every page will always emits a single CSS link to a "virtual page" from its HEAD section and then * handles the later request for that virtual page by serving any resources that really should * have been embedded in the head of the original page. When the page uses the t:documentHead tag to * rite the HEAD tags of the page, this link is emitted automatically. This does unfortunately mean * that use of this StreamingAddResource always results in an extra GET request per page. It * also means that there needs to be an application-scoped cache that holds per-request cached data, * which introduces some issues regarding "cleanup" of the cache entries. See javadoc of method * addStyleLoaderHere() for more details. * * @author Mario Ivankovits (latest modification by $Author: lu4242 $) * @version $Revision: 954965 $ $Date: 2010-06-15 11:58:31 -0500 (Tue, 15 Jun 2010) $ */ public class StreamingAddResource extends AddResource2 { /** * central place where all request store their "to be added" stylesheets */ //private final static Map headerInfos = new HashMap(); /** * request counter */ private static long REQUEST_ID_COUNTER = 0; /** * own request */ private Long requestId; /** * own header infos - e.g holds the "to be added" stylesheets and a destroy time */ private HeaderInfoEntry headerInfoEntry; /** * helper to determines if the resource has already been added */ private Set alreadySeenResources = new TreeSet(); private static final String PATH_SEPARATOR = "/"; protected static final Log log = LogFactory.getLog(StreamingAddResource.class); protected static final Log logSend = LogFactory.getLog(StreamingAddResource.class.getName() + ".SEND"); private static final String RESOURCE_VIRTUAL_PATH = "/faces/myFacesExtensionResource"; private static final String RESOURCES_CACHE_KEY = AddResource.class.getName() + ".CACHE_KEY"; protected String _contextPath; private String resourceVirtualPath; /* public static class HeaderInfoEntry { private final long destroyTime = System.currentTimeMillis() + (1000 * 60); // one minute; private final List addedInfos = new ArrayList(10); private volatile boolean requestDone = false; protected HeaderInfoEntry() { } protected boolean isDestroyable(long now) { return destroyTime < now; } protected void addInfo(StreamablePositionedInfo positionedInfo) { synchronized (addedInfos) { addedInfos.add(positionedInfo); addedInfos.notifyAll(); } } protected StreamablePositionedInfo fetchInfo() throws InterruptedException { synchronized (addedInfos) { while (addedInfos.size() < 1 && !requestDone) { addedInfos.wait(100); } if (addedInfos.size() < 1) { // request done return null; } return (StreamablePositionedInfo) addedInfos.remove(0); } } protected void setRequestDone() { requestDone = true; } } private static class CleanupThread implements Runnable { // how many entries should be removed per run private final static int CHECKS_PER_RUN = 10; // but never reach this maximum private final static int CACHE_LIMIT = 1000; public void run() { while (!Thread.interrupted()) { checkMap(); try { Thread.sleep(1000 * 30); // check every 30 sek } catch (InterruptedException e) { // ignore } } } private void checkMap() { synchronized (headerInfos) { long now = System.currentTimeMillis(); int checkNo = 0; Iterator iterEntries = headerInfos.entrySet().iterator(); while (iterEntries.hasNext() && !Thread.currentThread().isInterrupted()) { checkNo++; if (headerInfos.size() < CACHE_LIMIT && checkNo > CHECKS_PER_RUN) { return; } Map.Entry entry = (Map.Entry) iterEntries.next(); HeaderInfoEntry headerInfoEntry = (HeaderInfoEntry) entry.getValue(); if (headerInfoEntry.isDestroyable(now)) { iterEntries.remove(); } } } } } static { Thread cleanupThread = new Thread(new CleanupThread(), "StreamingAddResource.CleanupThread"); cleanupThread.setDaemon(true); cleanupThread.start(); } */ public StreamingAddResource() { } /* public static HeaderInfoEntry getHeaderInfo(Long requestId) { synchronized (headerInfos) { return (HeaderInfoEntry) headerInfos.get(requestId); } } public static void removeHeaderInfo(Long requestId) { synchronized (headerInfos) { headerInfos.remove(requestId); } }*/ // Methods to add resources public void setContextPath(String contextPath) { _contextPath = contextPath; } /** * 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 { ResponseWriter writer = context.getResponseWriter(); 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); String src = context.getExternalContext().encodeResourceURL(getResourceUri(context, uri)); writer.writeURIAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SRC_ATTR, src, null); writer.endElement(HTML.SCRIPT_ELEM); } public void addJavaScriptHerePlain(FacesContext context, String uri) throws IOException { ResponseWriter writer = context.getResponseWriter(); 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); String src = getResourceUri(context, uri); writer.writeURIAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SRC_ATTR, src, null); writer.endElement(HTML.SCRIPT_ELEM); } /** * Insert a [script src="url"] entry at the current location in the response. * * @param 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); ResponseWriter writer = context.getResponseWriter(); writer.startElement(HTML.SCRIPT_ELEM, null); writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null); String src = context.getExternalContext().encodeResourceURL( getResourceUri(context, resourceHandler)); writer.writeURIAttribute(HTML.SRC_ATTR, src, null); writer.endElement(HTML.SCRIPT_ELEM); } 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 */ 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 */ protected void validateResourceLoader(Class resourceloader) { if (!ResourceLoader.class.isAssignableFrom(resourceloader)) { throw new FacesException("Class " + resourceloader.getName() + " must implement " + ResourceLoader.class.getName()); } } /** * Adds the given Javascript resource to the document header at the specified * document positioy by supplying a resourcehandler instance. *

* Use this method to have full control about building the reference url * to identify the resource and to customize how the resource is * written to the response. In most cases, however, one of the convenience * methods on this class can be used without requiring a custom ResourceHandler * to be provided. *

* If the script has already been referenced, it's added only once. *

* Note that this method queues the javascript for insertion, and that * the script is inserted into the buffered response by the ExtensionsFilter * after the page is complete. */ public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler) { addJavaScriptAtPosition(context, position, resourceHandler, false); } /** * Insert a [script src="url"] entry into the document header at the * specified document position. If the script has already been * referenced, it's added only once. *

* 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 addJavaScriptAtPosition(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName) { addJavaScriptAtPosition(context, position, new MyFacesResourceHandler( myfacesCustomComponent, resourceName)); } public void addJavaScriptAtPositionPlain(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName) { addJavaScriptAtPosition(context, position, new MyFacesResourceHandler(myfacesCustomComponent, resourceName), false, false); } /** * Insert a [script src="url"] entry into the document header at the * specified document position. If the script has already been * referenced, it's added only once. * * @param defer specifies whether the html attribute "defer" is set on the * generated script tag. If this is true then the browser will continue * processing the html page without waiting for the specified script to * load and be run. */ public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName, boolean defer) { addJavaScriptAtPosition(context, position, new MyFacesResourceHandler( myfacesCustomComponent, resourceName), defer); } /** * Insert a [script src="url"] entry into the document header at the * specified document position. If the script has already been * referenced, it's added only once. * * @param uri is the location of the desired resource, relative to the base * directory of the webapp (ie its contextPath). */ public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, String uri) { addJavaScriptAtPosition(context, position, uri, false); } /** * Adds the given Javascript resource at the specified document position. * If the script has already been referenced, it's added only once. */ public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, String uri, boolean defer) { WritablePositionedInfo info = (WritablePositionedInfo) getScriptInstance(context, uri, defer); if (checkAlreadyAdded(info)) { return; } HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse(); try { info.writePositionedInfo(response, context.getResponseWriter()); } catch (IOException e) { throw new RuntimeException(e); } } public void addJavaScriptToBodyTag(FacesContext context, String javascriptEventName, String addedJavaScript) { throw new UnsupportedOperationException(); } /** * Adds the given Javascript resource at the specified document position. * If the script has already been referenced, it's added only once. */ public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler, boolean defer) { addJavaScriptAtPosition(context, position, resourceHandler, defer, false); } private void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler, boolean defer, boolean encodeURL) { validateResourceHandler(resourceHandler); WritablePositionedInfo info = (WritablePositionedInfo) getScriptInstance(context, resourceHandler, defer, encodeURL); if (checkAlreadyAdded(info)) { return; } HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse(); try { info.writePositionedInfo(response, context.getResponseWriter()); } catch (IOException e) { throw new RuntimeException(e); } } private boolean checkAlreadyAdded(PositionedInfo info) { Long key = new Long(info.hashCode()); if (alreadySeenResources.contains(key)) { return true; } alreadySeenResources.add(key); return false; } /** * Adds the given Style Sheet at the specified document position. * If the style sheet has already been referenced, it's added only once. */ public void addStyleSheet(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName) { addStyleSheet(context, position, new MyFacesResourceHandler(myfacesCustomComponent, resourceName)); } /** * Adds the given Style Sheet at the specified document position. * If the style sheet has already been referenced, it's added only once. */ public void addStyleSheet(FacesContext context, ResourcePosition position, String uri) { uri = getAbsoluteUri(context, uri); addStyleSheet(context, getStyleInstance(context, uri)); } protected String getAbsoluteUri(FacesContext context, String uri) { if (uri.startsWith("/")) { return uri; } StringBuffer sb = new StringBuffer(80); if (context.getExternalContext().getRequestPathInfo() != null) { sb.append(context.getExternalContext().getRequestPathInfo()); } sb.append("/"); sb.append(uri); return sb.toString(); } private void addStyleSheet(FacesContext context, StreamablePositionedInfo styleInstance) { if (checkAlreadyAdded(styleInstance)) { return; } StreamingThreadManager manager = (StreamingThreadManager) context.getExternalContext().getApplicationMap().get(StreamingThreadManager.KEY); getHeaderInfoEntry().addInfo(styleInstance); } /** * Adds the given Style Sheet at the specified document position. * If the style sheet has already been referenced, it's added only once. */ public void addStyleSheet(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler) { validateResourceHandler(resourceHandler); addStyleSheet(context, getStyleInstance(context, resourceHandler)); } /** * Adds the given Inline Style at the specified document position. */ public void addInlineStyleAtPosition(FacesContext context, ResourcePosition position, String inlineStyle) { addStyleSheet(context, getInlineStyleInstance(inlineStyle)); } /** * Adds the given Inline Script at the specified document position. */ public void addInlineScriptAtPosition(FacesContext context, ResourcePosition position, String inlineScript) { WritablePositionedInfo info = (WritablePositionedInfo) getInlineScriptInstance(inlineScript); if (checkAlreadyAdded(info)) { return; } HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse(); try { info.writePositionedInfo(response, context.getResponseWriter()); } catch (IOException e) { throw new RuntimeException(e); } } 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. */ 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. */ 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 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); // response.flushBuffer(); // 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 (ClassNotFoundException e) { log.error("Could not find class for name: " + className, e); sendError(response, 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); sendError(response, 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); sendError(response, HttpServletResponse.SC_FORBIDDEN, "Could not access resourceloader class for name: " + className); } catch (IOException e) { logSend.error("Error while serving resource: " +resourceUri+", message : "+ e.getMessage(), e); sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (Throwable e) { log.error("Unknown error while serving resource: " +resourceUri+", message : "+ e.getMessage(), e); sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } } protected void sendError(HttpServletResponse response, int errorCode, String errorText) throws IOException { try { response.sendError(errorCode, errorText); } catch (IllegalStateException e) { logSend.error("Could not send error, maybe some data has already been sent.", e); } } public boolean hasHeaderBeginInfos(HttpServletRequest request) { throw new UnsupportedOperationException(); } /** * Parses the response to mark the positions where code will be inserted */ public void parseResponse(HttpServletRequest request, String bufferedResponse, HttpServletResponse response) { throw new UnsupportedOperationException(); } /** * Writes the javascript code necessary for myfaces in every page, just befode the closing </body> tag */ public void writeMyFacesJavascriptBeforeBodyEnd(HttpServletRequest request, HttpServletResponse response) throws IOException { throw new UnsupportedOperationException(); } /** * Add the resources to the <head> of the page. * If the head tag is missing, but the <body> tag is present, the head tag is added. * If both are missing, no resource is added. * * The ordering is such that the user header CSS & JS override the MyFaces' ones. */ public void writeWithFullHeader(HttpServletRequest request, HttpServletResponse response) throws IOException { throw new UnsupportedOperationException(); } /** * Writes the response */ public void writeResponse(HttpServletRequest request, HttpServletResponse response) throws IOException { throw new UnsupportedOperationException(); } private StylePositionedInfo getStyleInstance(FacesContext context, ResourceHandler resourceHandler) { return new StylePositionedInfo(getResourceUri(context, resourceHandler)); } private PositionedInfo getScriptInstance(FacesContext context, ResourceHandler resourceHandler, boolean defer, boolean encodeUrl) { return new ScriptPositionedInfo(getResourceUri(context, resourceHandler), defer, encodeUrl); } private StylePositionedInfo getStyleInstance(FacesContext context, String uri) { return new StylePositionedInfo(getResourceUri(context, uri)); } protected PositionedInfo getScriptInstance(FacesContext context, String uri, boolean defer) { return new ScriptPositionedInfo(getResourceUri(context, uri), defer); } private PositionedInfo getInlineScriptInstance(String inlineScript) { return new InlineScriptPositionedInfo(inlineScript); } private InlineStylePositionedInfo getInlineStyleInstance(String inlineStyle) { return new InlineStylePositionedInfo(inlineStyle); } protected interface PositionedInfo { } protected interface WritablePositionedInfo extends PositionedInfo { public abstract void writePositionedInfo(HttpServletResponse response, ResponseWriter writer) throws IOException; } protected interface StreamablePositionedInfo extends PositionedInfo { public abstract void writePositionedInfo(HttpServletResponse response, PrintWriter writer) throws IOException; } private abstract class AbstractResourceUri { protected final String _resourceUri; protected AbstractResourceUri(String resourceUri) { _resourceUri = resourceUri; } public int hashCode() { return _resourceUri.hashCode(); } public boolean equals(Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (obj instanceof AbstractResourceUri) { AbstractResourceUri other = (AbstractResourceUri) obj; return _resourceUri.equals(other._resourceUri); } return false; } protected String getResourceUri() { return _resourceUri; } } private class StylePositionedInfo extends AbstractResourceUri implements WritablePositionedInfo, StreamablePositionedInfo { protected StylePositionedInfo(String resourceUri) { super(resourceUri); } public void writePositionedInfo(HttpServletResponse response, ResponseWriter writer) 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(this.getResourceUri()), null); writer.writeAttribute(HTML.TYPE_ATTR, HTML.STYLE_TYPE_TEXT_CSS, null); writer.endElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.LINK_ELEM); } public void writePositionedInfo(HttpServletResponse response, PrintWriter writer) throws IOException { writer.println(); writer.write("@import url(\""); writer.write(response.encodeURL(this.getResourceUri())); writer.write("\");"); writer.println(); } } private class ScriptPositionedInfo extends AbstractResourceUri implements WritablePositionedInfo { protected final boolean _defer; protected final boolean _encodeUrl; public ScriptPositionedInfo(String resourceUri, boolean defer) { this(resourceUri, defer, true); } public ScriptPositionedInfo(String resourceUri, boolean defer, boolean encodeUrl) { super(resourceUri); _defer = defer; _encodeUrl = encodeUrl; } public int hashCode() { return new HashCodeBuilder() .append(this.getResourceUri()) .append(_defer) .append(_encodeUrl) .toHashCode(); } public boolean equals(Object obj) { if (super.equals(obj)) { if (obj instanceof ScriptPositionedInfo) { ScriptPositionedInfo other = (ScriptPositionedInfo) obj; return new EqualsBuilder() .append(_defer, other._defer) .append(_encodeUrl, other._encodeUrl) .isEquals(); } } return false; } public void writePositionedInfo(HttpServletResponse response, ResponseWriter writer) throws IOException { writer.startElement(HTML.SCRIPT_ELEM, null); writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null); if (_encodeUrl) { writer.writeAttribute(HTML.SRC_ATTR, response.encodeURL(this.getResourceUri()), null); } else { writer.writeAttribute(HTML.SRC_ATTR, this.getResourceUri(), null); } if (_defer) { writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_ELEM_DEFER_ATTR, "true", null); } writer.endElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_ELEM); } } private abstract class InlinePositionedInfo implements WritablePositionedInfo { private final String _inlineValue; protected InlinePositionedInfo(String inlineValue) { _inlineValue = inlineValue; } public String getInlineValue() { return _inlineValue; } public int hashCode() { return new HashCodeBuilder().append(_inlineValue).toHashCode(); } public boolean equals(Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (obj instanceof InlinePositionedInfo) { InlinePositionedInfo other = (InlinePositionedInfo) obj; return new EqualsBuilder().append(_inlineValue, other._inlineValue).isEquals(); } return false; } } private class InlineScriptPositionedInfo extends InlinePositionedInfo { protected InlineScriptPositionedInfo(String inlineScript) { super(inlineScript); } public void writePositionedInfo(HttpServletResponse response, ResponseWriter writer) 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(getInlineValue(), null); writer.endElement(HTML.SCRIPT_ELEM); } } private class InlineStylePositionedInfo extends InlinePositionedInfo implements StreamablePositionedInfo { protected InlineStylePositionedInfo(String inlineStyle) { super(inlineStyle); } public void writePositionedInfo(HttpServletResponse response, ResponseWriter writer) 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(getInlineValue(), null); writer.endElement(HTML.STYLE_ELEM); } public void writePositionedInfo(HttpServletResponse response, PrintWriter writer) throws IOException { writer.println(); writer.write(getInlineValue()); writer.println(); } } public boolean requiresBuffer() { return false; } protected StreamingThreadManager.HeaderInfoEntry getHeaderInfoEntry() { if (headerInfoEntry == null) { throw new IllegalStateException("responseStarted() needs to be called first"); } return headerInfoEntry; } public void responseStarted() { /* synchronized(StreamingAddResource.class) { REQUEST_ID_COUNTER++; requestId = new Long(REQUEST_ID_COUNTER); } headerInfoEntry = new HeaderInfoEntry(); synchronized (headerInfos) { headerInfos.put(requestId, headerInfoEntry); }*/ } public void responseFinished() { getHeaderInfoEntry().setRequestDone(); } public void responseStarted(Object context, Object request) { if(ExternalContextUtils.getRequestType(context, request).isPortlet()) { StreamingThreadManager manager = (StreamingThreadManager) PortletUtils.getAttribute(context, StreamingThreadManager.KEY); requestId = manager.putNewHeaderInfoEntry(); headerInfoEntry = manager.getHeaderInfo(requestId); } else { StreamingThreadManager manager = (StreamingThreadManager) ((ServletContext)context).getAttribute(StreamingThreadManager.KEY); requestId = manager.putNewHeaderInfoEntry(); headerInfoEntry = manager.getHeaderInfo(requestId); } } public boolean hasHeaderBeginInfos() { return false; } /** * Hack to allow pages to register CSS stylesheet files or inline CSS commands. *

* As described in the class javadocs, the "streaming" approach for resources has problems * when it comes to stylesheet links or inline stylesheet commands. These MUST go in the HEAD * section of a page, but by the time a component is being rendered the HEAD section is long * gone. The DefaultAddResource class can solve this because it buffers the page, but here * a different approach is needed. *

* This method should be called during rendering of the HEAD section of a page. For example, * the t:documentHead tag (DocumentHeadRenderer) calls this automatically. A link tag of type * CSS is written to the response, pointing at a virtual page "header.css" which does not actually exist. *

* During rendering of the page body, component renderers may register inline CSS or CSS files. This * info is just cached in the user session. After the page has been sent to the remote browser, the * browser will then make a request to the virtual "header.css" page which this class intercepts and * then serves up the resources needed by the page. *

* Note that the link to the virtual page must always be rendered, as at this time we do * not know whether the body of the page will contain components that need css resources or not. * If no component did need CSS resources, then a zero-sized response is returned. And the value * can change on each request, depending on which components are rendered or not, so a "requestId" * is embedded into the url, making the url change for every request. This requestId is also used * to find the relevant cached resources that need to be served (if any). *

* The url is generated using the StreamingResourceHandler (ie StreamingResourceLoader is the class * embedded in the url). This means that when the browser fetches this resource, the * StreamingResourceLoader is invoked. It in turn extracts the requestId from the parameter and * serves any "head" resources that were registered for the original page. *

* Note that JSF2.0 solves this issue by having components queue "system events" during the "build tree" * phase of rendering. Tomahawk could possibly provide a framework to allow its own classes to * do this for JSF1.2. But for JSF1.1 there is no "build tree" phase so this approach is the only * possibility. * * @param context * @param myfacesCustomComponent * @throws IOException */ public void addStyleLoaderHere(FacesContext context, Class myfacesCustomComponent) 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, getResourceUri(context, new StreamingResourceHandler(requestId + "/header.css"), true), null); writer.writeAttribute(HTML.TYPE_ATTR, HTML.STYLE_TYPE_TEXT_CSS, null); writer.endElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.LINK_ELEM); } 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy