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 }