View Javadoc

1   package org.apache.myfaces.tobago.ajax.api;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
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        //noinspection unchecked
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          //noinspection unchecked
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       //LOG.error(" content = \"" + content.toString() + "\"");
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) {  // in case of CODE_RELOAD_REQUIRED page is 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     //TODO: fix this to work in PortletRequest as well
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 }