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 org.apache.myfaces.tobago.compat.FacesUtils;
23 import org.apache.myfaces.tobago.component.Attributes;
24 import org.apache.myfaces.tobago.context.TobagoFacesContext;
25 import static org.apache.myfaces.tobago.lifecycle.TobagoLifecycle.FACES_MESSAGES_KEY;
26 import static org.apache.myfaces.tobago.lifecycle.TobagoLifecycle.VIEW_ROOT_KEY;
27 import org.apache.myfaces.tobago.util.ComponentUtil;
28 import org.apache.myfaces.tobago.util.EncodeAjaxCallback;
29 import org.apache.myfaces.tobago.util.RequestUtils;
30 import org.apache.myfaces.tobago.util.ResponseUtils;
31 import org.apache.myfaces.tobago.webapp.TobagoResponseJsonWriterImpl;
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);
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), 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 StringBuilder sb = new StringBuilder(contentType);
152 if (charset == null) {
153 charset = "UTF-8";
154 }
155 sb.append("; charset=");
156 sb.append(charset);
157 ResponseUtils.ensureContentTypeHeader(facesContext, sb.toString());
158 }
159
160 private void writeResponse(FacesContext facesContext, RenderKit renderKit, boolean reloadRequired)
161 throws IOException {
162
163 RequestUtils.ensureEncoding(facesContext);
164 ResponseUtils.ensureNoCacheHeader(facesContext);
165 UIComponent page = ComponentUtil.findPage(facesContext);
166 String charset;
167 if (page != null) {
168 charset = (String) page.getAttributes().get(Attributes.CHARSET);
169 } else {
170 charset = "UTF-8";
171 }
172 ensureContentTypeHeader(facesContext, charset, contentType);
173
174 PrintWriter writer = getPrintWriter(facesContext);
175 writer.write("{\n tobagoAjaxResponse: true,\n");
176 writer.write(" responseCode: ");
177 writer.write(reloadRequired ? Integer.toString(CODE_RELOAD_REQUIRED) : Integer.toString(CODE_SUCCESS));
178
179 if (!reloadRequired) {
180 writer.write(",\n");
181 writer.write(" jsfState: \"");
182 saveState(facesContext, renderKit);
183 writer.write("\"");
184 }
185
186 Map<String, UIComponent> ajaxComponents = AjaxUtils.getAjaxComponents(facesContext);
187 int i = 0;
188 for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
189 writer.write(",\n");
190 writer.write(" ajaxPart_");
191 writer.write(Integer.toString(i++));
192 writer.write(": ");
193
194 AjaxComponent component = (AjaxComponent) entry.getValue();
195 if (facesContext instanceof TobagoFacesContext) {
196 ((TobagoFacesContext) facesContext).setAjaxComponentId(entry.getKey());
197 }
198 renderComponent(facesContext, renderKit, entry.getKey(), component);
199 }
200
201 writer.write("\n}\n");
202 writer.flush();
203 writer.close();
204 }
205
206 private PrintWriter getPrintWriter(FacesContext facesContext) throws IOException {
207 ExternalContext externalContext = facesContext.getExternalContext();
208
209 if (externalContext.getResponse() instanceof HttpServletResponse) {
210 final HttpServletResponse httpServletResponse
211 = (HttpServletResponse) externalContext.getResponse();
212 return httpServletResponse.getWriter();
213 }
214 throw new IOException("No ResponseWriter found for ExternalContext " + externalContext);
215 }
216 }