1 package org.apache.myfaces.tobago.ajax.api;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_CHARSET;
23 import org.apache.myfaces.tobago.util.ComponentUtil;
24 import static org.apache.myfaces.tobago.lifecycle.TobagoLifecycle.FACES_MESSAGES_KEY;
25 import static org.apache.myfaces.tobago.lifecycle.TobagoLifecycle.VIEW_ROOT_KEY;
26 import org.apache.myfaces.tobago.util.EncodeAjaxCallback;
27 import org.apache.myfaces.tobago.util.RequestUtils;
28 import org.apache.myfaces.tobago.util.ResponseUtils;
29 import org.apache.myfaces.tobago.compat.FacesUtils;
30 import org.apache.myfaces.tobago.webapp.TobagoResponseJsonWriterImpl;
31 import org.apache.myfaces.tobago.context.TobagoFacesContext;
32
33 import javax.faces.FactoryFinder;
34 import javax.faces.application.StateManager;
35 import javax.faces.component.UIComponent;
36 import javax.faces.component.UIViewRoot;
37 import javax.faces.context.ExternalContext;
38 import javax.faces.context.FacesContext;
39 import javax.faces.context.ResponseWriter;
40 import javax.faces.render.RenderKit;
41 import javax.faces.render.RenderKitFactory;
42 import javax.servlet.http.HttpServletResponse;
43 import java.io.IOException;
44 import java.io.PrintWriter;
45 import java.util.ArrayList;
46 import java.util.EmptyStackException;
47 import java.util.Iterator;
48 import java.util.List;
49 import java.util.Map;
50
51 public class AjaxResponseRenderer {
52 public static final int CODE_SUCCESS = 200;
53 public static final int CODE_NOT_MODIFIED = 304;
54 public static final int CODE_RELOAD_REQUIRED = 309;
55 public static final int CODE_ERROR = 500;
56
57 private static final Log LOG = LogFactory.getLog(AjaxResponseRenderer.class);
58
59 private EncodeAjaxCallback callback;
60 private String contentType = "application/json";
61
62 public AjaxResponseRenderer() {
63 callback = new EncodeAjaxCallback();
64 }
65
66 public void renderResponse(FacesContext facesContext) throws IOException {
67 final UIViewRoot viewRoot = facesContext.getViewRoot();
68 RenderKitFactory renderFactory = (RenderKitFactory)
69 FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
70 RenderKit renderKit = renderFactory.getRenderKit(
71 facesContext, viewRoot.getRenderKitId());
72
73 UIViewRoot incommingViewRoot = (UIViewRoot)
74 facesContext.getExternalContext().getRequestMap().get(VIEW_ROOT_KEY);
75 if (viewRoot != incommingViewRoot) {
76 if (LOG.isDebugEnabled()) {
77 LOG.debug("requesting full page reload because of navigation to "
78 + viewRoot.getViewId() + " from " + incommingViewRoot.getViewId());
79 }
80 Map sessionMap = facesContext.getExternalContext().getSessionMap();
81
82 sessionMap.put(VIEW_ROOT_KEY, viewRoot);
83 List<Object[]> messageHolders = new ArrayList<Object[]>();
84 Iterator clientIds = facesContext.getClientIdsWithMessages();
85 while (clientIds.hasNext()) {
86 String clientId = (String) clientIds.next();
87 Iterator messages = facesContext.getMessages(clientId);
88 while (messages.hasNext()) {
89 Object[] messageHolder = new Object[2];
90 messageHolder[0] = clientId;
91 messageHolder[1] = messages.next();
92 messageHolders.add(messageHolder);
93 }
94 }
95 if (!messageHolders.isEmpty()) {
96
97 sessionMap.put(FACES_MESSAGES_KEY, messageHolders);
98 }
99 writeResponse(facesContext, renderKit, true);
100 } else {
101 writeResponse(facesContext, renderKit, false);
102 }
103 }
104
105 private void renderComponent(FacesContext facesContext, RenderKit renderKit, String clientId,
106 AjaxComponent component) throws IOException {
107 PrintWriter writer = getPrintWriter(facesContext.getExternalContext());
108 ResponseWriter contentWriter = renderKit.createResponseWriter(writer, contentType, null);
109 facesContext.setResponseWriter(contentWriter);
110
111 if (LOG.isDebugEnabled()) {
112 LOG.debug("write ajax response for " + component);
113 }
114 writer.write("{\n ajaxId: \"");
115 writer.write(clientId);
116 writer.write("\",\n");
117
118 writer.write(" responseCode: ");
119 writer.write(Integer.toString(CODE_SUCCESS));
120 writer.write(",\n");
121
122 writer.write(" html: \"");
123 try {
124 FacesUtils.invokeOnComponent(facesContext, facesContext.getViewRoot(), clientId, callback);
125 } catch (EmptyStackException e) {
126
127 throw e;
128 }
129
130 if (contentWriter instanceof TobagoResponseJsonWriterImpl) {
131 writer.write("\",\n");
132 writer.write(" script: function() {\n");
133 writer.write(((TobagoResponseJsonWriterImpl) contentWriter).getJavascript());
134 writer.write("\n }");
135 }
136 writer.write("\n }");
137 }
138
139 private void saveState(FacesContext facesContext, RenderKit renderKit)
140 throws IOException {
141 ResponseWriter stateWriter =
142 renderKit.createResponseWriter(getPrintWriter(facesContext.getExternalContext()), contentType, null);
143 facesContext.setResponseWriter(stateWriter);
144
145 StateManager stateManager = facesContext.getApplication().getStateManager();
146 StateManager.SerializedView serializedView = stateManager.saveSerializedView(facesContext);
147 stateManager.writeState(facesContext, serializedView);
148 }
149
150 private static void ensureContentTypeHeader(FacesContext facesContext, String charset, String contentType) {
151
152 if (facesContext.getExternalContext().getResponse() instanceof HttpServletResponse) {
153 HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
154
155 StringBuilder sb = new StringBuilder(contentType);
156 if (charset == null) {
157 charset = "UTF-8";
158 }
159 sb.append("; charset=");
160 sb.append(charset);
161 response.setContentType(sb.toString());
162 }
163 }
164
165 private void writeResponse(FacesContext facesContext, RenderKit renderKit, boolean reloadRequired)
166 throws IOException {
167
168 ExternalContext externalContext = facesContext.getExternalContext();
169 RequestUtils.ensureEncoding(externalContext);
170 ResponseUtils.ensureNoCacheHeader(externalContext);
171 UIComponent page = ComponentUtil.findPage(facesContext);
172 String charset;
173 if (page != null) {
174 charset = (String) page.getAttributes().get(ATTR_CHARSET);
175 } else {
176 charset = "UTF-8";
177 }
178 ensureContentTypeHeader(facesContext, charset, contentType);
179
180 PrintWriter writer = getPrintWriter(externalContext);
181 writer.write("{\n tobagoAjaxResponse: true,\n");
182 writer.write(" responseCode: ");
183 writer.write(reloadRequired ? Integer.toString(CODE_RELOAD_REQUIRED) : Integer.toString(CODE_SUCCESS));
184
185 if (!reloadRequired) {
186 writer.write(",\n");
187 writer.write(" jsfState: \"");
188 saveState(facesContext, renderKit);
189 writer.write("\"");
190 }
191
192 Map<String, UIComponent> ajaxComponents = AjaxUtils.getAjaxComponents(facesContext);
193 int i = 0;
194 for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
195 writer.write(",\n");
196 writer.write(" ajaxPart_");
197 writer.write(Integer.toString(i++));
198 writer.write(": ");
199
200 AjaxComponent component = (AjaxComponent) entry.getValue();
201 if (facesContext instanceof TobagoFacesContext) {
202 ((TobagoFacesContext) facesContext).setAjaxComponentId(entry.getKey());
203 }
204 renderComponent(facesContext, renderKit, entry.getKey(), component);
205 }
206
207 writer.write("\n}\n");
208 writer.flush();
209 writer.close();
210 }
211
212 private PrintWriter getPrintWriter(ExternalContext externalContext) throws IOException {
213
214 if (externalContext.getResponse() instanceof HttpServletResponse) {
215 final HttpServletResponse httpServletResponse
216 = (HttpServletResponse) externalContext.getResponse();
217 return httpServletResponse.getWriter();
218 }
219 throw new IOException("No ResponseWriter found for ExternalContext " + externalContext);
220 }
221 }