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