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.renderkit.html;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.io.Writer;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.logging.Level;
30  import java.util.logging.Logger;
31  import javax.faces.application.ProjectStage;
32  
33  import javax.faces.context.FacesContext;
34  import javax.faces.context.ResponseStream;
35  import javax.faces.context.ResponseWriter;
36  import javax.faces.render.ClientBehaviorRenderer;
37  import javax.faces.render.RenderKit;
38  import javax.faces.render.Renderer;
39  import javax.faces.render.RendererWrapper;
40  import javax.faces.render.ResponseStateManager;
41  
42  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFRenderKit;
43  import org.apache.myfaces.renderkit.LazyRenderKit;
44  import org.apache.myfaces.shared.config.MyfacesConfig;
45  import org.apache.myfaces.shared.renderkit.ContentTypeUtils;
46  import org.apache.myfaces.shared.renderkit.html.HtmlRendererUtils;
47  import org.apache.myfaces.shared.renderkit.html.HtmlResponseWriterImpl;
48  import org.apache.myfaces.shared.util.ClassUtils;
49  
50  /**
51   * @author Manfred Geiler (latest modification by $Author$)
52   * @version $Revision$ $Date$
53   */
54  @JSFRenderKit(renderKitId = "HTML_BASIC")
55  public class HtmlRenderKitImpl extends RenderKit implements LazyRenderKit
56  {
57      //private static final Log log = LogFactory.getLog(HtmlRenderKitImpl.class);
58      private static final Logger log = Logger.getLogger(HtmlRenderKitImpl.class.getName());
59  
60      // ~ Instance fields ----------------------------------------------------------------------------
61  
62      private Map<String, Map<String, Renderer>> _renderers;
63      private ResponseStateManager _responseStateManager;
64      //private Map<String,Set<String>> _families;
65      private Map<String, ClientBehaviorRenderer> _clientBehaviorRenderers;
66      
67      // ~ Constructors -------------------------------------------------------------------------------
68  
69      public HtmlRenderKitImpl()
70      {
71          _renderers = new ConcurrentHashMap<String, Map<String, Renderer>>(64, 0.75f, 1);
72          _responseStateManager = new HtmlResponseStateManager();
73          //_families = new HashMap<String, Set<String> >();
74          _clientBehaviorRenderers = new HashMap<String, ClientBehaviorRenderer>();
75      }
76  
77      // ~ Methods ------------------------------------------------------------------------------------
78  
79      @Override
80      public void addClientBehaviorRenderer(String type, ClientBehaviorRenderer renderer)
81      {
82          if (type == null)
83          {
84              throw new NullPointerException("client behavior renderer type must not be null");
85          }
86          if ( renderer == null)
87          {
88              throw new NullPointerException("client behavior renderer must not be null");
89          }
90          
91          _clientBehaviorRenderers.put(type, renderer);
92      }
93      
94      @Override
95      public ClientBehaviorRenderer getClientBehaviorRenderer(String type)
96      {
97          if (type == null)
98          {
99              throw new NullPointerException("client behavior renderer type must not be null");
100         }
101         
102         return _clientBehaviorRenderers.get(type);
103     }
104     
105     @Override
106     public Iterator<String> getClientBehaviorRendererTypes()
107     {
108         return _clientBehaviorRenderers.keySet().iterator();
109     }
110     
111     @Override
112     public Renderer getRenderer(String componentFamily, String rendererType)
113     {
114         if (componentFamily == null)
115         {
116             throw new NullPointerException("component family must not be null.");
117         }
118         if (rendererType == null)
119         {
120             throw new NullPointerException("renderer type must not be null.");
121         }
122         Map <String,Renderer> familyRendererMap = _renderers.get(componentFamily); 
123         Renderer renderer = null;
124         if (familyRendererMap != null)
125         {
126             renderer = familyRendererMap.get(rendererType);
127         }
128         if (renderer == null)
129         {
130             log.warning("Unsupported component-family/renderer-type: " + componentFamily + "/" + rendererType);
131         }
132         if (renderer instanceof LazyRendererWrapper)
133         {
134             renderer = ((LazyRendererWrapper)renderer).getWrapped();
135             familyRendererMap.put(rendererType, renderer);
136         }
137         return renderer;
138     }
139 
140     @Override
141     public void addRenderer(String componentFamily, String rendererType, Renderer renderer)
142     {
143         if (componentFamily == null)
144         {
145             log.severe("addRenderer: componentFamily = null is not allowed");
146             throw new NullPointerException("component family must not be null.");
147         }
148         if (rendererType == null)
149         {
150             log.severe("addRenderer: rendererType = null is not allowed");
151             throw new NullPointerException("renderer type must not be null.");
152         }
153         if (renderer == null)
154         {
155             log.severe("addRenderer: renderer = null is not allowed");
156             throw new NullPointerException("renderer must not be null.");
157         }
158         
159         _put(componentFamily, rendererType, renderer);
160 
161         if (log.isLoggable(Level.FINEST))
162         {
163             log.finest("add Renderer family = " + componentFamily + " rendererType = " + rendererType
164                     + " renderer class = " + renderer.getClass().getName());
165         }
166     }
167     
168     public void addRenderer(String componentFamily, String rendererType, String rendererClass)
169     {
170         if (componentFamily == null)
171         {
172             log.severe("addRenderer: componentFamily = null is not allowed");
173             throw new NullPointerException("component family must not be null.");
174         }
175         if (rendererType == null)
176         {
177             log.severe("addRenderer: rendererType = null is not allowed");
178             throw new NullPointerException("renderer type must not be null.");
179         }
180         if (rendererClass == null)
181         {
182             log.severe("addRenderer: renderer = null is not allowed");
183             throw new NullPointerException("renderer must not be null.");
184         }
185 
186         _put(componentFamily, rendererType, new LazyRendererWrapper(rendererClass));
187 
188         if (log.isLoggable(Level.FINEST))
189         {
190             log.finest("add Renderer family = " + componentFamily + " rendererType = " + rendererType
191                     + " renderer class = " + rendererClass);
192         }
193     }
194 
195     /**
196      * Put the renderer on the double map
197      * 
198      * @param componentFamily
199      * @param rendererType
200      * @param renderer
201      */
202     synchronized private void _put(String componentFamily, String rendererType, Renderer renderer)
203     {
204         Map <String,Renderer> familyRendererMap = _renderers.get(componentFamily);
205         if (familyRendererMap == null)
206         {
207             familyRendererMap = new ConcurrentHashMap<String, Renderer>(8, 0.75f, 1);
208             _renderers.put(componentFamily, familyRendererMap);
209         }
210         else
211         {
212             if (familyRendererMap.get(rendererType) != null)
213             {
214                 // this is not necessarily an error, but users do need to be
215                 // very careful about jar processing order when overriding
216                 // some component's renderer with an alternate renderer.
217                 log.fine("Overwriting renderer with family = " + componentFamily +
218                    " rendererType = " + rendererType +
219                    " renderer class = " + renderer.getClass().getName());
220             }
221         }
222         familyRendererMap.put(rendererType, renderer);
223     }
224 
225     @Override
226     public ResponseStateManager getResponseStateManager()
227     {
228         return _responseStateManager;
229     }
230     
231     /**
232      * @since JSF 2.0
233      */
234     @Override
235     public Iterator<String> getComponentFamilies()
236     {
237         //return _families.keySet().iterator();
238         return _renderers.keySet().iterator();
239     }
240     
241     /**
242      * @since JSF 2.0
243      */
244     @Override
245     public Iterator<String> getRendererTypes(String componentFamily)
246     {
247         //Return an Iterator over the renderer-type entries for the given component-family.
248         Map<String, Renderer> map = _renderers.get(componentFamily);
249         if (map != null)
250         {
251             return map.keySet().iterator();
252         }
253         /*
254         Set<String> rendererTypes = _families.get(componentFamily);
255         if(rendererTypes != null)
256         {
257             return rendererTypes.iterator();
258         }*/
259         //If the specified componentFamily is not known to this RenderKit implementation, return an empty Iterator
260         return Collections.<String>emptySet().iterator();
261         
262 
263 
264     }
265 
266     @Override
267     public ResponseWriter createResponseWriter(Writer writer, String contentTypeListString, String characterEncoding)
268     {
269         FacesContext facesContext = FacesContext.getCurrentInstance();
270         MyfacesConfig myfacesConfig = MyfacesConfig.getCurrentInstance(
271                 facesContext.getExternalContext());
272         String selectedContentType = null;
273         String writerContentType = null;
274         boolean isAjaxRequest = facesContext.getPartialViewContext().isAjaxRequest();
275         String contentTypeListStringFromAccept = null;
276 
277         // To detect the right contentType, we need to check if the request is an ajax request or not.
278         // If it is an ajax request, HTTP Accept header content type will be set for the ajax itself, which
279         // is application/xml or text/xml. In that case, there are two response writers
280         // (PartialResponseWriterImpl and HtmlResponseWriterImpl),
281         
282         //1. if there is a passed contentTypeListString, it takes precedence over accept header
283         if (contentTypeListString != null)
284         {
285             selectedContentType = ContentTypeUtils.chooseWriterContentType(contentTypeListString, 
286                     ContentTypeUtils.HTML_ALLOWED_CONTENT_TYPES, 
287                     isAjaxRequest ? ContentTypeUtils.AJAX_XHTML_ALLOWED_CONTENT_TYPES :
288                                     ContentTypeUtils.XHTML_ALLOWED_CONTENT_TYPES);
289         }
290 
291         //2. If no selectedContentType
292         //   try to derive it from accept header
293         if (selectedContentType == null)
294         {
295             contentTypeListStringFromAccept = 
296                 ContentTypeUtils.getContentTypeFromAcceptHeader(facesContext);
297             
298             if (contentTypeListStringFromAccept != null)
299             {
300                 selectedContentType = ContentTypeUtils.chooseWriterContentType(contentTypeListStringFromAccept,
301                         ContentTypeUtils.HTML_ALLOWED_CONTENT_TYPES, 
302                         isAjaxRequest ? ContentTypeUtils.AJAX_XHTML_ALLOWED_CONTENT_TYPES :
303                                         ContentTypeUtils.XHTML_ALLOWED_CONTENT_TYPES);
304             }
305         }
306 
307         //3. if no selectedContentType was derived, set default from the param 
308         if (selectedContentType == null)
309         {
310             if (contentTypeListString == null && contentTypeListStringFromAccept == null)
311             {
312                 //If no contentTypeList, return the default
313                 selectedContentType = myfacesConfig.getDefaultResponseWriterContentTypeMode();
314             }
315             else
316             {
317                 // If a contentTypeList was passed and we don't have direct matches, we still need
318                 // to check if */* is found and if that so return the default, otherwise throw
319                 // exception.
320                 if (contentTypeListString != null)
321                 {
322                     String[] contentTypes = ContentTypeUtils.splitContentTypeListString(contentTypeListString);
323                     if (ContentTypeUtils.containsContentType(ContentTypeUtils.ANY_CONTENT_TYPE, contentTypes))
324                     {
325                         selectedContentType = myfacesConfig.getDefaultResponseWriterContentTypeMode();
326                     }
327                 }
328                 
329                 if (selectedContentType == null)
330                 {
331                     if (contentTypeListStringFromAccept != null)
332                     {
333                         String[] contentTypes = ContentTypeUtils.splitContentTypeListString(
334                                 contentTypeListStringFromAccept);
335                         if (ContentTypeUtils.containsContentType(ContentTypeUtils.ANY_CONTENT_TYPE, contentTypes))
336                         {
337                             selectedContentType = myfacesConfig.getDefaultResponseWriterContentTypeMode();
338                         }
339                     }
340                     else if (isAjaxRequest)
341                     {
342                         // If is an ajax request, contentTypeListStringFromAccept == null and 
343                         // contentTypeListString != null, contentTypeListString should not be taken 
344                         // into account, because the final content type in this case is for PartialResponseWriter 
345                         // implementation. In this case rfc2616-sec14 takes precedence:
346                         // 
347                         // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
348                         // 14.1 Accept 
349                         // If no Accept header field is present, then it is assumed that the client 
350                         // accepts all media types.
351                         selectedContentType = myfacesConfig.getDefaultResponseWriterContentTypeMode();
352                     }
353                     if (selectedContentType == null)
354                     {
355                         // Note this case falls when contentTypeListStringFromAccept == null and 
356                         // contentTypeListString != null, but since this not an ajax request, 
357                         // contentTypeListString should be taken strictly and throw IllegalArgumentException
358                         throw new IllegalArgumentException(
359                                 "ContentTypeList does not contain a supported content type: "
360                                         + ((contentTypeListString != null) ? 
361                                                 contentTypeListString : contentTypeListStringFromAccept) );
362                     }
363                 }
364             }
365         }
366         if (isAjaxRequest)
367         {
368             // If HTTP Accept header has application/xml or text/xml, that does not means the writer
369             // content type mode should be set to application/xhtml+xml.
370             writerContentType = selectedContentType.indexOf(ContentTypeUtils.XHTML_CONTENT_TYPE) != -1 ?
371                     ContentTypeUtils.XHTML_CONTENT_TYPE : ContentTypeUtils.HTML_CONTENT_TYPE;
372         }
373         else
374         {
375             writerContentType = HtmlRendererUtils.isXHTMLContentType(selectedContentType) ? 
376                     ContentTypeUtils.XHTML_CONTENT_TYPE : ContentTypeUtils.HTML_CONTENT_TYPE;
377         }
378         
379         if (characterEncoding == null)
380         {
381             characterEncoding = HtmlRendererUtils.DEFAULT_CHAR_ENCODING;
382         }
383 
384         if (myfacesConfig.isEarlyFlushEnabled() &&
385                 facesContext.isProjectStage(ProjectStage.Production))
386         {
387             return new EarlyFlushHtmlResponseWriterImpl(writer, selectedContentType, characterEncoding, 
388                 myfacesConfig.isWrapScriptContentWithXmlCommentTag(),
389                         writerContentType);
390         }
391         else
392         {
393             return new HtmlResponseWriterImpl(writer, selectedContentType, characterEncoding, 
394                 myfacesConfig.isWrapScriptContentWithXmlCommentTag(),
395                         writerContentType);
396         }
397     }
398 
399     @Override
400     public ResponseStream createResponseStream(OutputStream outputStream)
401     {
402         return new MyFacesResponseStream(outputStream);
403     }
404 
405     private static class MyFacesResponseStream extends ResponseStream
406     {
407         private OutputStream output;
408 
409         public MyFacesResponseStream(OutputStream output)
410         {
411             this.output = output;
412         }
413 
414         @Override
415         public void write(int b) throws IOException
416         {
417             output.write(b);
418         }
419 
420         @Override
421         public void write(byte b[]) throws IOException
422         {
423             output.write(b);
424         }
425 
426         @Override
427         public void write(byte b[], int off, int len) throws IOException
428         {
429             output.write(b, off, len);
430         }
431 
432         @Override
433         public void flush() throws IOException
434         {
435             output.flush();
436         }
437 
438         @Override
439         public void close() throws IOException
440         {
441             output.close();
442         }
443     }
444     
445     private static class LazyRendererWrapper extends RendererWrapper
446     {
447         private String rendererClass;
448         private Renderer delegate;
449         
450         public LazyRendererWrapper(String rendererClass)
451         {
452             this.rendererClass = rendererClass;
453         }
454 
455         @Override
456         public Renderer getWrapped()
457         {
458             if (delegate == null)
459             {
460                 delegate = (Renderer) ClassUtils.newInstance(
461                     ClassUtils.simpleClassForName(rendererClass));
462             }
463             return delegate;
464         }
465     }
466 }