001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package org.apache.myfaces.tobago.ajax.api; 021 022 import org.apache.commons.lang.StringUtils; 023 import org.apache.commons.logging.Log; 024 import org.apache.commons.logging.LogFactory; 025 import org.apache.myfaces.tobago.context.ResourceManagerUtil; 026 import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes; 027 import org.apache.myfaces.tobago.renderkit.html.HtmlConstants; 028 import org.apache.myfaces.tobago.util.FastStringWriter; 029 import org.apache.myfaces.tobago.util.RequestUtils; 030 import org.apache.myfaces.tobago.util.ResponseUtils; 031 032 import javax.faces.FactoryFinder; 033 import javax.faces.application.StateManager; 034 import javax.faces.component.UIComponent; 035 import javax.faces.component.UIViewRoot; 036 import javax.faces.context.ExternalContext; 037 import javax.faces.context.FacesContext; 038 import javax.faces.context.ResponseWriter; 039 import javax.faces.event.PhaseEvent; 040 import javax.faces.event.PhaseId; 041 import javax.faces.event.PhaseListener; 042 import javax.faces.render.RenderKit; 043 import javax.faces.render.RenderKitFactory; 044 import javax.servlet.http.HttpServletResponse; 045 import java.io.IOException; 046 import java.io.PrintWriter; 047 import java.util.Map; 048 049 /** 050 * !! adapted copy of sandbox org.apache.myfaces.custom.ajax.api.AjaxPhaseListener !! 051 */ 052 public class AjaxPhaseListener implements PhaseListener { 053 private static final Log LOG = LogFactory.getLog(AjaxPhaseListener.class); 054 public static final String AJAX_COMPONENT_ID = "affectedAjaxComponent"; 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 public static final String TOBAGO_AJAX_STATUS_CODE = "org.apache.myfaces.tobago.StatusCode"; 060 061 public static Object getValueForComponent( 062 FacesContext facesContext, UIComponent component) { 063 String possibleClientId = component.getClientId(facesContext); 064 065 final Map requestParameterMap 066 = facesContext.getExternalContext().getRequestParameterMap(); 067 if (requestParameterMap.containsKey(possibleClientId)) { 068 return requestParameterMap.get(possibleClientId); 069 } else { 070 possibleClientId = (String) requestParameterMap.get(AJAX_COMPONENT_ID); 071 072 UIViewRoot root = facesContext.getViewRoot(); 073 074 UIComponent ajaxComponent = root.findComponent(possibleClientId); 075 076 if (ajaxComponent == component) { 077 return requestParameterMap.get(possibleClientId); 078 } else { 079 LOG.error("No value found for this component : " + possibleClientId); 080 return null; 081 } 082 } 083 } 084 085 086 public void afterPhase(PhaseEvent event) { 087 088 if (event.getPhaseId().getOrdinal() != PhaseId.APPLY_REQUEST_VALUES.getOrdinal()) { 089 return; 090 } 091 092 FacesContext facesContext = event.getFacesContext(); 093 094 final ExternalContext externalContext = facesContext.getExternalContext(); 095 if (externalContext.getRequestParameterMap().containsKey(AJAX_COMPONENT_ID)) { 096 try { 097 if (LOG.isDebugEnabled()) { 098 LOG.debug("AJAX: componentID found :" 099 + externalContext.getRequestParameterMap().get(AJAX_COMPONENT_ID)); 100 } 101 102 RequestUtils.ensureEncoding(facesContext); 103 ResponseUtils.ensureNoCacheHeader(externalContext); 104 final UIViewRoot viewRoot = facesContext.getViewRoot(); 105 FastStringWriter content = new FastStringWriter(1024 * 10); 106 RenderKitFactory renderFactory = (RenderKitFactory) 107 FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY); 108 RenderKit renderKit = renderFactory.getRenderKit( 109 facesContext, viewRoot.getRenderKitId()); 110 ResponseWriter contentWriter = renderKit.createResponseWriter(content, null, null); 111 facesContext.setResponseWriter(contentWriter); 112 113 AjaxUtils.processAjax(facesContext, viewRoot); 114 115 FastStringWriter jsfState = new FastStringWriter(); 116 ResponseWriter jsfStateWriter = contentWriter.cloneWithWriter(jsfState); 117 facesContext.setResponseWriter(jsfStateWriter); 118 119 final StateManager stateManager 120 = facesContext.getApplication().getStateManager(); 121 StateManager.SerializedView serializedView = stateManager.saveSerializedView(facesContext); 122 stateManager.writeState(facesContext, serializedView); 123 124 String stateValue = jsfState.toString(); 125 if (stateValue.length() > 0) { 126 // in case of inputSuggest jsfState.lenght is 0 127 // inputSuggest is a special case, because the form is not included in request. 128 contentWriter.startElement(HtmlConstants.SCRIPT, null); 129 contentWriter.writeAttribute(HtmlAttributes.TYPE, "text/javascript", null); 130 contentWriter.flush(); 131 contentWriter.write("Tobago.replaceJsfState(\""); 132 contentWriter.write(StringUtils.replace(StringUtils.replace(stateValue, "\"", "\\\""), "\n", "")); 133 contentWriter.write("\");"); 134 contentWriter.endElement(HtmlConstants.SCRIPT); 135 } 136 137 writeAjaxResponse(facesContext, content.toString()); 138 facesContext.responseComplete(); 139 140 } catch (IOException e) { 141 LOG.error("Exception while processing Ajax", e); 142 } 143 } 144 } 145 146 private void writeAjaxResponse(FacesContext facesContext, String content) 147 throws IOException { 148 149 ExternalContext externalContext = facesContext.getExternalContext(); 150 StringBuilder buf = new StringBuilder(content); 151 152 if (LOG.isDebugEnabled()) { 153 LOG.debug("Size of AjaxResponse:\n" + buf.length() 154 + " = 0x" + Integer.toHexString(buf.length())); 155 } 156 if (facesContext.getExternalContext().getRequestMap().containsKey(TOBAGO_AJAX_STATUS_CODE)) { 157 buf.insert(0, facesContext.getExternalContext().getRequestMap().get(TOBAGO_AJAX_STATUS_CODE)); 158 } else { 159 buf.insert(0, CODE_SUCCESS); 160 } 161 162 buf.insert(0, Integer.toHexString(buf.length()) + "\r\n"); 163 buf.append("\r\n" + 0 + "\r\n\r\n"); 164 165 //TODO: fix this to work in PortletRequest as well 166 if (externalContext.getResponse() instanceof HttpServletResponse) { 167 final HttpServletResponse httpServletResponse 168 = (HttpServletResponse) externalContext.getResponse(); 169 httpServletResponse.addHeader("Transfer-Encoding", "chunked"); 170 PrintWriter responseWriter = httpServletResponse.getWriter(); 171 // buf.delete(buf.indexOf("<"), buf.indexOf(">")+1); 172 responseWriter.print(buf.toString()); 173 responseWriter.flush(); 174 responseWriter.close(); 175 } 176 } 177 178 public void beforePhase(PhaseEvent event) { 179 180 if (event.getPhaseId().getOrdinal() != PhaseId.RENDER_RESPONSE.getOrdinal()) { 181 return; 182 } 183 184 try { 185 FacesContext facesContext = event.getFacesContext(); 186 final ExternalContext externalContext = facesContext.getExternalContext(); 187 final Map requestParameterMap = externalContext.getRequestParameterMap(); 188 if (requestParameterMap.containsKey(AJAX_COMPONENT_ID)) { 189 LOG.error("Ignoring AjaxRequest without valid component tree!"); 190 191 final String message = ResourceManagerUtil.getPropertyNotNull( 192 facesContext, "tobago", "tobago.ajax.response.error"); 193 194 writeAjaxResponse(facesContext, message); 195 196 facesContext.responseComplete(); 197 } 198 199 } catch (IOException e) { 200 LOG.error("Exception while processing Ajax", e); 201 } 202 } 203 204 public PhaseId getPhaseId() { 205 return PhaseId.ANY_PHASE; 206 //return PhaseId.RESTORE_VIEW; 207 // return PhaseId.INVOKE_APPLICATION; 208 // return PhaseId.APPLY_REQUEST_VALUES; 209 } 210 211 }