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.config.TobagoConfig;
24  import org.apache.myfaces.tobago.internal.util.FacesContextUtils;
25  import org.apache.myfaces.tobago.internal.util.ResponseUtils;
26  import org.apache.myfaces.tobago.internal.util.StringUtils;
27  import org.apache.myfaces.tobago.internal.webapp.JsonResponseWriter;
28  import org.apache.myfaces.tobago.portlet.PortletUtils;
29  import org.apache.myfaces.tobago.util.ComponentUtils;
30  import org.apache.myfaces.tobago.util.EncodeAjaxCallback;
31  import org.apache.myfaces.tobago.util.RequestUtils;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import javax.faces.FactoryFinder;
36  import javax.faces.application.StateManager;
37  import javax.faces.component.UIComponent;
38  import javax.faces.component.UIViewRoot;
39  import javax.faces.context.FacesContext;
40  import javax.faces.context.ResponseWriter;
41  import javax.faces.render.RenderKit;
42  import javax.faces.render.RenderKitFactory;
43  import javax.portlet.MimeResponse;
44  import javax.servlet.http.HttpServletResponse;
45  import java.io.IOException;
46  import java.io.PrintWriter;
47  import java.util.Map;
48  
49  public class AjaxResponseRenderer {
50  
51    public static final int CODE_SUCCESS = 200;
52    /**
53     * @deprecated since 1.5. May do some stuff on the response like in ApplyRequestValuesCallback.
54     */
55    @Deprecated
56    public static final int CODE_NOT_MODIFIED = 304;
57    public static final int CODE_RELOAD_REQUIRED = 309;
58    public static final int CODE_ERROR = 500;
59    public static final String CONTENT_TYPE = "application/json";
60    private static final Logger LOG = LoggerFactory.getLogger(AjaxResponseRenderer.class);
61  
62    private EncodeAjaxCallback callback;
63  
64    public AjaxResponseRenderer() {
65      callback = new EncodeAjaxCallback();
66    }
67  
68    public void renderResponse(final FacesContext facesContext) throws IOException {
69      final UIViewRoot viewRoot = facesContext.getViewRoot();
70      final RenderKitFactory renderFactory
71          = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
72      final RenderKit renderKit = renderFactory.getRenderKit(facesContext, viewRoot.getRenderKitId());
73      writeResponse(facesContext, renderKit);
74    }
75  
76    private void renderComponent(
77        final FacesContext facesContext, final RenderKit renderKit, final String clientId, final UIComponent component)
78        throws IOException {
79      final PrintWriter writer = getPrintWriter(facesContext);
80      final JsonResponseWriter jsonWriter = getJsonResponseWriter(renderKit, writer);
81  
82      facesContext.setResponseWriter(jsonWriter);
83  
84      LOG.debug("write ajax response for {}", component);
85  
86      writer.write("{\n    \"ajaxId\": \"");
87      writer.write(clientId);
88      writer.write("\",\n");
89  
90      writer.write("    \"html\": \"");
91      facesContext.getViewRoot().invokeOnComponent(facesContext, clientId, callback);
92      writer.write("\",\n");
93  
94      writer.write("    \"responseCode\": ");
95      writer.write(Integer.toString(CODE_SUCCESS));
96  
97      final String javascript = jsonWriter.getJavascript();
98      if (StringUtils.isNotBlank(javascript)) {
99        writer.write(",\n");
100       writer.write("    \"script\": \"function() { ");
101       writer.write(javascript);
102       writer.write(" }\"");
103     }
104 
105     writer.write("\n  }");
106   }
107 
108   private void saveState(final FacesContext facesContext, final RenderKit renderKit) throws IOException {
109 
110     final ResponseWriter stateWriter = renderKit.createResponseWriter(getPrintWriter(facesContext), CONTENT_TYPE, null);
111     facesContext.setResponseWriter(stateWriter);
112 
113     final StateManager stateManager = facesContext.getApplication().getStateManager();
114     final Object serializedView = stateManager.saveView(facesContext);
115     stateManager.writeState(facesContext, serializedView);
116   }
117 
118   private static void ensureContentTypeHeader(
119       final FacesContext facesContext, String charset, final String contentType) {
120     final StringBuilder sb = new StringBuilder(contentType);
121     if (charset == null) {
122       charset = "UTF-8";
123     }
124     sb.append("; charset=");
125     sb.append(charset);
126     ResponseUtils.ensureContentTypeHeader(facesContext, sb.toString());
127     if (TobagoConfig.getInstance(facesContext).isSetNosniffHeader()) {
128       ResponseUtils.ensureNosniffHeader(facesContext);
129     }
130   }
131 
132   private void writeResponse(final FacesContext facesContext, final RenderKit renderKit)
133       throws IOException {
134 
135     RequestUtils.ensureEncoding(facesContext);
136     ResponseUtils.ensureNoCacheHeader(facesContext);
137     final UIComponent page = ComponentUtils.findPage(facesContext);
138     final String charset;
139     if (page != null) {  // in case of CODE_RELOAD_REQUIRED page is null
140       charset = (String) page.getAttributes().get(Attributes.CHARSET);
141     } else {
142       charset = "UTF-8";
143     }
144     ensureContentTypeHeader(facesContext, charset, CONTENT_TYPE);
145 
146     final PrintWriter writer = getPrintWriter(facesContext);
147     writer.write("{\n  \"tobagoAjaxResponse\": true,\n");
148     writer.write("  \"responseCode\": ");
149     writer.write(Integer.toString(CODE_SUCCESS));
150 
151     final Map<String, UIComponent> ajaxComponents = AjaxInternalUtils.getAjaxComponents(facesContext);
152     if (ajaxComponents != null) {
153       int i = 0;
154       for (final Map.Entry<String, UIComponent> entry : ajaxComponents.entrySet()) {
155         writer.write(",\n");
156         writer.write("  \"ajaxPart_");
157         writer.write(Integer.toString(i++));
158         writer.write("\": ");
159 
160         final UIComponent component = entry.getValue();
161         FacesContextUtils.setAjaxComponentId(facesContext, entry.getKey());
162         renderComponent(facesContext, renderKit, entry.getKey(), component);
163       }
164     }
165 
166     writer.write(",\n");
167     writer.write("  \"jsfState\": \"");
168     saveState(facesContext, renderKit);
169     writer.write("\"");
170 
171     writer.write("\n}\n");
172     writer.flush();
173     writer.close();
174   }
175 
176   private PrintWriter getPrintWriter(final FacesContext facesContext) throws IOException {
177     final Object response = facesContext.getExternalContext().getResponse();
178     if (response instanceof HttpServletResponse) {
179       return ((HttpServletResponse) response).getWriter();
180     } else if (PortletUtils.isPortletApiAvailable() && response instanceof MimeResponse) {
181       return ((MimeResponse) response).getWriter();
182     }
183     throw new IOException("No ResponseWriter found for response " + response);
184   }
185 
186   private JsonResponseWriter getJsonResponseWriter(final RenderKit renderKit, final PrintWriter writer) {
187 
188     final ResponseWriter newWriter = renderKit.createResponseWriter(writer, CONTENT_TYPE, null);
189     if (newWriter instanceof JsonResponseWriter) {
190       return (JsonResponseWriter) newWriter;
191     } else {
192       // with different RenderKit we got not the correct class
193       return new JsonResponseWriter(newWriter, CONTENT_TYPE, newWriter.getCharacterEncoding());
194     }
195   }
196 }