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.shared.renderkit.html.util;
20  
21  import java.io.IOException;
22  import java.util.HashMap;
23  import java.util.Map;
24  
25  import javax.faces.application.Resource;
26  import javax.faces.component.UIComponent;
27  import javax.faces.context.FacesContext;
28  import javax.faces.context.ResponseWriter;
29  
30  import org.apache.myfaces.shared.config.MyfacesConfig;
31  import org.apache.myfaces.shared.renderkit.JSFAttr;
32  import org.apache.myfaces.shared.renderkit.html.HTML;
33  
34  /**
35   * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
36   * @version $Revision: 1461894 $ $Date: 2013-03-27 18:19:16 -0500 (Wed, 27 Mar 2013) $
37   * @since 4.0.1
38   */
39  public class ResourceUtils
40  {
41      public final static String JAVAX_FACES_LIBRARY_NAME = "javax.faces";
42      public final static String JSF_JS_RESOURCE_NAME = "jsf.js";
43  
44      public final static String MYFACES_JS_RESOURCE_NAME = "oamSubmit.js";
45      public final static String MYFACES_JS_RESOURCE_NAME_UNCOMPRESSED = "oamSubmit-uncompressed.js";
46      public final static String MYFACES_LIBRARY_NAME = "org.apache.myfaces";
47      private final static String RENDERED_MYFACES_JS = "org.apache.myfaces.RENDERED_MYFACES_JS";
48  
49      public final static String JSF_MYFACES_JSFJS_MINIMAL = "minimal";
50      public final static String JSF_MYFACES_JSFJS_MINIMAL_MODERN = "minimal-modern";
51      public final static String JSF_MYFACES_JSFJS_NORMAL = "normal";
52      
53      public final static String JSF_UNCOMPRESSED_JS_RESOURCE_NAME = "jsf-uncompressed.js";
54      public final static String JSF_MINIMAL_JS_RESOURCE_NAME = "jsf-minimal.js";
55      public final static String JSF_MINIMAL_MODERN_JS_RESOURCE_NAME = "jsf-minimal-modern.js";
56      
57      public final static String JSF_MYFACES_JSFJS_I18N = "jsf-i18n.js";
58      public final static String JSF_MYFACES_JSFJS_EXPERIMENTAL = "jsf-experimental.js";
59      public final static String JSF_MYFACES_JSFJS_LEGACY = "jsf-legacy.js";
60  
61      private final static String RENDERED_STYLESHEET_RESOURCES_SET = 
62          "org.apache.myfaces.RENDERED_STYLESHEET_RESOURCES_SET";
63      private final static String RENDERED_SCRIPT_RESOURCES_SET = "org.apache.myfaces.RENDERED_SCRIPT_RESOURCES_SET";
64      private final static String RENDERED_JSF_JS = "org.apache.myfaces.RENDERED_JSF_JS";
65      public final static String HEAD_TARGET = "head";
66      public final static String BODY_TARGET = "body";
67      public final static String FORM_TARGET = "form";
68  
69      public static final String JAVAX_FACES_OUTPUT_COMPONENT_TYPE = "javax.faces.Output";
70      public static final String JAVAX_FACES_TEXT_RENDERER_TYPE = "javax.faces.Text";
71      public static final String DEFAULT_SCRIPT_RENDERER_TYPE = "javax.faces.resource.Script";
72      public static final String DEFAULT_STYLESHEET_RENDERER_TYPE = "javax.faces.resource.Stylesheet";
73  
74      /**
75       * Return a set of already rendered resources by this renderer on the current
76       * request. 
77       * 
78       * @param facesContext
79       * @return
80       */
81      @SuppressWarnings("unchecked")
82      private static Map<String, Boolean> getRenderedStylesheetResources(FacesContext facesContext)
83      {
84          Map<String, Boolean> map = (Map<String, Boolean>) facesContext.getAttributes().get(
85                  RENDERED_STYLESHEET_RESOURCES_SET);
86          if (map == null)
87          {
88              map = new HashMap<String, Boolean>();
89              facesContext.getAttributes().put(RENDERED_STYLESHEET_RESOURCES_SET,map);
90          }
91          return map;
92      }
93      
94      /**
95       * Return a set of already rendered resources by this renderer on the current
96       * request. 
97       * 
98       * @param facesContext
99       * @return
100      */
101     @SuppressWarnings("unchecked")
102     private static Map<String, Boolean> getRenderedScriptResources(FacesContext facesContext)
103     {
104         Map<String, Boolean> map = (Map<String, Boolean>) facesContext.getAttributes().get(
105                 RENDERED_SCRIPT_RESOURCES_SET);
106         if (map == null)
107         {
108             map = new HashMap<String, Boolean>();
109             facesContext.getAttributes().put(RENDERED_SCRIPT_RESOURCES_SET,map);
110         }
111         return map;
112     }
113     
114     public static void markScriptAsRendered(FacesContext facesContext, String libraryName, String resourceName)
115     {
116         getRenderedScriptResources(facesContext).put(
117                 libraryName != null ? libraryName+'/'+resourceName : resourceName, Boolean.TRUE);
118         if (JAVAX_FACES_LIBRARY_NAME.equals(libraryName) &&
119             JSF_JS_RESOURCE_NAME.equals(resourceName))
120         {
121             // If we are calling this method, it is expected myfaces core is being used as runtime and note
122             // oamSubmit script is included inside jsf.js, so mark this one too.
123             getRenderedScriptResources(facesContext).put(
124                     MYFACES_LIBRARY_NAME+'/'+MYFACES_JS_RESOURCE_NAME, Boolean.TRUE);
125         }
126     }
127     
128     public static void markStylesheetAsRendered(FacesContext facesContext, String libraryName, String resourceName)
129     {
130         getRenderedStylesheetResources(facesContext).put(
131                 libraryName != null ? libraryName+'/'+resourceName : resourceName, Boolean.TRUE);
132     }
133     
134     public static boolean isRenderedScript(FacesContext facesContext, String libraryName, String resourceName)
135     {
136         return getRenderedScriptResources(facesContext).containsKey(
137                 libraryName != null ? libraryName+'/'+resourceName : resourceName);
138     }
139     
140     public static boolean isRenderedStylesheet(FacesContext facesContext, String libraryName, String resourceName)
141     {
142         return getRenderedStylesheetResources(facesContext).containsKey(
143                 libraryName != null ? libraryName+'/'+resourceName : resourceName);
144     }
145     
146     public static void writeScriptInline(FacesContext facesContext, ResponseWriter writer, String libraryName, 
147             String resourceName) throws IOException
148     {
149         if (!ResourceUtils.isRenderedScript(facesContext, libraryName, resourceName))
150         {
151             if (MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isRiImplAvailable())
152             {
153                 //Use more compatible way.
154                 UIComponent outputScript = facesContext.getApplication().
155                     createComponent(facesContext, JAVAX_FACES_OUTPUT_COMPONENT_TYPE, DEFAULT_SCRIPT_RENDERER_TYPE);
156                 outputScript.getAttributes().put(JSFAttr.NAME_ATTR, resourceName);
157                 outputScript.getAttributes().put(JSFAttr.LIBRARY_ATTR, libraryName);
158                 outputScript.encodeAll(facesContext);
159             }
160             else
161             {
162                 //Fast shortcut, don't create component instance and do what HtmlScriptRenderer do.
163                 Resource resource = facesContext.getApplication().getResourceHandler().createResource(
164                         resourceName, libraryName);
165                 markScriptAsRendered(facesContext, libraryName, resourceName);
166                 writer.startElement(HTML.SCRIPT_ELEM, null);
167                 writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT , null);
168                 writer.writeURIAttribute(HTML.SRC_ATTR, resource.getRequestPath(), null);
169                 writer.endElement(HTML.SCRIPT_ELEM);
170             }
171         }
172     }
173     
174     public static void renderDefaultJsfJsInlineIfNecessary(FacesContext facesContext, ResponseWriter writer) 
175         throws IOException
176     {
177         if (facesContext.getAttributes().containsKey(RENDERED_JSF_JS))
178         {
179             return;
180         }
181         
182         // Check first if we have lucky, we are using myfaces and the script has
183         // been previously rendered
184         if (isRenderedScript(facesContext, JAVAX_FACES_LIBRARY_NAME, JSF_JS_RESOURCE_NAME))
185         {
186             facesContext.getAttributes().put(RENDERED_JSF_JS, Boolean.TRUE);
187             return;
188         }
189 
190         // Check if this is an ajax request. If so, we don't need to include it, because that was
191         // already done and in the worst case, jsf script was already loaded on the page.
192         if (facesContext.getPartialViewContext() != null && 
193                 (facesContext.getPartialViewContext().isPartialRequest() ||
194                  facesContext.getPartialViewContext().isAjaxRequest() )
195             )
196         {
197             return;
198         }
199         
200 
201         // Here we have two cases:
202         // 1. The standard script could be put in another target (body or form).
203         // 2. RI is used so it is not used our set to check for rendered resources
204         //    and we don't have access to that one.
205         // 
206         // Check if we have the resource registered on UIViewRoot "body" or "form" target
207         // is not safe, because the page could not use h:body or h:form. 
208         // Anyway, there is no clear reason why page authors could do the first one, 
209         // but the second one could be.
210         //
211         // Finally we need to force render the script. If the script has been already rendered
212         // (RI used and rendered as a h:outputScript not relocated), nothing will happen because the default
213         // script renderer will check if the script has been rendered first.
214         if (MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isRiImplAvailable())
215         {
216             //Use more compatible way.
217             UIComponent outputScript = facesContext.getApplication().
218                     createComponent(facesContext, JAVAX_FACES_OUTPUT_COMPONENT_TYPE, DEFAULT_SCRIPT_RENDERER_TYPE);
219             outputScript.getAttributes().put(JSFAttr.NAME_ATTR, JSF_JS_RESOURCE_NAME);
220             outputScript.getAttributes().put(JSFAttr.LIBRARY_ATTR, JAVAX_FACES_LIBRARY_NAME);
221             outputScript.encodeAll(facesContext);
222         }
223         else
224         {
225             //Fast shortcut, don't create component instance and do what HtmlScriptRenderer do.
226             Resource resource = facesContext.getApplication().getResourceHandler().createResource(
227                     JSF_JS_RESOURCE_NAME, JAVAX_FACES_LIBRARY_NAME);
228             markScriptAsRendered(facesContext, JAVAX_FACES_LIBRARY_NAME, JSF_JS_RESOURCE_NAME);
229             writer.startElement(HTML.SCRIPT_ELEM, null);
230             writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
231             writer.writeURIAttribute(HTML.SRC_ATTR, resource.getRequestPath(), null);
232             writer.endElement(HTML.SCRIPT_ELEM);
233         }
234 
235         //mark as rendered
236         facesContext.getAttributes().put(RENDERED_JSF_JS, Boolean.TRUE);
237         return;
238     }
239 
240     public static void renderMyfacesJSInlineIfNecessary(FacesContext facesContext, ResponseWriter writer)
241         throws IOException
242     {
243         if (facesContext.getAttributes().containsKey(RENDERED_MYFACES_JS))
244         {
245             return;
246         }
247 
248 
249         //we only are allowed to do this on partial requests
250         //because on normal requests a static viewroot still could mean that a full page refresh is performed
251         //only in a ppr case this means we have the script already loaded and parsed
252         if (facesContext.getPartialViewContext() != null && 
253                 (facesContext.getPartialViewContext().isPartialRequest() ||
254                  facesContext.getPartialViewContext().isAjaxRequest() )
255             )
256         {
257             return;
258         }
259         // Check first if we have lucky, we are using myfaces and the script has
260         // been previously rendered
261         if (isRenderedScript(facesContext, MYFACES_LIBRARY_NAME, MYFACES_JS_RESOURCE_NAME))
262         {
263             facesContext.getAttributes().put(RENDERED_MYFACES_JS, Boolean.TRUE);
264             return;
265         }
266 
267         // Here we have two cases:
268         // 1. The standard script could be put in another target (body or form).
269         // 2. RI is used so it is not used our set to check for rendered resources
270         //    and we don't have access to that one.
271         //
272         // Check if we have the resource registered on UIViewRoot "body" or "form" target
273         // is not safe, because the page could not use h:body or h:form.
274         // Anyway, there is no clear reason why page authors could do the first one,
275         // but the second one could be.
276         //
277         // Finally we need to force render the script. If the script has been already rendered
278         // (RI used and rendered as a h:outputScript not relocated), nothing will happen because the default
279         // script renderer will check if the script has been rendered first.
280         if (MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isRiImplAvailable())
281         {
282             //Use more compatible way.
283             UIComponent outputScript = facesContext.getApplication().
284                     createComponent(facesContext, JAVAX_FACES_OUTPUT_COMPONENT_TYPE, DEFAULT_SCRIPT_RENDERER_TYPE);
285             outputScript.getAttributes().put(JSFAttr.NAME_ATTR, MYFACES_JS_RESOURCE_NAME);
286             outputScript.getAttributes().put(JSFAttr.LIBRARY_ATTR, MYFACES_LIBRARY_NAME);
287             outputScript.encodeAll(facesContext);
288         }
289         else
290         {
291             //Fast shortcut, don't create component instance and do what HtmlScriptRenderer do.
292             Resource resource = facesContext.getApplication().getResourceHandler().createResource(
293                     MYFACES_JS_RESOURCE_NAME, MYFACES_LIBRARY_NAME);
294             markScriptAsRendered(facesContext, MYFACES_LIBRARY_NAME, MYFACES_JS_RESOURCE_NAME);
295             writer.startElement(HTML.SCRIPT_ELEM, null);
296             writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
297             writer.writeURIAttribute(HTML.SRC_ATTR, resource.getRequestPath(), null);
298             writer.endElement(HTML.SCRIPT_ELEM);
299         }
300 
301         //mark as rendered
302         facesContext.getAttributes().put(RENDERED_MYFACES_JS, Boolean.TRUE);
303         return;
304     }
305 
306 }