1 /*
2 * $Id: RenderKitImpl.java,v 1.57 2008/02/07 08:56:00 edburns Exp $
3 */
4
5 /*
6 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
7 *
8 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
9 *
10 * The contents of this file are subject to the terms of either the GNU
11 * General Public License Version 2 only ("GPL") or the Common Development
12 * and Distribution License("CDDL") (collectively, the "License"). You
13 * may not use this file except in compliance with the License. You can obtain
14 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
15 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
16 * language governing permissions and limitations under the License.
17 *
18 * When distributing the software, include this License Header Notice in each
19 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
20 * Sun designates this particular file as subject to the "Classpath" exception
21 * as provided by Sun in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the License
23 * Header, with the fields enclosed by brackets [] replaced by your own
24 * identifying information: "Portions Copyrighted [year]
25 * [name of copyright owner]"
26 *
27 * Contributor(s):
28 *
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
40
41 // RenderKitImpl.java
42
43 package com.sun.faces.renderkit;
44
45 import javax.faces.context.FacesContext;
46 import javax.faces.context.ResponseStream;
47 import javax.faces.context.ResponseWriter;
48 import javax.faces.render.ClientBehaviorRenderer;
49 import javax.faces.render.RenderKit;
50 import javax.faces.render.Renderer;
51 import javax.faces.render.ResponseStateManager;
52
53 import java.io.IOException;
54 import java.io.OutputStream;
55 import java.io.Writer;
56 import java.util.HashMap;
57 import java.util.Iterator;
58 import java.util.Map;
59 import java.util.Set;
60 import java.util.Collections;
61 import java.util.logging.Logger;
62 import java.util.logging.Level;
63 import java.util.concurrent.ConcurrentHashMap;
64
65 import com.sun.faces.RIConstants;
66 import com.sun.faces.config.WebConfiguration;
67 import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.DisableUnicodeEscaping;
68 import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableJSStyleHiding;
69 import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableScriptInAttributeValue;
70 import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.PreferXHTMLContentType;
71 import com.sun.faces.renderkit.html_basic.HtmlResponseWriter;
72 import com.sun.faces.util.MessageUtils;
73 import com.sun.faces.util.Util;
74 import com.sun.faces.util.FacesLogger;
75
76 /**
77 * <B>RenderKitImpl</B> is a class ...
78 * <p/>
79 * <B>Lifetime And Scope</B> <P>
80 *
81 * @version $Id: RenderKitImpl.java,v 1.57 2008/02/07 08:56:00 edburns Exp $
82 */
83
84 public class RenderKitImpl extends RenderKit {
85
86 private static final Logger LOGGER = FacesLogger.RENDERKIT.getLogger();
87
88 private static final String[] SUPPORTED_CONTENT_TYPES_ARRAY =
89 new String[]{
90 RIConstants.HTML_CONTENT_TYPE,
91 RIConstants.XHTML_CONTENT_TYPE,
92 RIConstants.APPLICATION_XML_CONTENT_TYPE,
93 RIConstants.TEXT_XML_CONTENT_TYPE
94 };
95
96 private static final String SUPPORTED_CONTENT_TYPES =
97 RIConstants.HTML_CONTENT_TYPE + ','
98 + RIConstants.XHTML_CONTENT_TYPE + ','
99 + RIConstants.APPLICATION_XML_CONTENT_TYPE + ','
100 + RIConstants.TEXT_XML_CONTENT_TYPE;
101
102
103 /**
104 * Keys are String renderer family. Values are HashMaps. Nested
105 * HashMap keys are Strings for the rendererType, and values are the
106 * Renderer instances themselves.
107 */
108
109 private ConcurrentHashMap<String, HashMap<String, Renderer>> rendererFamilies =
110 new ConcurrentHashMap<String, HashMap<String, Renderer>>();
111
112 /**
113 * For Behavior Renderers:
114 * Keys are Strings for the behaviorRendererType, and values are the
115 * behaviorRenderer instances themselves.
116 */
117
118 private ConcurrentHashMap<String, ClientBehaviorRenderer> behaviorRenderers =
119 new ConcurrentHashMap<String, ClientBehaviorRenderer>();
120
121
122 private ResponseStateManager responseStateManager =
123 new ResponseStateManagerImpl();
124
125 private WebConfiguration webConfig;
126
127 public RenderKitImpl() {
128
129 FacesContext context = FacesContext.getCurrentInstance();
130 webConfig = WebConfiguration.getInstance(context.getExternalContext());
131
132 }
133
134 public void addRenderer(String family,
135 String rendererType,
136 Renderer renderer) {
137
138 Util.notNull("family", family);
139 Util.notNull("rendererType", rendererType);
140 Util.notNull("renderer", renderer);
141
142 HashMap<String,Renderer> renderers = rendererFamilies.get(family);
143 if (renderers == null) {
144 renderers = new HashMap<String,Renderer>();
145 rendererFamilies.put(family, renderers);
146 }
147
148 if (LOGGER.isLoggable(Level.FINE) && renderers.containsKey(rendererType)) {
149 LOGGER.log(Level.FINE,
150 "rendererType {0} has already been registered for family {1}. Replacing existing renderer class type {2} with {3}.",
151 new Object[] { rendererType, family, renderers.get(rendererType).getClass().getName(), renderer.getClass().getName() });
152 }
153 renderers.put(rendererType, renderer);
154
155 }
156
157
158 public Renderer getRenderer(String family, String rendererType) {
159
160 Util.notNull("family", family);
161 Util.notNull("rendererType", rendererType);
162
163 assert(rendererFamilies != null);
164
165 HashMap<String,Renderer> renderers = rendererFamilies.get(family);
166 return ((renderers != null) ? renderers.get(rendererType) : null);
167
168 }
169
170 public void addClientBehaviorRenderer(String behaviorRendererType,
171 ClientBehaviorRenderer behaviorRenderer) {
172
173 Util.notNull("behaviorRendererType", behaviorRendererType);
174 Util.notNull("behaviorRenderer", behaviorRenderer);
175
176 if (LOGGER.isLoggable(Level.FINE) && behaviorRenderers.containsKey(behaviorRendererType)) {
177 LOGGER.log(Level.FINE,
178 "behaviorRendererType {0} has already been registered. Replacing existing behavior renderer class type {1} with {2}.",
179 new Object[] { behaviorRendererType, behaviorRenderers.get(behaviorRendererType).getClass().getName(), behaviorRenderer.getClass().getName() });
180 }
181 behaviorRenderers.put(behaviorRendererType, behaviorRenderer);
182
183 }
184
185 public ClientBehaviorRenderer getClientBehaviorRenderer(String behaviorRendererType) {
186
187 Util.notNull("behaviorRendererType", behaviorRendererType);
188
189 return ((behaviorRenderers != null) ? behaviorRenderers.get(behaviorRendererType) : null);
190
191 }
192
193
194
195 public synchronized ResponseStateManager getResponseStateManager() {
196 if (responseStateManager == null) {
197 responseStateManager = new ResponseStateManagerImpl();
198 }
199 return responseStateManager;
200 }
201
202
203 public ResponseWriter createResponseWriter(Writer writer,
204 String desiredContentTypeList,
205 String characterEncoding) {
206 if (writer == null) {
207 return null;
208 }
209 String contentType = null;
210 boolean contentTypeNullFromResponse = false;
211 FacesContext context = FacesContext.getCurrentInstance();
212
213 // Step 1: Check the content type passed into this method
214 if (null != desiredContentTypeList) {
215 contentType = findMatch(
216 desiredContentTypeList,
217 SUPPORTED_CONTENT_TYPES_ARRAY);
218 }
219
220 // Step 2: Check the response content type
221 if (null == desiredContentTypeList) {
222 desiredContentTypeList =
223 context.getExternalContext().getResponseContentType();
224 if (null != desiredContentTypeList) {
225 contentType = findMatch(
226 desiredContentTypeList,
227 SUPPORTED_CONTENT_TYPES_ARRAY);
228 if (null == contentType) {
229 contentTypeNullFromResponse = true;
230 }
231 }
232 }
233
234 // Step 3: Check the Accept Header content type
235 // Evaluate the accept header in accordance with HTTP specification -
236 // Section 14.1
237 // Preconditions for this (1 or 2):
238 // 1. content type was not specified to begin with
239 // 2. an unsupported content type was retrieved from the response
240 if (null == desiredContentTypeList || contentTypeNullFromResponse) {
241 String[] typeArray =
242 context.getExternalContext().getRequestHeaderValuesMap().get("Accept");
243 if (typeArray.length > 0) {
244 StringBuffer buff = new StringBuffer();
245 buff.append(typeArray[0]);
246 for (int i = 1, len = typeArray.length; i < len; i++) {
247 buff.append(',');
248 buff.append(typeArray[i]);
249 }
250 desiredContentTypeList = buff.toString();
251 }
252
253 if (null != desiredContentTypeList) {
254 desiredContentTypeList =
255 RenderKitUtils.determineContentType(desiredContentTypeList,
256 SUPPORTED_CONTENT_TYPES,
257 ((preferXhtml())
258 ? RIConstants.XHTML_CONTENT_TYPE
259 : null));
260 if (null != desiredContentTypeList) {
261 contentType = findMatch(
262 desiredContentTypeList,
263 SUPPORTED_CONTENT_TYPES_ARRAY);
264 }
265 }
266 }
267
268 // Step 4: Default to text/html
269 if (contentType == null) {
270 if (null == desiredContentTypeList) {
271 contentType = getDefaultContentType();
272 } else {
273 String[] desiredContentTypes =
274 contentTypeSplit(desiredContentTypeList);
275 for (String desiredContentType : desiredContentTypes) {
276 if (RIConstants.ALL_MEDIA.equals(desiredContentType.trim())) {
277 contentType = getDefaultContentType();
278 }
279 }
280 }
281 }
282
283 if (null == contentType) {
284 throw new IllegalArgumentException(MessageUtils.getExceptionMessageString(
285 MessageUtils.CONTENT_TYPE_ERROR_MESSAGE_ID));
286 }
287
288 if (characterEncoding == null) {
289 characterEncoding = RIConstants.CHAR_ENCODING;
290 }
291
292 boolean scriptHiding = webConfig.isOptionEnabled(EnableJSStyleHiding);
293 boolean scriptInAttributes = webConfig.isOptionEnabled( EnableScriptInAttributeValue);
294 WebConfiguration.DisableUnicodeEscaping escaping =
295 WebConfiguration.DisableUnicodeEscaping.getByValue(
296 webConfig.getOptionValue(DisableUnicodeEscaping));
297 boolean isPartial = context.getPartialViewContext().isAjaxRequest();
298 return new HtmlResponseWriter(writer,
299 contentType,
300 characterEncoding,
301 scriptHiding,
302 scriptInAttributes,
303 escaping,
304 isPartial);
305 }
306
307
308 private boolean preferXhtml() {
309
310 return webConfig.isOptionEnabled(PreferXHTMLContentType);
311
312 }
313
314
315 private String getDefaultContentType() {
316
317 return ((preferXhtml())
318 ? RIConstants.XHTML_CONTENT_TYPE
319 : RIConstants.HTML_CONTENT_TYPE);
320
321 }
322
323
324 private String[] contentTypeSplit(String contentTypeString) {
325 String[] result = Util.split(contentTypeString, ",");
326 for (int i = 0; i < result.length; i++) {
327 int semicolon = result[i].indexOf(";");
328 if (-1 != semicolon) {
329 result[i] = result[i].substring(0, semicolon);
330 }
331 }
332 return result;
333 }
334
335 // Helper method that returns the content type if the desired content type is found in the
336 // array of supported types.
337
338 private String findMatch(String desiredContentTypeList,
339 String[] supportedTypes) {
340
341 String contentType = null;
342 String[] desiredTypes = contentTypeSplit(desiredContentTypeList);
343
344 // For each entry in the desiredTypes array, look for a match in
345 // the supportedTypes array
346 for (int i = 0, ilen = desiredTypes.length; i < ilen; i++) {
347 String curDesiredType = desiredTypes[i];
348 for (int j = 0, jlen = supportedTypes.length; j < jlen; j++) {
349 String curContentType = supportedTypes[j].trim();
350 if (curDesiredType.contains(curContentType)) {
351 if (curContentType.contains(RIConstants.HTML_CONTENT_TYPE)) {
352 contentType = RIConstants.HTML_CONTENT_TYPE;
353 } else
354 if (curContentType.contains(RIConstants.XHTML_CONTENT_TYPE) ||
355 curContentType.contains(RIConstants.APPLICATION_XML_CONTENT_TYPE) ||
356 curContentType.contains(RIConstants.TEXT_XML_CONTENT_TYPE)) {
357 contentType = RIConstants.XHTML_CONTENT_TYPE;
358 }
359 break;
360 }
361 }
362 if (null != contentType) {
363 break;
364 }
365 }
366 return contentType;
367 }
368
369
370 public ResponseStream createResponseStream(OutputStream out) {
371 final OutputStream output = out;
372 return new ResponseStream() {
373 public void write(int b) throws IOException {
374 output.write(b);
375 }
376
377
378 public void write(byte b[]) throws IOException {
379 output.write(b);
380 }
381
382
383 public void write(byte b[], int off, int len) throws IOException {
384 output.write(b, off, len);
385 }
386
387
388 public void flush() throws IOException {
389 output.flush();
390 }
391
392
393 public void close() throws IOException {
394 output.close();
395 }
396 };
397 }
398
399
400 /**
401 * @see javax.faces.render.RenderKit#getComponentFamilies()
402 */
403 @Override
404 public Iterator<String> getComponentFamilies() {
405
406 return rendererFamilies.keySet().iterator();
407
408 }
409
410
411 /**
412 * @see javax.faces.render.RenderKit#getRendererTypes(String)
413 */
414 @Override
415 public Iterator<String> getRendererTypes(String componentFamily) {
416
417 Map<String,Renderer> family = rendererFamilies.get(componentFamily);
418 if (family != null) {
419 return family.keySet().iterator();
420 } else {
421 Set<String> empty = Collections.emptySet();
422 return empty.iterator();
423 }
424
425 }
426
427 // The test for this class is in TestRenderKit.java
428
429 } // end of class RenderKitImpl
430