| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.myfaces.renderkit.html; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.Writer; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import jakarta.faces.context.FacesContext; |
| import jakarta.faces.context.ResponseStream; |
| import jakarta.faces.context.ResponseWriter; |
| import jakarta.faces.render.ClientBehaviorRenderer; |
| import jakarta.faces.render.RenderKit; |
| import jakarta.faces.render.Renderer; |
| import jakarta.faces.render.RendererWrapper; |
| import jakarta.faces.render.ResponseStateManager; |
| |
| import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFRenderKit; |
| import org.apache.myfaces.renderkit.LazyRenderKit; |
| import org.apache.myfaces.config.MyfacesConfig; |
| import org.apache.myfaces.renderkit.ContentTypeUtils; |
| import org.apache.myfaces.core.api.shared.lang.Assert; |
| import org.apache.myfaces.util.lang.ClassUtils; |
| |
| /** |
| * @author Manfred Geiler (latest modification by $Author$) |
| * @version $Revision$ $Date$ |
| */ |
| @JSFRenderKit(renderKitId = "HTML_BASIC") |
| public class HtmlRenderKitImpl extends RenderKit implements LazyRenderKit |
| { |
| private static final Logger log = Logger.getLogger(HtmlRenderKitImpl.class.getName()); |
| |
| // ~ Instance fields ---------------------------------------------------------------------------- |
| |
| private Map<String, Map<String, Renderer>> _renderers; |
| private ResponseStateManager _responseStateManager; |
| private Map<String, ClientBehaviorRenderer> _clientBehaviorRenderers; |
| private MyfacesConfig myfacesConfig; |
| |
| // ~ Constructors ------------------------------------------------------------------------------- |
| |
| public HtmlRenderKitImpl() |
| { |
| _renderers = new ConcurrentHashMap<>(64, 0.75f, 1); |
| _responseStateManager = new HtmlResponseStateManager(); |
| _clientBehaviorRenderers = new HashMap<>(); |
| myfacesConfig = MyfacesConfig.getCurrentInstance(); |
| } |
| |
| // ~ Methods ------------------------------------------------------------------------------------ |
| |
| @Override |
| public void addClientBehaviorRenderer(String type, ClientBehaviorRenderer renderer) |
| { |
| Assert.notNull(type, "type"); |
| Assert.notNull(renderer, "renderer"); |
| |
| _clientBehaviorRenderers.put(type, renderer); |
| } |
| |
| @Override |
| public ClientBehaviorRenderer getClientBehaviorRenderer(String type) |
| { |
| Assert.notNull(type, "type"); |
| |
| return _clientBehaviorRenderers.get(type); |
| } |
| |
| @Override |
| public Iterator<String> getClientBehaviorRendererTypes() |
| { |
| return _clientBehaviorRenderers.keySet().iterator(); |
| } |
| |
| @Override |
| public Renderer getRenderer(String componentFamily, String rendererType) |
| { |
| Assert.notNull(componentFamily, "componentFamily"); |
| Assert.notNull(rendererType, "rendererType"); |
| |
| Map <String,Renderer> familyRendererMap = _renderers.get(componentFamily); |
| Renderer renderer = null; |
| if (familyRendererMap != null) |
| { |
| renderer = familyRendererMap.get(rendererType); |
| } |
| if (renderer == null) |
| { |
| log.warning("Unsupported component-family/renderer-type: " + componentFamily + '/' + rendererType); |
| } |
| if (renderer instanceof LazyRendererWrapper) |
| { |
| renderer = ((LazyRendererWrapper)renderer).getWrapped(); |
| familyRendererMap.put(rendererType, renderer); |
| } |
| return renderer; |
| } |
| |
| @Override |
| public void addRenderer(String componentFamily, String rendererType, Renderer renderer) |
| { |
| Assert.notNull(componentFamily, "componentFamily"); |
| Assert.notNull(rendererType, "rendererType"); |
| Assert.notNull(renderer, "renderer"); |
| |
| _put(componentFamily, rendererType, renderer); |
| |
| if (log.isLoggable(Level.FINEST)) |
| { |
| log.finest("add Renderer family = " + componentFamily + " rendererType = " + rendererType |
| + " renderer class = " + renderer.getClass().getName()); |
| } |
| } |
| |
| @Override |
| public void addRenderer(String componentFamily, String rendererType, String rendererClass) |
| { |
| Assert.notNull(componentFamily, "componentFamily"); |
| Assert.notNull(rendererType, "rendererType"); |
| Assert.notNull(rendererClass, "rendererClass"); |
| |
| _put(componentFamily, rendererType, new LazyRendererWrapper(rendererClass)); |
| |
| if (log.isLoggable(Level.FINEST)) |
| { |
| log.finest("add Renderer family = " + componentFamily + " rendererType = " + rendererType |
| + " renderer class = " + rendererClass); |
| } |
| } |
| |
| /** |
| * Put the renderer on the double map |
| * |
| * @param componentFamily |
| * @param rendererType |
| * @param renderer |
| */ |
| synchronized private void _put(String componentFamily, String rendererType, Renderer renderer) |
| { |
| Map <String,Renderer> familyRendererMap = _renderers.get(componentFamily); |
| if (familyRendererMap == null) |
| { |
| familyRendererMap = new ConcurrentHashMap<>(8, 0.75f, 1); |
| _renderers.put(componentFamily, familyRendererMap); |
| } |
| else |
| { |
| if (familyRendererMap.get(rendererType) != null) |
| { |
| // this is not necessarily an error, but users do need to be |
| // very careful about jar processing order when overriding |
| // some component's renderer with an alternate renderer. |
| log.fine("Overwriting renderer with family = " + componentFamily + |
| " rendererType = " + rendererType + |
| " renderer class = " + renderer.getClass().getName()); |
| } |
| } |
| familyRendererMap.put(rendererType, renderer); |
| } |
| |
| @Override |
| public ResponseStateManager getResponseStateManager() |
| { |
| return _responseStateManager; |
| } |
| |
| /** |
| * @since JSF 2.0 |
| */ |
| @Override |
| public Iterator<String> getComponentFamilies() |
| { |
| //return _families.keySet().iterator(); |
| return _renderers.keySet().iterator(); |
| } |
| |
| /** |
| * @since JSF 2.0 |
| */ |
| @Override |
| public Iterator<String> getRendererTypes(String componentFamily) |
| { |
| //Return an Iterator over the renderer-type entries for the given component-family. |
| Map<String, Renderer> map = _renderers.get(componentFamily); |
| if (map != null) |
| { |
| return map.keySet().iterator(); |
| } |
| |
| //If the specified componentFamily is not known to this RenderKit implementation, return an empty Iterator |
| return Collections.<String>emptySet().iterator(); |
| } |
| |
| @Override |
| public ResponseWriter createResponseWriter(Writer writer, String contentTypeListString, String characterEncoding) |
| { |
| FacesContext facesContext = FacesContext.getCurrentInstance(); |
| String selectedContentType = null; |
| String writerContentType = null; |
| boolean isAjaxRequest = facesContext.getPartialViewContext().isAjaxRequest(); |
| String contentTypeListStringFromAccept = null; |
| |
| // To detect the right contentType, we need to check if the request is an ajax request or not. |
| // If it is an ajax request, HTTP Accept header content type will be set for the ajax itself, which |
| // is application/xml or text/xml. In that case, there are two response writers |
| // (PartialResponseWriterImpl and HtmlResponseWriterImpl), |
| |
| //1. if there is a passed contentTypeListString, it takes precedence over accept header |
| if (contentTypeListString != null) |
| { |
| selectedContentType = ContentTypeUtils.chooseWriterContentType(contentTypeListString, |
| ContentTypeUtils.HTML_ALLOWED_CONTENT_TYPES, |
| isAjaxRequest ? ContentTypeUtils.AJAX_XHTML_ALLOWED_CONTENT_TYPES : |
| ContentTypeUtils.XHTML_ALLOWED_CONTENT_TYPES); |
| } |
| |
| //2. If no selectedContentType |
| // try to derive it from accept header |
| if (selectedContentType == null) |
| { |
| contentTypeListStringFromAccept = |
| ContentTypeUtils.getContentTypeFromAcceptHeader(facesContext); |
| |
| if (contentTypeListStringFromAccept != null) |
| { |
| selectedContentType = ContentTypeUtils.chooseWriterContentType(contentTypeListStringFromAccept, |
| ContentTypeUtils.HTML_ALLOWED_CONTENT_TYPES, |
| isAjaxRequest ? ContentTypeUtils.AJAX_XHTML_ALLOWED_CONTENT_TYPES : |
| ContentTypeUtils.XHTML_ALLOWED_CONTENT_TYPES); |
| } |
| } |
| |
| //3. if no selectedContentType was derived, set default from the param |
| if (selectedContentType == null) |
| { |
| if (contentTypeListString == null && contentTypeListStringFromAccept == null) |
| { |
| //If no contentTypeList, return the default |
| selectedContentType = myfacesConfig.getDefaultResponseWriterContentTypeMode(); |
| } |
| else |
| { |
| // If a contentTypeList was passed and we don't have direct matches, we still need |
| // to check if */* is found and if that so return the default, otherwise throw |
| // exception. |
| if (contentTypeListString != null) |
| { |
| String[] contentTypes = ContentTypeUtils.splitContentTypeListString(contentTypeListString); |
| if (ContentTypeUtils.containsContentType(ContentTypeUtils.ANY_CONTENT_TYPE, contentTypes)) |
| { |
| selectedContentType = myfacesConfig.getDefaultResponseWriterContentTypeMode(); |
| } |
| } |
| |
| if (selectedContentType == null) |
| { |
| if (contentTypeListStringFromAccept != null) |
| { |
| String[] contentTypes = ContentTypeUtils.splitContentTypeListString( |
| contentTypeListStringFromAccept); |
| if (ContentTypeUtils.containsContentType(ContentTypeUtils.ANY_CONTENT_TYPE, contentTypes)) |
| { |
| selectedContentType = myfacesConfig.getDefaultResponseWriterContentTypeMode(); |
| } |
| } |
| else if (isAjaxRequest) |
| { |
| // If is an ajax request, contentTypeListStringFromAccept == null and |
| // contentTypeListString != null, contentTypeListString should not be taken |
| // into account, because the final content type in this case is for PartialResponseWriter |
| // implementation. In this case rfc2616-sec14 takes precedence: |
| // |
| // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html |
| // 14.1 Accept |
| // If no Accept header field is present, then it is assumed that the client |
| // accepts all media types. |
| selectedContentType = myfacesConfig.getDefaultResponseWriterContentTypeMode(); |
| } |
| if (selectedContentType == null) |
| { |
| // Note this case falls when contentTypeListStringFromAccept == null and |
| // contentTypeListString != null, but since this not an ajax request, |
| // contentTypeListString should be taken strictly and throw IllegalArgumentException |
| throw new IllegalArgumentException( |
| "ContentTypeList does not contain a supported content type: " |
| + ((contentTypeListString != null) ? |
| contentTypeListString : contentTypeListStringFromAccept) ); |
| } |
| } |
| } |
| } |
| if (isAjaxRequest) |
| { |
| // If HTTP Accept header has application/xml or text/xml, that does not means the writer |
| // content type mode should be set to application/xhtml+xml. |
| writerContentType = selectedContentType.contains(ContentTypeUtils.XHTML_CONTENT_TYPE) ? |
| ContentTypeUtils.XHTML_CONTENT_TYPE : ContentTypeUtils.HTML_CONTENT_TYPE; |
| } |
| else |
| { |
| writerContentType = ContentTypeUtils.isXHTMLContentType(selectedContentType) ? |
| ContentTypeUtils.XHTML_CONTENT_TYPE : ContentTypeUtils.HTML_CONTENT_TYPE; |
| } |
| |
| if (characterEncoding == null) |
| { |
| characterEncoding = ContentTypeUtils.DEFAULT_CHAR_ENCODING; |
| } |
| |
| if (myfacesConfig.isEarlyFlushEnabled()) |
| { |
| return new EarlyFlushHtmlResponseWriterImpl(writer, selectedContentType, characterEncoding, |
| myfacesConfig.isWrapScriptContentWithXmlCommentTag(), writerContentType); |
| } |
| else |
| { |
| return new HtmlResponseWriterImpl(writer, selectedContentType, characterEncoding, |
| myfacesConfig.isWrapScriptContentWithXmlCommentTag(), writerContentType); |
| } |
| } |
| |
| @Override |
| public ResponseStream createResponseStream(OutputStream outputStream) |
| { |
| return new MyFacesResponseStream(outputStream); |
| } |
| |
| private static class MyFacesResponseStream extends ResponseStream |
| { |
| private OutputStream output; |
| |
| public MyFacesResponseStream(OutputStream output) |
| { |
| this.output = output; |
| } |
| |
| @Override |
| public void write(int b) throws IOException |
| { |
| output.write(b); |
| } |
| |
| @Override |
| public void write(byte b[]) throws IOException |
| { |
| output.write(b); |
| } |
| |
| @Override |
| public void write(byte b[], int off, int len) throws IOException |
| { |
| output.write(b, off, len); |
| } |
| |
| @Override |
| public void flush() throws IOException |
| { |
| output.flush(); |
| } |
| |
| @Override |
| public void close() throws IOException |
| { |
| output.close(); |
| } |
| } |
| |
| private static class LazyRendererWrapper extends RendererWrapper |
| { |
| private String rendererClass; |
| private Renderer delegate; |
| |
| public LazyRendererWrapper(String rendererClass) |
| { |
| this.rendererClass = rendererClass; |
| } |
| |
| @Override |
| public Renderer getWrapped() |
| { |
| if (delegate == null) |
| { |
| delegate = (Renderer) ClassUtils.newInstance( |
| ClassUtils.simpleClassForName(rendererClass)); |
| } |
| return delegate; |
| } |
| } |
| } |