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