View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.ajax;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.internal.util.FacesContextUtils;
24  import org.apache.myfaces.tobago.internal.util.ResponseUtils;
25  import org.apache.myfaces.tobago.internal.util.StringUtils;
26  import org.apache.myfaces.tobago.internal.webapp.JsonResponseWriter;
27  import org.apache.myfaces.tobago.portlet.PortletUtils;
28  import org.apache.myfaces.tobago.util.ComponentUtils;
29  import org.apache.myfaces.tobago.util.EncodeAjaxCallback;
30  import org.apache.myfaces.tobago.util.RequestUtils;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  import javax.faces.FactoryFinder;
35  import javax.faces.application.StateManager;
36  import javax.faces.component.UIComponent;
37  import javax.faces.component.UIViewRoot;
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.portlet.MimeResponse;
43  import javax.servlet.http.HttpServletResponse;
44  import java.io.IOException;
45  import java.io.PrintWriter;
46  import java.util.Map;
47  
48  public class AjaxResponseRenderer {
49  
50    public static final int CODE_SUCCESS = 200;
51    /**
52     * @deprecated since 1.5. May do some stuff on the response like in ApplyRequestValuesCallback.
53     */
54    @Deprecated
55    public static final int CODE_NOT_MODIFIED = 304;
56    public static final int CODE_RELOAD_REQUIRED = 309;
57    public static final int CODE_ERROR = 500;
58    public static final String CONTENT_TYPE = "application/json";
59    private static final Logger LOG = LoggerFactory.getLogger(AjaxResponseRenderer.class);
60  
61    private EncodeAjaxCallback callback;
62  
63    public AjaxResponseRenderer() {
64      callback = new EncodeAjaxCallback();
65    }
66  
67    public void renderResponse(FacesContext facesContext) throws IOException {
68      final UIViewRoot viewRoot = facesContext.getViewRoot();
69      RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
70      RenderKit renderKit = renderFactory.getRenderKit(facesContext, viewRoot.getRenderKitId());
71      writeResponse(facesContext, renderKit, AjaxNavigationState.isNavigation(facesContext));
72    }
73  
74    private void renderComponent(FacesContext facesContext, RenderKit renderKit, String clientId, UIComponent component)
75        throws IOException {
76      final PrintWriter writer = getPrintWriter(facesContext);
77      final JsonResponseWriter jsonWriter = getJsonResponseWriter(renderKit, writer);
78  
79      facesContext.setResponseWriter(jsonWriter);
80  
81      LOG.debug("write ajax response for {}", component);
82  
83      writer.write("{\n    \"ajaxId\": \"");
84      writer.write(clientId);
85      writer.write("\",\n");
86  
87      writer.write("    \"html\": \"");
88      ComponentUtils.invokeOnComponent(facesContext, facesContext.getViewRoot(), clientId, callback);
89      writer.write("\",\n");
90  
91      writer.write("    \"responseCode\": ");
92      writer.write(Integer.toString(CODE_SUCCESS));
93  
94      final String javascript = jsonWriter.getJavascript();
95      if (StringUtils.isNotBlank(javascript)) {
96        writer.write(",\n");
97        writer.write("    \"script\": \"function() { ");
98        writer.write(javascript);
99        writer.write(" }\"");
100     }
101 
102     writer.write("\n  }");
103   }
104 
105   private void saveState(FacesContext facesContext, RenderKit renderKit) throws IOException {
106 
107     final ResponseWriter stateWriter = renderKit.createResponseWriter(getPrintWriter(facesContext), CONTENT_TYPE, null);
108     facesContext.setResponseWriter(stateWriter);
109 
110     final StateManager stateManager = facesContext.getApplication().getStateManager();
111     final Object serializedView = stateManager.saveView(facesContext);
112     stateManager.writeState(facesContext, serializedView);
113   }
114 
115   private static void ensureContentTypeHeader(FacesContext facesContext, String charset, String contentType) {
116     StringBuilder sb = new StringBuilder(contentType);
117     if (charset == null) {
118       charset = "UTF-8";
119     }
120     sb.append("; charset=");
121     sb.append(charset);
122     ResponseUtils.ensureContentTypeHeader(facesContext, sb.toString());
123   }
124 
125   private void writeResponse(FacesContext facesContext, RenderKit renderKit, boolean reloadRequired)
126       throws IOException {
127 
128     RequestUtils.ensureEncoding(facesContext);
129     ResponseUtils.ensureNoCacheHeader(facesContext);
130     UIComponent page = ComponentUtils.findPage(facesContext);
131     String charset;
132     if (page != null) {  // in case of CODE_RELOAD_REQUIRED page is null
133       charset = (String) page.getAttributes().get(Attributes.CHARSET);
134     } else {
135       charset = "UTF-8";
136     }
137     ensureContentTypeHeader(facesContext, charset, CONTENT_TYPE);
138 
139     PrintWriter writer = getPrintWriter(facesContext);
140     writer.write("{\n  \"tobagoAjaxResponse\": true,\n");
141     writer.write("  \"responseCode\": ");
142     writer.write(reloadRequired ? Integer.toString(CODE_RELOAD_REQUIRED) : Integer.toString(CODE_SUCCESS));
143 
144     Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(facesContext);
145     if (!reloadRequired && ajaxComponents != null) {
146       int i = 0;
147       for (Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
148         writer.write(",\n");
149         writer.write("  \"ajaxPart_");
150         writer.write(Integer.toString(i++));
151         writer.write("\": ");
152 
153         UIComponent component = entry.getValue();
154         FacesContextUtils.setAjaxComponentId(facesContext, entry.getKey());
155         renderComponent(facesContext, renderKit, entry.getKey(), component);
156       }
157     }
158 
159     if (!reloadRequired) {
160       writer.write(",\n");
161       writer.write("  \"jsfState\": \"");
162       saveState(facesContext, renderKit);
163       writer.write("\"");
164     }
165 
166     writer.write("\n}\n");
167     writer.flush();
168     writer.close();
169   }
170 
171   private PrintWriter getPrintWriter(FacesContext facesContext) throws IOException {
172     final Object response = facesContext.getExternalContext().getResponse();
173     if (response instanceof HttpServletResponse) {
174       return ((HttpServletResponse) response).getWriter();
175     } else if (PortletUtils.isPortletApiAvailable() && response instanceof MimeResponse) {
176       return ((MimeResponse) response).getWriter();
177     }
178     throw new IOException("No ResponseWriter found for response " + response);
179   }
180 
181   private JsonResponseWriter getJsonResponseWriter(RenderKit renderKit, PrintWriter writer) {
182 
183     ResponseWriter newWriter = renderKit.createResponseWriter(writer, CONTENT_TYPE, null);
184     if (newWriter instanceof JsonResponseWriter) {
185       return (JsonResponseWriter) newWriter;
186     } else {
187       // with different RenderKit we got not the correct class
188       return new JsonResponseWriter(newWriter, CONTENT_TYPE, newWriter.getCharacterEncoding());
189     }
190   }
191 }