KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > myfaces > component > html > util > AddResource


1 /*
2  * Copyright 2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.myfaces.component.html.util;
17
18 import java.io.IOException JavaDoc;
19 import java.io.InputStream JavaDoc;
20 import java.io.OutputStream JavaDoc;
21 import java.io.PrintWriter JavaDoc;
22 import java.text.ParseException JavaDoc;
23 import java.text.SimpleDateFormat JavaDoc;
24 import java.util.Calendar JavaDoc;
25 import java.util.Date JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.LinkedHashSet JavaDoc;
28 import java.util.ResourceBundle JavaDoc;
29 import java.util.Set JavaDoc;
30
31 import javax.faces.context.FacesContext;
32 import javax.faces.context.ResponseWriter;
33 import javax.servlet.http.HttpServletRequest JavaDoc;
34 import javax.servlet.http.HttpServletResponse JavaDoc;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.apache.myfaces.renderkit.html.HTML;
39
40 /**
41  * This is a utility class to render link to resources used by custom components.
42  * Mostly used to avoid having to include <script SRC="..."></script>
43  * in the head of the pages before using a component.
44  *
45  * @author Sylvain Vieujot (latest modification by $Author: svieujot $)
46  * @version $Revision: 1.23 $ $Date: 2005/03/14 18:25:36 $
47  * $Log: AddResource.java,v $
48  * Revision 1.23 2005/03/14 18:25:36 svieujot
49  * ExtensionsFilter : Set last modified header (and us it in the URL instead of cacheKey).
50  *
51  * Revision 1.22 2005/03/14 17:49:32 svieujot
52  * Cleanup.
53  *
54  * Revision 1.21 2005/03/14 15:58:42 svieujot
55  * Added caching to the ExtensionsFilter
56  *
57  * Revision 1.20 2005/02/22 08:41:50 matzew
58  * Patch for the new tree component form Sean Schofield
59  *
60  * Revision 1.19 2005/02/16 00:50:37 oros
61  * SF issue #1043331: replaced all &nbsp; by the corresponding numeric entity &#160; so safari users will be happy, too, with MyFaces output
62  *
63  * Revision 1.18 2005/02/08 12:13:39 svieujot
64  * Bugfix : Serve xsl files with the text/xml content type.
65  *
66  * Revision 1.17 2004/12/27 04:11:11 mmarinschek
67  * Data Table stores the state of facets of children; script tag is rendered with type attribute instead of language attribute, popup works better as a column in a data table
68  *
69  * Revision 1.16 2004/12/24 14:11:50 svieujot
70  * Return resource relative mapped path when given null context.
71  *
72  * Revision 1.15 2004/12/17 13:19:10 mmarinschek
73  * new component jsValueChangeListener
74  *
75  * Revision 1.14 2004/12/06 01:02:02 svieujot
76  * Write the response in the log messages (mainly to debug problems due to filters order).
77  *
78  * Revision 1.13 2004/12/03 21:20:09 svieujot
79  * Add type="text/css" for inline styles.
80  *
81  * Revision 1.12 2004/12/03 21:15:21 svieujot
82  * define AdditionalHeaderInfoToRender.equals to prevent several include of the same header info.
83  *
84  * Revision 1.11 2004/12/03 20:50:52 svieujot
85  * Minor bugfix, and add <script ... defer="true"> capability.
86  *
87  * Revision 1.10 2004/12/03 20:27:51 svieujot
88  * Add capability to add inline style to the header.
89  *
90  * Revision 1.9 2004/12/02 22:26:23 svieujot
91  * Simplify the AddResource methods
92  *
93  * Revision 1.8 2004/12/02 11:53:27 svieujot
94  * Replace java 1.5 code by 1.4 version.
95  *
96  * Revision 1.7 2004/12/02 02:20:55 svieujot
97  * Bugfix : render the head elements in the same order as they were added (use a LinkedHashSet).
98  *
99  * Revision 1.6 2004/12/02 02:07:22 svieujot
100  * Make the Extensions filter work with resource hierarchies.
101  * A small concession had to be made though :
102  * The ExtensionsFilter must have the (additional) /faces/*
103  *
104  * Revision 1.5 2004/12/02 00:25:34 oros
105  * i18n issues
106  * some slight refactorings
107  *
108  * Revision 1.4 2004/12/01 22:12:51 svieujot
109  * Add xml and xsl content types.
110  *
111  * Revision 1.3 2004/12/01 20:29:22 svieujot
112  * Add javadoc.
113  *
114  * Revision 1.2 2004/12/01 20:25:10 svieujot
115  * Make the Extensions filter support css and image resources.
116  * Convert the popup calendar to use this new filter.
117  *
118  * Revision 1.1 2004/12/01 16:32:03 svieujot
119  * Convert the Multipart filter in an ExtensionsFilter that provides an additional facility to include resources in a page.
120  * Tested only with javascript resources right now, but should work fine with images too.
121  * Some work to do to include css resources.
122  * The popup component has been converted to use this new Filter.
123  *
124  */

125 public class AddResource {
126     private static final Log log = LogFactory.getLog(AddResource.class);
127
128     private static final String JavaDoc COMPONENTS_PACKAGE = "org.apache.myfaces.custom.";
129
130     private static final String JavaDoc RESOURCE_VIRTUAL_PATH = "/faces/myFacesExtensionResource";
131
132     private static final String JavaDoc ADDITIONAL_HEADER_INFO_REQUEST_ATTRUBITE_NAME = "myFacesHeaderResource2Render";
133
134     // Methodes to Add resources
135

136     /**
137      * Adds the given Javascript resource to the document body.
138      */

139     public static void addJavaScriptHere(Class JavaDoc componentClass, String JavaDoc resourceFileName, FacesContext context) throws IOException JavaDoc{
140         ResponseWriter writer = context.getResponseWriter();
141
142         writer.startElement(HTML.SCRIPT_ELEM,null);
143         writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR,HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT,null);
144         writer.writeURIAttribute(HTML.SRC_ATTR,
145                 getResourceMappedPath(componentClass, resourceFileName, context),
146                 null);
147         writer.endElement(HTML.SCRIPT_ELEM);
148     }
149
150     /**
151      * Adds the given Javascript resource to the document Header.
152      * If the script is already has already been referenced, it's added only once.
153      */

154     public static void addJavaScriptToHeader(Class JavaDoc componentClass, String JavaDoc resourceFileName, FacesContext context){
155         addJavaScriptToHeader(componentClass, resourceFileName, false, context);
156     }
157
158     /**
159      * Adds the given Javascript resource to the document Header.
160      * If the script is already has already been referenced, it's added only once.
161      */

162     public static void addJavaScriptToHeader(Class JavaDoc componentClass, String JavaDoc resourceFileName, boolean defer, FacesContext context){
163         AdditionalHeaderInfoToRender jsInfo =
164             new AdditionalHeaderInfoToRender(AdditionalHeaderInfoToRender.TYPE_JS, componentClass, resourceFileName, defer);
165         addAdditionalHeaderInfoToRender(context, jsInfo );
166     }
167
168     /**
169      * Adds the given Style Sheet to the document Header.
170      * If the style sheet is already has already been referenced, it's added only once.
171      */

172     public static void addStyleSheet(Class JavaDoc componentClass, String JavaDoc resourceFileName, FacesContext context){
173         AdditionalHeaderInfoToRender cssInfo =
174             new AdditionalHeaderInfoToRender(AdditionalHeaderInfoToRender.TYPE_CSS, componentClass, resourceFileName);
175         addAdditionalHeaderInfoToRender(context, cssInfo );
176     }
177
178     /**
179      * Adds the given Style Sheet to the document Header.
180      * If the style sheet is already has already been referenced, it's added only once.
181      */

182     public static void addInlineStyleToHeader(String JavaDoc inlineStyle, FacesContext context){
183         AdditionalHeaderInfoToRender cssInfo =
184             new AdditionalHeaderInfoToRender(AdditionalHeaderInfoToRender.TYPE_CSS_INLINE, inlineStyle);
185         addAdditionalHeaderInfoToRender(context, cssInfo );
186     }
187
188     /**
189      * Get the Path used to retrieve an internal resource for a custom component.
190      * Example : You can use this to initialize javascript scripts so that they know the path of some other resources
191      * (image, css, ...).
192      * <code>
193      * AddResource.addJavaScriptOncePerPage(HtmlCalendarRenderer.class, "popcalendar.js", facesContext,
194      * "jscalendarSetImageDirectory("+AddResource.getResourceMappedPath(HtmlCalendarRenderer.class, "DB", facesContext)+")");
195      * </code>
196      *
197      * Note : set context to null if you want the path after the application context path.
198      */

199     public static String JavaDoc getResourceMappedPath(Class JavaDoc componentClass, String JavaDoc resourceFileName, FacesContext context){
200         HttpServletRequest JavaDoc request = null;
201         if( context != null )
202             request = (HttpServletRequest JavaDoc)context.getExternalContext().getRequest();
203
204         return getResourceMappedPath(
205                 getComponentName(componentClass),
206                 resourceFileName,
207                 request);
208     }
209
210     private static String JavaDoc getResourceMappedPath(String JavaDoc componentName, String JavaDoc resourceFileName, HttpServletRequest JavaDoc request){
211         String JavaDoc contextPath = "";
212         if( request != null )
213             contextPath = request.getContextPath();
214         return contextPath+RESOURCE_VIRTUAL_PATH+"/"+componentName+'/'+getCacheKey()+'/'+resourceFileName;
215     }
216
217     private static long getCacheKey(){
218         return getLastModified();
219     }
220     
221     private static Date JavaDoc lastModified = null;
222     private static long getLastModified(){
223         if( lastModified == null ){
224             final String JavaDoc format = "yyyy-MM-dd HH:mm:ss Z"; // Must match the one used in the build file
225
final String JavaDoc bundleName = AddResource.class.getName();
226             ResourceBundle JavaDoc resources = ResourceBundle.getBundle( bundleName );
227             String JavaDoc sLastModified = resources.getString("lastModified");
228             try {
229                 lastModified = new SimpleDateFormat JavaDoc(format).parse( sLastModified );
230             } catch (ParseException JavaDoc e) {
231                 lastModified = new Date JavaDoc();
232                 log.error("Unparsable lastModified : "+sLastModified);
233             }
234         }
235         
236         return lastModified.getTime();
237     }
238
239     public static boolean isResourceMappedPath(HttpServletRequest JavaDoc request){
240         return request.getRequestURI().indexOf( RESOURCE_VIRTUAL_PATH ) != -1;
241     }
242
243     /**
244      * Decodes the path to return the requested componentName & resourceFileName
245      * String[0] == componentName
246      * String[1] == resourceFileName
247      */

248     private static String JavaDoc[] getResourceInfoFromPath(HttpServletRequest JavaDoc request){
249         String JavaDoc uri = request.getRequestURI();
250         String JavaDoc componentNameStartsAfter = RESOURCE_VIRTUAL_PATH+'/';
251
252         int posStartComponentName = uri.indexOf( componentNameStartsAfter )+componentNameStartsAfter.length();
253         int posEndComponentName = uri.indexOf("/", posStartComponentName);
254         String JavaDoc componentName = uri.substring(posStartComponentName, posEndComponentName);
255         
256         // Skip cache key
257
int posStartResourceFileName = uri.indexOf("/", posEndComponentName+1)+1;
258
259         String JavaDoc resourceFileName = uri.substring(posStartResourceFileName);
260
261         return new String JavaDoc[]{componentName, resourceFileName};
262     }
263
264     private static String JavaDoc getComponentName(Class JavaDoc componentClass){
265         String JavaDoc name = componentClass.getName();
266         if( ! name.startsWith(COMPONENTS_PACKAGE) ){
267             log.error("getComponentName called for non extension component : "+name+"\n"+
268                     "For security reasons, only components member of the "+COMPONENTS_PACKAGE+" are allowed to add ressources.");
269             return null;
270         }
271
272         name = name.substring( COMPONENTS_PACKAGE.length() );
273
274         return name;
275     }
276
277     static Class JavaDoc getComponent(String JavaDoc componentName) throws ClassNotFoundException JavaDoc{
278         return Class.forName( COMPONENTS_PACKAGE+componentName );
279     }
280
281     static private InputStream JavaDoc getResource(String JavaDoc componentName, String JavaDoc resourceFileName) {
282         Class JavaDoc component;
283         try {
284             component = getComponent(componentName);
285         } catch (ClassNotFoundException JavaDoc e) {
286             log.error("Class not found for component "+componentName);
287             return null;
288         }
289         while( resourceFileName.startsWith(".") || resourceFileName.startsWith("/") || resourceFileName.startsWith("\\") )
290                 resourceFileName = resourceFileName.substring(1);
291
292         return component.getResourceAsStream( "resource/"+resourceFileName );
293     }
294
295     static public void serveResource(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response) throws IOException JavaDoc{
296         String JavaDoc[] resourceInfo = getResourceInfoFromPath(request);
297         String JavaDoc componentName = resourceInfo[0];
298         String JavaDoc resourceFileName = resourceInfo[1];
299
300         log.debug("Serving resource "+resourceFileName+" for component "+componentName);
301
302         String JavaDoc lcResourceFileName = resourceFileName.toLowerCase();
303
304         if( lcResourceFileName.endsWith(".js") )
305             response.setContentType("text/javascript");
306         else if( lcResourceFileName.endsWith(".css") )
307             response.setContentType("text/css");
308         else if( lcResourceFileName.endsWith(".gif") )
309             response.setContentType("image/gif");
310         else if( lcResourceFileName.endsWith(".png") )
311             response.setContentType("image/png");
312         else if( lcResourceFileName.endsWith(".jpg") || lcResourceFileName.endsWith(".jpeg") )
313             response.setContentType("image/jpeg");
314         else if( lcResourceFileName.endsWith(".xml") || lcResourceFileName.endsWith(".xsl") )
315             response.setContentType("text/xml"); // XSL has to be served as XML.
316

317         InputStream JavaDoc is = getResource(componentName, resourceFileName);
318         if( is == null ){
319             throw new IOException JavaDoc("Unable to find resource "+resourceFileName+" for component "+componentName+
320                     ". Check that this file is available in the classpath in sub-directory /resource of the component-directory.");
321         }
322
323         response.setDateHeader("Last-Modified", getLastModified());
324         
325         // Set browser cache to a week.
326
// There is no risk, as the cache key is part of the URL.
327
Calendar JavaDoc expires = Calendar.getInstance();
328         expires.add(Calendar.DAY_OF_YEAR, 7);
329         response.setDateHeader("Expires", expires.getTimeInMillis());
330         
331         OutputStream JavaDoc os = response.getOutputStream();
332         int c;
333         while ((c = is.read()) != -1)
334             os.write(c);
335
336         os.close();
337     }
338
339     // Header stuffs
340

341     private static Set JavaDoc getAdditionalHeaderInfoToRender(HttpServletRequest JavaDoc request){
342         Set JavaDoc set = (Set JavaDoc) request.getAttribute(ADDITIONAL_HEADER_INFO_REQUEST_ATTRUBITE_NAME);
343         if( set == null ){
344             set = new LinkedHashSet JavaDoc();
345             request.setAttribute(ADDITIONAL_HEADER_INFO_REQUEST_ATTRUBITE_NAME, set);
346         }
347
348         return set;
349     }
350
351     private static void addAdditionalHeaderInfoToRender(FacesContext context, AdditionalHeaderInfoToRender info){
352         HttpServletRequest JavaDoc request = (HttpServletRequest JavaDoc) context.getExternalContext().getRequest();
353         Set JavaDoc set = getAdditionalHeaderInfoToRender( request );
354         set.add( info );
355     }
356
357     static public boolean hasAdditionalHeaderInfoToRender(HttpServletRequest JavaDoc request){
358         return request.getAttribute(ADDITIONAL_HEADER_INFO_REQUEST_ATTRUBITE_NAME) != null;
359     }
360
361     static public void writeWithFullHeader(HttpServletRequest JavaDoc request,
362             ExtensionsResponseWrapper responseWrapper,
363             HttpServletResponse JavaDoc response) throws IOException JavaDoc{
364
365         String JavaDoc originalResponse = responseWrapper.toString();
366
367         // try the most common cases first
368
boolean addHeaderTags = false;
369         int insertPosition = originalResponse.indexOf( "</head>" );
370
371         if( insertPosition < 0 ){
372             insertPosition = originalResponse.indexOf( "</HEAD>" );
373
374             if( insertPosition < 0 ){
375                 insertPosition = originalResponse.indexOf( "<body" );
376                 addHeaderTags = true;
377
378                 if( insertPosition < 0 ){
379                     insertPosition = originalResponse.indexOf( "<BODY" );
380                     addHeaderTags = true;
381
382                     if( insertPosition < 0 ){
383                        // the two most common cases head/HEAD and body/BODY did not work, so we try it with lowercase
384
String JavaDoc lowerCase = originalResponse.toLowerCase(response.getLocale());
385                        insertPosition = lowerCase.indexOf( "</head>" );
386
387                        if( insertPosition < 0 ){
388                            insertPosition = lowerCase.indexOf( "<body" );
389                            addHeaderTags = true;
390                        }
391                     }
392                 }
393             }
394
395             if( insertPosition < 0 ){
396                 log.warn("Response has no <head> or <body> tag:\n"+originalResponse);
397                 insertPosition = 0;
398             }
399         }
400
401         PrintWriter JavaDoc writer = response.getWriter();
402
403         if( insertPosition > 0 )
404             writer.write( originalResponse.substring(0, insertPosition) );
405         if( addHeaderTags )
406             writer.write("<head>");
407
408         for(Iterator JavaDoc i = getAdditionalHeaderInfoToRender(request).iterator(); i.hasNext() ;){
409             AdditionalHeaderInfoToRender headerInfo = (AdditionalHeaderInfoToRender) i.next();
410             writer.write( headerInfo.getString(request) );
411         }
412
413         if( addHeaderTags )
414             writer.write("</head>");
415
416         writer.write( originalResponse.substring(insertPosition) );
417     }
418
419     private static class AdditionalHeaderInfoToRender{
420         static final int TYPE_JS = 0;
421         static final int TYPE_CSS = 1;
422         static final int TYPE_CSS_INLINE = 2;
423
424         public int type;
425         public boolean deferJS = false;
426         public String JavaDoc componentName;
427         public String JavaDoc resourceFileName;
428         public String JavaDoc inlineText;
429
430         public AdditionalHeaderInfoToRender(int infoType, Class JavaDoc componentClass, String JavaDoc resourceFileName) {
431             this.type = infoType;
432             this.componentName = getComponentName(componentClass);
433             this.resourceFileName = resourceFileName;
434         }
435
436         public AdditionalHeaderInfoToRender(int infoType, Class JavaDoc componentClass, String JavaDoc resourceFileName, boolean defer) {
437             if( defer && infoType != TYPE_JS )
438                 log.error("Defer can only be used for scripts.");
439             this.type = infoType;
440             this.componentName = getComponentName(componentClass);
441             this.resourceFileName = resourceFileName;
442             this.deferJS = defer;
443         }
444
445         public AdditionalHeaderInfoToRender(int infoType, String JavaDoc inlineText) {
446             if( infoType != TYPE_CSS_INLINE )
447                 log.error("This constructor only supports TYPE_CSS_INLINE");
448             this.type = infoType;
449             this.inlineText = inlineText;
450         }
451
452         public int hashCode() {
453             return (componentName+((char)7)
454                     +resourceFileName+((char)7)
455                     +(type+""+((char)7))
456                     +(inlineText+""+((char)7))
457                     +(deferJS+"")).hashCode();
458         }
459
460         public boolean equals(Object JavaDoc obj) {
461             if( !(obj instanceof AdditionalHeaderInfoToRender) )
462                 return false;
463             AdditionalHeaderInfoToRender toCompare = (AdditionalHeaderInfoToRender) obj;
464
465             if( type != toCompare.type || deferJS != toCompare.deferJS )
466                 return false;
467
468             if( componentName == null ){
469                 if( toCompare.componentName != null )
470                     return false;
471             }else if( ! componentName.equals(toCompare.componentName) )
472                 return false;
473
474             if( resourceFileName == null ){
475                 if( toCompare.resourceFileName != null )
476                     return false;
477             }else if( ! resourceFileName.equals(toCompare.resourceFileName) )
478                 return false;
479
480             if( inlineText == null )
481                 return toCompare.inlineText == null;
482
483             return inlineText.equals(toCompare.inlineText);
484         }
485
486         public String JavaDoc getString(HttpServletRequest JavaDoc request){
487             switch (type) {
488            case TYPE_JS:
489                     return "<script "
490                         +"src=\""+getResourceMappedPath(componentName, resourceFileName, request)+"\" "
491                         +(deferJS ? "defer=\"true\" " : "")
492                         +"type=\"text/javascript\""
493                         +">"
494                         +"</script>\n";
495            case TYPE_CSS:
496                return "<link rel=\"stylesheet\" "
497                 +"href=\""+getResourceMappedPath(componentName, resourceFileName, request)+"\" "
498                 +"type=\"text/css\"/>\n";
499            case TYPE_CSS_INLINE:
500                return "<style type=\"text/css\">"+inlineText+"</style>\n";
501             default:
502                 log.warn("Unknown type:"+type);
503                 return "<link HREF=\""+"\"/>\n";
504             }
505         }
506     }
507 }
Popular Tags