View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.custom.dojoaddresource;
20  
21  import org.apache.commons.lang.builder.EqualsBuilder;
22  import org.apache.commons.lang.builder.HashCodeBuilder;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.myfaces.custom.dojo.DojoUtils;
26  import org.apache.myfaces.renderkit.html.util.AddResource;
27  import org.apache.myfaces.renderkit.html.util.MyFacesResourceHandler;
28  import org.apache.myfaces.renderkit.html.util.ResourceHandler;
29  import org.apache.myfaces.renderkit.html.util.ResourceLoader;
30  import org.apache.myfaces.renderkit.html.util.ResourcePosition;
31  import org.apache.myfaces.shared_tomahawk.config.MyfacesConfig;
32  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
33  import org.apache.myfaces.shared_tomahawk.util.ClassUtils;
34  
35  import javax.faces.FacesException;
36  import javax.faces.context.FacesContext;
37  import javax.faces.context.ResponseWriter;
38  import javax.servlet.ServletContext;
39  import javax.servlet.http.HttpServletRequest;
40  import javax.servlet.http.HttpServletResponse;
41  import java.io.IOException;
42  import java.io.UnsupportedEncodingException;
43  import java.net.URLEncoder;
44  import java.util.Map;
45  import java.util.Set;
46  import java.util.TreeSet;
47  
48  /**
49   * This is a utility class to render link to resources used by custom components.
50   * Mostly used to avoid having to include [script src="..."][/script]
51   * in the head of the pages before using a component.
52   * <p>
53   * The special URL format is:
54   * <pre>
55   * {contextPath}/faces/myFacesExtensionResource/
56   *    {resourceLoaderName}/{cacheKey}/{resourceURI}
57   * </pre>
58   * Where:
59   * <ul>
60   * <li> {contextPath} is the context path of the current webapp
61   * <li> {resourceLoaderName} is the fully-qualified name of a class which
62   *  implements the ResourceLoader interface. When a browser app sends a request
63   *  for the specified resource, an instance of the specified ResourceLoader class
64   *  will be created and passed the resourceURI part of the URL for resolving to the
65   *  actual resource to be served back. The standard MyFaces ResourceLoader
66   *  implementation only serves resources for files stored beneath path
67   *  org/apache/myfaces/custom in the classpath but non-myfaces code can provide their
68   *  own ResourceLoader implementations.
69   * </ul>
70   *
71   * @author Mario Ivankovits (latest modification by $Author: lu4242 $)
72   * @version $Revision: 702330 $ $Date: 2008-10-06 21:41:07 -0500 (Mon, 06 Oct 2008) $
73   */
74  public class DojoAddResource implements AddResource
75  {
76      /**
77       * helper to determines if the resource has already been added
78       */
79      private Set alreadySeenResources = new TreeSet();
80  
81      private static final String PATH_SEPARATOR = "/";
82  
83      protected static final Log log = LogFactory.getLog(DojoAddResource.class);
84      protected static final Log logSend = LogFactory.getLog(DojoAddResource.class.getName() + ".SEND");
85  
86      private static final String RESOURCE_VIRTUAL_PATH = "/faces/myFacesExtensionResource";
87  
88      private static final String RESOURCES_CACHE_KEY = AddResource.class.getName() + ".CACHE_KEY";
89  
90      protected String _contextPath;
91      private String resourceVirtualPath;
92  
93      public DojoAddResource()
94      {
95      }
96  
97      // Methods to add resources
98      public void setContextPath(String contextPath)
99      {
100         _contextPath = contextPath;
101     }
102 
103     /**
104      * Insert a [script src="url"] entry at the current location in the response.
105      * The resource is expected to be in the classpath, at the same location as the
106      * specified component + "/resource".
107      * <p>
108      * Example: when customComponent is class example.Widget, and
109      * resourceName is script.js, the resource will be retrieved from
110      * "example/Widget/resource/script.js" in the classpath.
111      */
112     public void addJavaScriptHere(FacesContext context, Class myfacesCustomComponent,
113                                   String resourceName) throws IOException
114     {
115         addJavaScriptHere(context, new MyFacesResourceHandler(myfacesCustomComponent, resourceName));
116     }
117 
118     /**
119      * Insert a [script src="url"] entry at the current location in the response.
120      *
121      * @param uri is the location of the desired resource, relative to the base
122      * directory of the webapp (ie its contextPath).
123      */
124     public void addJavaScriptHere(FacesContext context, String uri) throws IOException
125     {
126         ResponseWriter writer = context.getResponseWriter();
127 
128         writer.startElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_ELEM, null);
129         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);
130         String src = context.getExternalContext().encodeResourceURL(getResourceUri(context, uri));
131         writer.writeURIAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SRC_ATTR, src, null);
132         writer.endElement(HTML.SCRIPT_ELEM);
133     }
134 
135     public void addJavaScriptHerePlain(FacesContext context, String uri) throws IOException
136     {
137         ResponseWriter writer = context.getResponseWriter();
138 
139         writer.startElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_ELEM, null);
140         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);
141         String src = getResourceUri(context, uri);
142         writer.writeURIAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SRC_ATTR, src, null);
143         writer.endElement(HTML.SCRIPT_ELEM);
144     }
145 
146     /**
147      * Insert a [script src="url"] entry at the current location in the response.
148      *
149      * @param context
150      *
151      * @param resourceHandler is an object which specifies exactly how to build the url
152      * that is emitted into the script tag. Code which needs to generate URLs in ways
153      * that this class does not support by default can implement a custom ResourceHandler.
154      *
155      * @throws java.io.IOException
156      */
157     public void addJavaScriptHere(FacesContext context, ResourceHandler resourceHandler)
158             throws IOException
159     {
160         validateResourceHandler(resourceHandler);
161 
162         ResponseWriter writer = context.getResponseWriter();
163 
164         writer.startElement(HTML.SCRIPT_ELEM, null);
165         writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
166         String src = context.getExternalContext().encodeResourceURL(
167                 getResourceUri(context, resourceHandler));
168         writer.writeURIAttribute(HTML.SRC_ATTR, src, null);
169         writer.endElement(HTML.SCRIPT_ELEM);
170     }
171 
172     public void addResourceHere(FacesContext context, ResourceHandler resourceHandler)
173             throws IOException
174     {
175         validateResourceHandler(resourceHandler);
176 
177         String path = getResourceUri(context, resourceHandler);
178         ResponseWriter writer = context.getResponseWriter();
179         writer.write(context.getExternalContext().encodeResourceURL(path));
180     }
181 
182     /**
183      * Verify that the resource handler is acceptable. Null is not
184      * valid, and the getResourceLoaderClass method must return a
185      * Class object whose instances implements the ResourceLoader
186      * interface.
187      *
188      * @param resourceHandler
189      */
190     protected void validateResourceHandler(ResourceHandler resourceHandler)
191     {
192         if (resourceHandler == null)
193         {
194             throw new IllegalArgumentException("ResourceHandler is null");
195         }
196         validateResourceLoader(resourceHandler.getResourceLoaderClass());
197     }
198 
199     /**
200      * Given a Class object, verify that the instances of that class
201      * implement the ResourceLoader interface.
202      *
203      * @param resourceloader
204      */
205     protected void validateResourceLoader(Class resourceloader)
206     {
207         if (!ResourceLoader.class.isAssignableFrom(resourceloader))
208         {
209             throw new FacesException("Class " + resourceloader.getName() + " must implement "
210                     + ResourceLoader.class.getName());
211         }
212     }
213 
214     /**
215      * Adds the given Javascript resource to the document header at the specified
216      * document positioy by supplying a resourcehandler instance.
217      * <p>
218      * Use this method to have full control about building the reference url
219      * to identify the resource and to customize how the resource is
220      * written to the response. In most cases, however, one of the convenience
221      * methods on this class can be used without requiring a custom ResourceHandler
222      * to be provided.
223      * <p>
224      * If the script has already been referenced, it's added only once.
225      * <p>
226      * Note that this method <i>queues</i> the javascript for insertion, and that
227      * the script is inserted into the buffered response by the ExtensionsFilter
228      * after the page is complete.
229      */
230     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position,
231                                         ResourceHandler resourceHandler)
232     {
233         addJavaScriptAtPosition(context, position, resourceHandler, false);
234     }
235 
236     /**
237      * Insert a [script src="url"] entry into the document header at the
238      * specified document position. If the script has already been
239      * referenced, it's added only once.
240      * <p>
241      * The resource is expected to be in the classpath, at the same location as the
242      * specified component + "/resource".
243      * <p>
244      * Example: when customComponent is class example.Widget, and
245      * resourceName is script.js, the resource will be retrieved from
246      * "example/Widget/resource/script.js" in the classpath.
247      */
248     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position,
249                                         Class myfacesCustomComponent, String resourceName)
250     {
251         addJavaScriptAtPosition(context, position, new MyFacesResourceHandler(
252                 myfacesCustomComponent, resourceName));
253     }
254 
255     public void addJavaScriptAtPositionPlain(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName)
256     {
257         addJavaScriptAtPosition(context, position,
258                 new MyFacesResourceHandler(myfacesCustomComponent, resourceName),
259                 false, false);
260     }
261 
262     /**
263      * Insert a [script src="url"] entry into the document header at the
264      * specified document position. If the script has already been
265      * referenced, it's added only once.
266      *
267      * @param defer specifies whether the html attribute "defer" is set on the
268      * generated script tag. If this is true then the browser will continue
269      * processing the html page without waiting for the specified script to
270      * load and be run.
271      */
272     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position,
273                                         Class myfacesCustomComponent, String resourceName, boolean defer)
274     {
275         addJavaScriptAtPosition(context, position, new MyFacesResourceHandler(
276                 myfacesCustomComponent, resourceName), defer);
277     }
278 
279     /**
280      * Insert a [script src="url"] entry into the document header at the
281      * specified document position. If the script has already been
282      * referenced, it's added only once.
283      *
284      * @param uri is the location of the desired resource, relative to the base
285      * directory of the webapp (ie its contextPath).
286      */
287     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, String uri)
288     {
289         addJavaScriptAtPosition(context, position, uri, false);
290     }
291 
292     /**
293      * Adds the given Javascript resource at the specified document position.
294      * If the script has already been referenced, it's added only once.
295      */
296     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, String uri,
297                                         boolean defer)
298     {
299         WritablePositionedInfo info = (WritablePositionedInfo) getScriptInstance(context, uri, defer);
300         if (checkAlreadyAdded(info))
301         {
302             return;
303         }
304         try
305         {
306             info.writePositionedInfo(context, context.getResponseWriter());
307         }
308         catch (IOException e)
309         {
310             throw new RuntimeException(e);
311         }
312     }
313 
314     public void addJavaScriptToBodyTag(FacesContext context, String javascriptEventName,
315                                        String addedJavaScript)
316     {
317         throw new UnsupportedOperationException();
318     }
319 
320     /**
321      * Adds the given Javascript resource at the specified document position.
322      * If the script has already been referenced, it's added only once.
323      */
324     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler, boolean defer)
325     {
326         addJavaScriptAtPosition(context, position, resourceHandler, defer, false);
327     }
328 
329     private void addJavaScriptAtPosition(FacesContext context, ResourcePosition position,
330                                          ResourceHandler resourceHandler, boolean defer, boolean encodeURL)
331     {
332         validateResourceHandler(resourceHandler);
333         WritablePositionedInfo info = (WritablePositionedInfo) getScriptInstance(context, resourceHandler, defer, encodeURL);
334         if (checkAlreadyAdded(info))
335         {
336             return;
337         }
338         try
339         {
340             info.writePositionedInfo(context, context.getResponseWriter());
341         }
342         catch (IOException e)
343         {
344             throw new RuntimeException(e);
345         }
346     }
347 
348     private boolean checkAlreadyAdded(PositionedInfo info)
349     {
350         Long key = new Long(info.hashCode());
351         if (alreadySeenResources.contains(key))
352         {
353             return true;
354         }
355 
356         alreadySeenResources.add(key);
357         return false;
358     }
359 
360     /**
361      * Adds the given Style Sheet at the specified document position.
362      * If the style sheet has already been referenced, it's added only once.
363      */
364     public void addStyleSheet(FacesContext context, ResourcePosition position,
365                               Class myfacesCustomComponent, String resourceName)
366     {
367         addStyleSheet(context, position, new MyFacesResourceHandler(myfacesCustomComponent,
368                 resourceName));
369     }
370 
371     /**
372      * Adds the given Style Sheet at the specified document position.
373      * If the style sheet has already been referenced, it's added only once.
374      */
375     public void addStyleSheet(FacesContext context, ResourcePosition position, String uri)
376     {
377         uri = getAbsoluteUri(context, uri);
378 
379         addStyleSheet(context, getStyleInstance(context, uri));
380     }
381 
382     private void addStyleSheet(FacesContext context, WritablePositionedInfo styleInstance)
383     {
384         try
385         {
386             styleInstance.writePositionedInfo(context, context.getResponseWriter());
387         }
388         catch (IOException e)
389         {
390             throw new RuntimeException(e);
391         }
392     }
393 
394     protected String getAbsoluteUri(FacesContext context, String uri)
395     {
396         if (uri.startsWith("/"))
397         {
398             return uri;
399         }
400 
401         StringBuffer sb = new StringBuffer(80);
402         if (context.getExternalContext().getRequestPathInfo() != null)
403         {
404             sb.append(context.getExternalContext().getRequestPathInfo());
405         }
406         sb.append("/");
407         sb.append(uri);
408 
409         return sb.toString();
410     }
411 
412     /**
413      * Adds the given Style Sheet at the specified document position.
414      * If the style sheet has already been referenced, it's added only once.
415      */
416     public void addStyleSheet(FacesContext context, ResourcePosition position,
417                               ResourceHandler resourceHandler)
418     {
419         validateResourceHandler(resourceHandler);
420         addStyleSheet(context, getStyleInstance(context, resourceHandler));
421     }
422 
423     /**
424      * Adds the given Inline Style at the specified document position.
425      */
426     public void addInlineStyleAtPosition(FacesContext context, ResourcePosition position, String inlineStyle)
427     {
428         addStyleSheet(context, getInlineStyleInstance(inlineStyle));
429     }
430 
431     /**
432      * Adds the given Inline Script at the specified document position.
433      */
434     public void addInlineScriptAtPosition(FacesContext context, ResourcePosition position,
435                                           String inlineScript)
436     {
437         WritablePositionedInfo info = (WritablePositionedInfo) getInlineScriptInstance(inlineScript);
438         if (checkAlreadyAdded(info))
439         {
440             return;
441         }
442 
443         try
444         {
445             info.writePositionedInfo(context, context.getResponseWriter());
446         }
447         catch (IOException e)
448         {
449             throw new RuntimeException(e);
450         }
451     }
452 
453     public String getResourceUri(FacesContext context, Class myfacesCustomComponent,
454                                  String resource, boolean withContextPath)
455     {
456         return getResourceUri(context,
457                 new MyFacesResourceHandler(myfacesCustomComponent, resource), withContextPath);
458     }
459 
460     public String getResourceUri(FacesContext context, Class myfacesCustomComponent, String resource)
461     {
462         return getResourceUri(context, new MyFacesResourceHandler(myfacesCustomComponent, resource));
463     }
464 
465     /**
466      * Get the Path used to retrieve an resource.
467      */
468     public String getResourceUri(FacesContext context, ResourceHandler resourceHandler)
469     {
470         String uri = resourceHandler.getResourceUri(context);
471         if (uri == null)
472         {
473             return getResourceUri(context, resourceHandler.getResourceLoaderClass(), true);
474         }
475         return getResourceUri(context, resourceHandler.getResourceLoaderClass(), true) + uri;
476     }
477 
478     /**
479      * Get the Path used to retrieve an resource.
480      */
481     public String getResourceUri(FacesContext context, ResourceHandler resourceHandler,
482                                  boolean withContextPath)
483     {
484         String uri = resourceHandler.getResourceUri(context);
485         if (uri == null)
486         {
487             return getResourceUri(context, resourceHandler.getResourceLoaderClass(),
488                     withContextPath);
489         }
490         return getResourceUri(context, resourceHandler.getResourceLoaderClass(), withContextPath)
491                 + uri;
492     }
493 
494     /**
495      * Get the Path used to retrieve an resource.
496      */
497     public String getResourceUri(FacesContext context, String uri)
498     {
499         return getResourceUri(context, uri, true);
500     }
501 
502     /**
503      * Get the Path used to retrieve an resource.
504      */
505     public String getResourceUri(FacesContext context, String uri, boolean withContextPath)
506     {
507         if (withContextPath)
508         {
509             return context.getApplication().getViewHandler().getResourceURL(context, uri);
510         }
511         return uri;
512     }
513 
514     /**
515      * Get the Path used to retrieve an resource.
516      */
517     protected String getResourceUri(FacesContext context, Class resourceLoader,
518                                     boolean withContextPath)
519     {
520         StringBuffer sb = new StringBuffer(200);
521         sb.append(MyfacesConfig.getCurrentInstance(context.getExternalContext()).getResourceVirtualPath());
522         sb.append(PATH_SEPARATOR);
523         sb.append(resourceLoader.getName());
524         sb.append(PATH_SEPARATOR);
525         sb.append(getCacheKey(context));
526         sb.append(PATH_SEPARATOR);
527         return getResourceUri(context, sb.toString(), withContextPath);
528     }
529 
530     /**
531      * Return a value used in the {cacheKey} part of a generated URL for a
532      * resource reference.
533      * <p>
534      * Caching in browsers normally works by having files served to them
535      * include last-modified and expiry-time http headers. Until the expiry
536      * time is reached, a browser will silently use its cached version. After
537      * the expiry time, it will send a "get if modified since {time}" message,
538      * where {time} is the last-modified header from the version it has cached.
539      * <p>
540      * Unfortunately this scheme only works well for resources represented as
541      * plain files on disk, where the webserver can easily and efficiently see
542      * the last-modified time of the resource file. When that query has to be
543      * processed by a servlet that doesn't scale well, even when it is possible
544      * to determine the resource's last-modified date from servlet code.
545      * <p>
546      * Fortunately, for the AddResource class a static resource is only ever
547      * accessed because a URL was embedded by this class in a dynamic page.
548      * This makes it possible to implement caching by instead marking every
549      * resource served with a very long expiry time, but forcing the URL that
550      * points to the resource to change whenever the old cached version becomes
551      * invalid; the browser effectively thinks it is fetching a different
552      * resource that it hasn't seen before. This is implemented by embedding
553      * a "cache key" in the generated URL.
554      * <p>
555      * Rather than using the actual modification date of a resource as the
556      * cache key, we simply use the webapp deployment time. This means that all
557      * data cached by browsers will become invalid after a webapp deploy (all
558      * the urls to the resources change). It also means that changes that occur
559      * to a resource <i>without</i> a webapp redeploy will not be seen by browsers.
560      */
561     protected long getCacheKey(FacesContext context)
562     {
563         // cache key is hold in application scope so it is recreated on redeploying the webapp.
564         Map applicationMap = context.getExternalContext().getApplicationMap();
565         Long cacheKey = (Long) applicationMap.get(RESOURCES_CACHE_KEY);
566         if (cacheKey == null)
567         {
568             cacheKey = new Long(System.currentTimeMillis() / 100000);
569             applicationMap.put(RESOURCES_CACHE_KEY, cacheKey);
570         }
571         return cacheKey.longValue();
572     }
573 
574     public boolean isResourceUri(ServletContext servletContext, HttpServletRequest request)
575     {
576         String path;
577         if (_contextPath != null)
578         {
579             path = _contextPath + getResourceVirtualPath(servletContext);
580         }
581         else
582         {
583             path = getResourceVirtualPath(servletContext);
584         }
585 
586         //fix for TOMAHAWK-660; to be sure this fix is backwards compatible, the
587         //encoded context-path is only used as a first option to check for the prefix
588         //if we're sure this works for all cases, we can directly return the first value
589         //and not double-check.
590         try
591         {
592             if(request.getRequestURI().startsWith(URLEncoder.encode(path,"UTF-8")))
593                 return true;
594         }
595         catch (UnsupportedEncodingException e)
596         {
597             log.error("Unsupported encoding UTF-8 used",e);
598 
599         }
600 
601         return request.getRequestURI().startsWith(path);
602     }
603 
604     private Class getClass(String className) throws ClassNotFoundException
605     {
606         Class clazz = ClassUtils.classForName(className);
607         validateResourceLoader(clazz);
608         return clazz;
609     }
610 
611     public void serveResource(ServletContext context, HttpServletRequest request,
612                               HttpServletResponse response) throws IOException
613     {
614         String pathInfo = request.getPathInfo();
615         String uri = request.getContextPath() + request.getServletPath()
616                 + (pathInfo == null ? "" : pathInfo);
617         String classNameStartsAfter = getResourceVirtualPath(context) + '/';
618 
619         int posStartClassName = uri.indexOf(classNameStartsAfter) + classNameStartsAfter.length();
620         int posEndClassName = uri.indexOf(PATH_SEPARATOR, posStartClassName);
621         String className = uri.substring(posStartClassName, posEndClassName);
622         int posEndCacheKey = uri.indexOf(PATH_SEPARATOR, posEndClassName + 1);
623         String resourceUri = null;
624         if (posEndCacheKey + 1 < uri.length())
625         {
626             resourceUri = uri.substring(posEndCacheKey + 1);
627         }
628 
629         try
630         {
631             Class resourceLoader = getClass(className);
632             validateResourceLoader(resourceLoader);
633             ((ResourceLoader) resourceLoader.newInstance()).serveResource(context, request,
634                     response, resourceUri);
635             // response.flushBuffer();
636             // Do not call response.flushBuffer buffer here. There is no point, as if there
637             // ever were header data to write, this would fail as we have already written
638             // the response body. The only point would be to flush the output stream, but
639             // that will happen anyway when the servlet container closes the socket.
640             //
641             // In addition, flushing could fail here; it appears that Microsoft IE
642             // hasthe habit of hard-closing its socket as soon as it has received a complete
643             // gif file, rather than letting the server close it. The container will hopefully
644             // silently ignore exceptions on close.  
645         }
646         catch (ClassNotFoundException e)
647         {
648             log.error("Could not find class for name: " + className, e);
649             sendError(response, HttpServletResponse.SC_NOT_FOUND,
650                     "Could not find resourceloader class for name: " + className);
651         }
652         catch (InstantiationException e)
653         {
654             log.error("Could not instantiate class for name: " + className, e);
655             sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
656                     "Could not instantiate resourceloader class for name: " + className);
657         }
658         catch (IllegalAccessException e)
659         {
660             log.error("Could not access class for name: " + className, e);
661             sendError(response, HttpServletResponse.SC_FORBIDDEN,
662                     "Could not access resourceloader class for name: " + className);
663         }
664         catch (IOException e)
665         {
666             logSend.error("Error while serving resource: " +resourceUri+", message : "+ e.getMessage(), e);
667             sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
668         }
669         catch (Throwable e)
670         {
671             log.error("Unknown error while serving resource: " +resourceUri+", message : "+ e.getMessage(), e);
672             sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
673         }
674     }
675 
676     protected void sendError(HttpServletResponse response, int errorCode, String errorText)
677         throws IOException
678     {
679         try
680         {
681             response.sendError(errorCode, errorText);
682         }
683         catch (IllegalStateException e)
684         {
685             logSend.error("Could not send error, maybe some data has already been sent.", e);
686         }
687     }
688 
689     public boolean hasHeaderBeginInfos(HttpServletRequest request)
690     {
691         throw new UnsupportedOperationException();
692     }
693 
694     /**
695      * Parses the response to mark the positions where code will be inserted
696      */
697     public void parseResponse(HttpServletRequest request, String bufferedResponse,
698                               HttpServletResponse response)
699     {
700         throw new UnsupportedOperationException();
701     }
702 
703     /**
704      * Writes the javascript code necessary for myfaces in every page, just befode the closing &lt;/body&gt; tag
705      */
706     public void writeMyFacesJavascriptBeforeBodyEnd(HttpServletRequest request,
707                                                     HttpServletResponse response) throws IOException
708     {
709         throw new UnsupportedOperationException();
710         /*
711         if (beforeBodyEndPosition >= 0)
712         {
713             String myFacesJavascript = (String) request.getAttribute("org.apache.myfaces.myFacesJavascript");
714             if(myFacesJavascript != null)
715             {
716                 originalResponse.insert(beforeBodyEndPosition, myFacesJavascript);
717             }
718             else
719             {
720                 log.warn("MyFaces special javascript could not be retrieved from request-map.");
721             }
722         }
723         */
724     }
725 
726     /**
727      * Add the resources to the &lt;head&gt; of the page.
728      * If the head tag is missing, but the &lt;body&gt; tag is present, the head tag is added.
729      * If both are missing, no resource is added.
730      *
731      * The ordering is such that the user header CSS & JS override the MyFaces' ones.
732      */
733     public void writeWithFullHeader(HttpServletRequest request,
734                                     HttpServletResponse response) throws IOException
735     {
736         throw new UnsupportedOperationException();
737     }
738 
739     /**
740      * Writes the response
741      */
742     public void writeResponse(HttpServletRequest request,
743                               HttpServletResponse response) throws IOException
744     {
745         throw new UnsupportedOperationException();
746     }
747 
748     private StylePositionedInfo getStyleInstance(FacesContext context, ResourceHandler resourceHandler)
749     {
750         return new StylePositionedInfo(getResourceUri(context, resourceHandler));
751     }
752 
753     private PositionedInfo getScriptInstance(FacesContext context, ResourceHandler resourceHandler,
754                                              boolean defer, boolean encodeUrl)
755     {
756         return new ScriptPositionedInfo(getResourceUri(context, resourceHandler), defer, encodeUrl);
757     }
758 
759     private StylePositionedInfo getStyleInstance(FacesContext context, String uri)
760     {
761         return new StylePositionedInfo(getResourceUri(context, uri));
762     }
763 
764     protected PositionedInfo getScriptInstance(FacesContext context, String uri, boolean defer)
765     {
766         return new ScriptPositionedInfo(getResourceUri(context, uri), defer);
767     }
768 
769     private PositionedInfo getInlineScriptInstance(String inlineScript)
770     {
771         return new InlineScriptPositionedInfo(inlineScript);
772     }
773 
774     private InlineStylePositionedInfo getInlineStyleInstance(String inlineStyle)
775     {
776         return new InlineStylePositionedInfo(inlineStyle);
777     }
778 
779     protected interface PositionedInfo
780     {
781     }
782 
783     protected interface WritablePositionedInfo extends PositionedInfo
784     {
785         public abstract void writePositionedInfo(FacesContext context, ResponseWriter writer)
786                 throws IOException;
787     }
788 
789     private abstract class AbstractResourceUri
790     {
791         protected final String _resourceUri;
792 
793         protected AbstractResourceUri(String resourceUri)
794         {
795             _resourceUri = resourceUri;
796         }
797 
798         public int hashCode()
799         {
800             return _resourceUri.hashCode();
801         }
802 
803         public boolean equals(Object obj)
804         {
805             if (obj == null)
806             {
807                 return false;
808             }
809             if (obj == this)
810             {
811                 return true;
812             }
813             if (obj instanceof AbstractResourceUri)
814             {
815                 AbstractResourceUri other = (AbstractResourceUri) obj;
816                 return _resourceUri.equals(other._resourceUri);
817             }
818             return false;
819         }
820 
821         protected String getResourceUri()
822         {
823             return _resourceUri;
824         }
825     }
826 
827     private class StylePositionedInfo extends AbstractResourceUri implements WritablePositionedInfo
828     {
829         protected StylePositionedInfo(String resourceUri)
830         {
831             super(resourceUri);
832         }
833 
834         public void writePositionedInfo(FacesContext context, ResponseWriter writer)
835                 throws IOException
836         {
837             // this will include the dojo stuff in default mode. Override it yourself by adding
838             // the DojoInitializer component before any other component (right after f:view)
839             DojoUtils.addMainInclude(context, null, null, DojoUtils.getDjConfigInstance(context));
840 
841             addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, DojoAddResource.class, "dojoaddresource.js");
842 
843             writer.startElement(HTML.SCRIPT_ELEM, null);
844             writer.writeAttribute(HTML.SCRIPT_LANGUAGE_ATTR, HTML.SCRIPT_LANGUAGE_JAVASCRIPT, null);
845             writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
846 
847             // writer.write("dojo.html.insertCssFile('");
848             writer.write("oamInsertCssFile('");
849             writer.write(context.getExternalContext().encodeActionURL(this.getResourceUri()));
850             writer.write("');");
851 
852             writer.endElement(HTML.SCRIPT_ELEM);
853         }
854     }
855 
856     private class ScriptPositionedInfo extends AbstractResourceUri implements
857             WritablePositionedInfo
858     {
859         protected final boolean _defer;
860         protected final boolean _encodeUrl;
861 
862         public ScriptPositionedInfo(String resourceUri, boolean defer)
863         {
864             this(resourceUri, defer, true);
865         }
866 
867         public ScriptPositionedInfo(String resourceUri, boolean defer, boolean encodeUrl)
868         {
869             super(resourceUri);
870             _defer = defer;
871             _encodeUrl = encodeUrl;
872         }
873 
874         public int hashCode()
875         {
876             return new HashCodeBuilder()
877                 .append(this.getResourceUri())
878                 .append(_defer)
879                 .append(_encodeUrl)
880                 .toHashCode();
881         }
882 
883         public boolean equals(Object obj)
884         {
885             if (super.equals(obj))
886             {
887                 if (obj instanceof ScriptPositionedInfo)
888                 {
889                     ScriptPositionedInfo other = (ScriptPositionedInfo) obj;
890                     return new EqualsBuilder()
891                         .append(_defer, other._defer)
892                         .append(_encodeUrl, other._encodeUrl)
893                         .isEquals();
894                 }
895             }
896             return false;
897         }
898 
899         public void writePositionedInfo(FacesContext context, ResponseWriter writer)
900                 throws IOException
901         {
902             writer.startElement(HTML.SCRIPT_ELEM, null);
903             writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
904             if (_encodeUrl)
905             {
906                 writer.writeAttribute(HTML.SRC_ATTR, context.getExternalContext().encodeActionURL(this.getResourceUri()), null);
907             }
908             else
909             {
910                 writer.writeAttribute(HTML.SRC_ATTR, this.getResourceUri(), null);
911             }
912 
913             if (_defer)
914             {
915                 writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_ELEM_DEFER_ATTR, "true", null);
916             }
917             writer.endElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_ELEM);
918         }
919     }
920 
921     private abstract class InlinePositionedInfo implements WritablePositionedInfo
922     {
923         private final String _inlineValue;
924 
925         protected InlinePositionedInfo(String inlineValue)
926         {
927             _inlineValue = inlineValue;
928         }
929 
930         public String getInlineValue()
931         {
932             return _inlineValue;
933         }
934 
935         public int hashCode()
936         {
937             return new HashCodeBuilder().append(_inlineValue).toHashCode();
938         }
939 
940         public boolean equals(Object obj)
941         {
942             if (obj == null)
943             {
944                 return false;
945             }
946             if (obj == this)
947             {
948                 return true;
949             }
950             if (obj instanceof InlinePositionedInfo)
951             {
952                 InlinePositionedInfo other = (InlinePositionedInfo) obj;
953                 return new EqualsBuilder().append(_inlineValue, other._inlineValue).isEquals();
954             }
955             return false;
956         }
957     }
958 
959     private class InlineScriptPositionedInfo extends InlinePositionedInfo
960     {
961         protected InlineScriptPositionedInfo(String inlineScript)
962         {
963             super(inlineScript);
964         }
965 
966         public void writePositionedInfo(FacesContext context, ResponseWriter writer)
967                 throws IOException
968         {
969             writer.startElement(HTML.SCRIPT_ELEM, null);
970             writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
971             writer.writeText(getInlineValue(), null);
972             writer.endElement(HTML.SCRIPT_ELEM);
973         }
974     }
975 
976     private class InlineStylePositionedInfo extends InlinePositionedInfo
977     {
978         protected InlineStylePositionedInfo(String inlineStyle)
979         {
980             super(inlineStyle);
981         }
982 
983         public void writePositionedInfo(FacesContext context, ResponseWriter writer)
984                 throws IOException
985         {
986             writer.startElement(HTML.STYLE_ELEM, null);
987             writer.writeAttribute(HTML.REL_ATTR, HTML.STYLESHEET_VALUE, null);
988             writer.writeAttribute(HTML.TYPE_ATTR, HTML.STYLE_TYPE_TEXT_CSS, null);
989             writer.writeText(getInlineValue(), null);
990             writer.endElement(HTML.STYLE_ELEM);
991         }
992     }
993 
994     public boolean requiresBuffer()
995     {
996         return false;
997     }
998 
999     public void responseStarted()
1000     {
1001     }
1002 
1003     public void responseFinished()
1004     {
1005     }
1006 
1007     public boolean hasHeaderBeginInfos()
1008     {
1009         return false;
1010     }
1011 
1012     private String getResourceVirtualPath(ServletContext servletContext)
1013     {
1014         if(resourceVirtualPath == null)
1015         {
1016             resourceVirtualPath = servletContext.getInitParameter(MyfacesConfig.INIT_PARAM_RESOURCE_VIRTUAL_PATH);
1017 
1018             if(resourceVirtualPath == null)
1019             {
1020                 resourceVirtualPath = MyfacesConfig.INIT_PARAM_RESOURCE_VIRTUAL_PATH_DEFAULT;
1021             }
1022         }
1023 
1024         return resourceVirtualPath;
1025     }
1026 }