001 package org.apache.myfaces.tobago.ajax.api; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one or more 005 * contributor license agreements. See the NOTICE file distributed with 006 * this work for additional information regarding copyright ownership. 007 * The ASF licenses this file to You under the Apache License, Version 2.0 008 * (the "License"); you may not use this file except in compliance with 009 * the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020 import static org.apache.myfaces.tobago.lifecycle.TobagoLifecycle.FACES_MESSAGES_KEY; 021 022 import org.apache.commons.lang.StringUtils; 023 import org.apache.commons.logging.Log; 024 import org.apache.commons.logging.LogFactory; 025 026 import org.apache.myfaces.tobago.util.RequestUtils; 027 import org.apache.myfaces.tobago.util.ResponseUtils; 028 import org.apache.myfaces.tobago.renderkit.html.HtmlConstants; 029 import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes; 030 import static org.apache.myfaces.tobago.lifecycle.TobagoLifecycle.VIEW_ROOT_KEY; 031 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_CHARSET; 032 import org.apache.myfaces.tobago.component.ComponentUtil; 033 034 import javax.faces.context.FacesContext; 035 import javax.faces.context.ResponseWriter; 036 import javax.faces.context.ExternalContext; 037 import javax.faces.component.UIViewRoot; 038 import javax.faces.component.UIComponent; 039 import javax.faces.render.RenderKitFactory; 040 import javax.faces.render.RenderKit; 041 import javax.faces.FactoryFinder; 042 import javax.faces.application.StateManager; 043 import javax.servlet.http.HttpServletResponse; 044 import java.io.StringWriter; 045 import java.io.IOException; 046 import java.io.PrintWriter; 047 import java.util.List; 048 import java.util.ArrayList; 049 import java.util.Map; 050 import java.util.Iterator; 051 052 public class AjaxResponseRenderer { 053 054 private static final Log LOG = LogFactory.getLog(AjaxResponseRenderer.class); 055 056 public static final String CODE_SUCCESS = "<status code=\"200\"/>"; 057 public static final String CODE_NOT_MODIFIED = "<status code=\"304\"/>"; 058 public static final String CODE_RELOAD_REQUIRED = "<status code=\"309\"/>"; 059 060 061 public void renderResponse(FacesContext facesContext) throws IOException { 062 final UIViewRoot viewRoot = facesContext.getViewRoot(); 063 RenderKitFactory renderFactory = (RenderKitFactory) 064 FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY); 065 RenderKit renderKit = renderFactory.getRenderKit( 066 facesContext, viewRoot.getRenderKitId()); 067 068 UIViewRoot incommingViewRoot = (UIViewRoot) 069 facesContext.getExternalContext().getRequestMap().get(VIEW_ROOT_KEY); 070 if (viewRoot != incommingViewRoot) { 071 if (LOG.isDebugEnabled()) { 072 LOG.debug("requesting full page reload because of navigation to " 073 + viewRoot.getViewId() + " from " + incommingViewRoot.getViewId()); 074 } 075 Map sessionMap = facesContext.getExternalContext().getSessionMap(); 076 //noinspection unchecked 077 sessionMap.put(VIEW_ROOT_KEY, viewRoot); 078 List<Object[]> messageHolders = new ArrayList<Object[]>(); 079 Iterator clientIds = facesContext.getClientIdsWithMessages(); 080 while (clientIds.hasNext()) { 081 String clientId = (String) clientIds.next(); 082 Iterator messages = facesContext.getMessages(clientId); 083 while (messages.hasNext()) { 084 Object[] messageHolder = new Object[2]; 085 messageHolder[0] = clientId; 086 messageHolder[1] = messages.next(); 087 messageHolders.add(messageHolder); 088 } 089 } 090 if (!messageHolders.isEmpty()) { 091 //noinspection unchecked 092 sessionMap.put(FACES_MESSAGES_KEY, messageHolders); 093 } 094 writeResponseReload(facesContext, renderKit); 095 } else { 096 List<StringWriter> responseParts = new ArrayList<StringWriter>(); 097 List<UIComponent> ajaxComponents = AjaxUtils.getAjaxComponents(facesContext); 098 099 for (int i = 0; i < 1; i++) { // TODO render multiple components 100 StringWriter content = new StringWriter(); 101 responseParts.add(content); 102 ResponseWriter contentWriter = renderKit.createResponseWriter(content, null, null); 103 facesContext.setResponseWriter(contentWriter); 104 AjaxComponent ajaxComponent = ((AjaxComponent) ajaxComponents.get(i)); 105 if (LOG.isDebugEnabled()) { 106 LOG.debug("write ajax response for " + ajaxComponent); 107 } 108 ajaxComponent.encodeAjax(facesContext); 109 } 110 111 String state = saveState(facesContext, renderKit); 112 writeResponse(facesContext, renderKit, responseParts, state); 113 } 114 } 115 116 private void writeResponse(FacesContext facesContext, RenderKit renderKit, 117 List<StringWriter> parts, String state) 118 throws IOException { 119 writeResponse(facesContext, renderKit, CODE_SUCCESS, parts, state); 120 } 121 122 private void writeResponseReload(FacesContext facesContext, RenderKit renderKit) 123 throws IOException { 124 writeResponse(facesContext, renderKit, CODE_RELOAD_REQUIRED, new ArrayList<StringWriter>(0), ""); 125 } 126 127 128 private StringWriter writeState(FacesContext facesContext, RenderKit renderKit, String state) 129 throws IOException { 130 StringWriter jsfState = new StringWriter(); 131 ResponseWriter stateWriter = renderKit.createResponseWriter(jsfState, null, null); 132 facesContext.setResponseWriter(stateWriter); 133 stateWriter.startElement(HtmlConstants.SCRIPT, null); 134 stateWriter.writeAttribute(HtmlAttributes.TYPE, "text/javascript", null); 135 stateWriter.flush(); 136 stateWriter.write("Tobago.replaceJsfState(\""); 137 stateWriter.write(encodeState(state)); 138 stateWriter.write("\");"); 139 stateWriter.endElement(HtmlConstants.SCRIPT); 140 return jsfState; 141 } 142 143 private String saveState(FacesContext facesContext, RenderKit renderKit) 144 throws IOException { 145 StringWriter jsfState = new StringWriter(); 146 ResponseWriter stateWriter = renderKit.createResponseWriter(jsfState, null, null); 147 facesContext.setResponseWriter(stateWriter); 148 149 StateManager stateManager = facesContext.getApplication().getStateManager(); 150 StateManager.SerializedView serializedView 151 = stateManager.saveSerializedView(facesContext); 152 stateManager.writeState(facesContext, serializedView); 153 return jsfState.toString(); 154 } 155 156 private void writeResponse(FacesContext facesContext, RenderKit renderKit, 157 String responseCode, List<StringWriter> responseParts, String jsfState) 158 throws IOException { 159 ExternalContext externalContext = facesContext.getExternalContext(); 160 RequestUtils.ensureEncoding(externalContext); 161 ResponseUtils.ensureNoCacheHeader(externalContext); 162 UIComponent page = ComponentUtil.findPage(facesContext, AjaxUtils.getAjaxComponents(facesContext).get(0)); 163 String charset = (String) page.getAttributes().get(ATTR_CHARSET); 164 ResponseUtils.ensureContentTypeHeader(facesContext, charset); 165 StringBuilder buffer = new StringBuilder(responseCode); 166 167 // add parts to response 168 for (StringWriter part : responseParts) { 169 // TODO surround by javascript parsable tokens 170 171 // FIXME: 172 if (part.toString().startsWith(CODE_NOT_MODIFIED) 173 && buffer.toString().equals(responseCode)) { 174 // remove resopnseCode from buffer 175 buffer.setLength(0); 176 } 177 // /FIXME: 178 179 buffer.append(part.toString()); 180 } 181 182 // add jsfState to response 183 if (jsfState.length() > 0) { 184 // in case of inputSuggest jsfState.lenght is 0 185 // inputSuggest is a special case, because the form is not included in request. 186 // TODO surround by javascript parsable token 187 buffer.append(writeState(facesContext, renderKit, jsfState)); 188 } 189 190 if (LOG.isTraceEnabled()) { 191 LOG.trace("\nresponse follows ##############################################################\n" 192 + buffer 193 + "\nend response ##############################################################"); 194 } 195 196 197 buffer.insert(0, Integer.toHexString(buffer.length()) + "\r\n"); 198 buffer.append("\r\n" + 0 + "\r\n\r\n"); 199 200 //TODO: fix this to work in PortletRequest as well 201 if (externalContext.getResponse() instanceof HttpServletResponse) { 202 final HttpServletResponse httpServletResponse 203 = (HttpServletResponse) externalContext.getResponse(); 204 httpServletResponse.addHeader("Transfer-Encoding", "chunked"); 205 PrintWriter responseWriter = httpServletResponse.getWriter(); 206 // buf.delete(buf.indexOf("<"), buf.indexOf(">")+1); 207 responseWriter.print(buffer.toString()); 208 responseWriter.flush(); 209 responseWriter.close(); 210 } 211 } 212 213 private String encodeState(String state) { 214 state = StringUtils.replace(state, "\"", "\\\""); 215 return StringUtils.replace(state, "\n", ""); 216 } 217 }