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