001    package org.apache.myfaces.tobago.ajax.api;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import static org.apache.myfaces.tobago.lifecycle.TobagoLifecycle.FACES_MESSAGES_KEY;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.apache.commons.logging.Log;
024    import org.apache.commons.logging.LogFactory;
025    
026    import org.apache.myfaces.tobago.util.RequestUtils;
027    import org.apache.myfaces.tobago.util.ResponseUtils;
028    import org.apache.myfaces.tobago.renderkit.html.HtmlConstants;
029    import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
030    import static org.apache.myfaces.tobago.lifecycle.TobagoLifecycle.VIEW_ROOT_KEY;
031    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_CHARSET;
032    import org.apache.myfaces.tobago.component.ComponentUtil;
033    
034    import javax.faces.context.FacesContext;
035    import javax.faces.context.ResponseWriter;
036    import javax.faces.context.ExternalContext;
037    import javax.faces.component.UIViewRoot;
038    import javax.faces.component.UIComponent;
039    import javax.faces.render.RenderKitFactory;
040    import javax.faces.render.RenderKit;
041    import javax.faces.FactoryFinder;
042    import javax.faces.application.StateManager;
043    import javax.servlet.http.HttpServletResponse;
044    import java.io.StringWriter;
045    import java.io.IOException;
046    import java.io.PrintWriter;
047    import java.util.List;
048    import java.util.ArrayList;
049    import java.util.Map;
050    import java.util.Iterator;
051    
052    public class AjaxResponseRenderer {
053    
054      private static final Log LOG = LogFactory.getLog(AjaxResponseRenderer.class);
055    
056      public static final String CODE_SUCCESS = "<status code=\"200\"/>";
057      public static final String CODE_NOT_MODIFIED = "<status code=\"304\"/>";
058      public static final String CODE_RELOAD_REQUIRED = "<status code=\"309\"/>";
059    
060    
061      public void renderResponse(FacesContext facesContext) throws IOException {
062        final UIViewRoot viewRoot = facesContext.getViewRoot();
063        RenderKitFactory renderFactory = (RenderKitFactory)
064            FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
065        RenderKit renderKit = renderFactory.getRenderKit(
066            facesContext, viewRoot.getRenderKitId());
067    
068        UIViewRoot incommingViewRoot = (UIViewRoot)
069            facesContext.getExternalContext().getRequestMap().get(VIEW_ROOT_KEY);
070        if (viewRoot != incommingViewRoot) {
071          if (LOG.isDebugEnabled()) {
072            LOG.debug("requesting full page reload because of navigation to "
073                + viewRoot.getViewId() + " from " + incommingViewRoot.getViewId());
074          }
075          Map sessionMap = facesContext.getExternalContext().getSessionMap();
076          //noinspection unchecked
077          sessionMap.put(VIEW_ROOT_KEY, viewRoot);
078          List<Object[]> messageHolders = new ArrayList<Object[]>();
079          Iterator clientIds = facesContext.getClientIdsWithMessages();
080          while (clientIds.hasNext()) {
081            String clientId = (String) clientIds.next();
082            Iterator messages = facesContext.getMessages(clientId);
083            while (messages.hasNext()) {
084              Object[] messageHolder = new Object[2];
085              messageHolder[0] = clientId;
086              messageHolder[1] = messages.next();
087              messageHolders.add(messageHolder);
088            }
089          }
090          if (!messageHolders.isEmpty()) {
091            //noinspection unchecked
092            sessionMap.put(FACES_MESSAGES_KEY, messageHolders);
093          }
094          writeResponseReload(facesContext, renderKit);
095        } else {
096          List<StringWriter> responseParts = new ArrayList<StringWriter>();
097          List<UIComponent> ajaxComponents = AjaxUtils.getAjaxComponents(facesContext);
098    
099          for (int i = 0; i < 1; i++) {  // TODO render multiple components
100            StringWriter content = new StringWriter();
101            responseParts.add(content);
102            ResponseWriter contentWriter = renderKit.createResponseWriter(content, null, null);
103            facesContext.setResponseWriter(contentWriter);
104            AjaxComponent ajaxComponent = ((AjaxComponent) ajaxComponents.get(i));
105            if (LOG.isDebugEnabled()) {
106              LOG.debug("write ajax response for " + ajaxComponent);
107            }
108            ajaxComponent.encodeAjax(facesContext);
109          }
110    
111          String state = saveState(facesContext, renderKit);
112          writeResponse(facesContext, renderKit, responseParts, state);
113        }
114      }
115    
116      private void writeResponse(FacesContext facesContext, RenderKit renderKit,
117                                 List<StringWriter> parts, String state)
118          throws IOException {
119        writeResponse(facesContext, renderKit, CODE_SUCCESS, parts, state);
120      }
121    
122      private void writeResponseReload(FacesContext facesContext, RenderKit renderKit)
123          throws IOException {
124        writeResponse(facesContext, renderKit, CODE_RELOAD_REQUIRED, new ArrayList<StringWriter>(0), "");
125      }
126    
127    
128      private StringWriter writeState(FacesContext facesContext, RenderKit renderKit, String state)
129          throws IOException {
130        StringWriter jsfState = new StringWriter();
131        ResponseWriter stateWriter = renderKit.createResponseWriter(jsfState, null, null);
132        facesContext.setResponseWriter(stateWriter);
133        stateWriter.startElement(HtmlConstants.SCRIPT, null);
134        stateWriter.writeAttribute(HtmlAttributes.TYPE, "text/javascript", null);
135        stateWriter.flush();
136        stateWriter.write("Tobago.replaceJsfState(\"");
137        stateWriter.write(encodeState(state));
138        stateWriter.write("\");");
139        stateWriter.endElement(HtmlConstants.SCRIPT);
140        return jsfState;
141      }
142    
143      private String saveState(FacesContext facesContext, RenderKit renderKit)
144          throws IOException {
145        StringWriter jsfState = new StringWriter();
146        ResponseWriter stateWriter = renderKit.createResponseWriter(jsfState, null, null);
147        facesContext.setResponseWriter(stateWriter);
148    
149        StateManager stateManager = facesContext.getApplication().getStateManager();
150        StateManager.SerializedView serializedView
151            = stateManager.saveSerializedView(facesContext);
152        stateManager.writeState(facesContext, serializedView);
153        return jsfState.toString();
154      }
155    
156      private void writeResponse(FacesContext facesContext, RenderKit renderKit,
157                                 String responseCode, List<StringWriter> responseParts, String jsfState)
158          throws IOException {
159        ExternalContext externalContext = facesContext.getExternalContext();
160        RequestUtils.ensureEncoding(externalContext);
161        ResponseUtils.ensureNoCacheHeader(externalContext);
162        UIComponent page = ComponentUtil.findPage(facesContext, AjaxUtils.getAjaxComponents(facesContext).get(0));
163        String charset = (String) page.getAttributes().get(ATTR_CHARSET);
164        ResponseUtils.ensureContentTypeHeader(facesContext, charset);
165        StringBuilder buffer = new StringBuilder(responseCode);
166    
167        // add parts to response
168        for (StringWriter part : responseParts) {
169          // TODO surround by javascript parsable tokens
170    
171          // FIXME:
172          if (part.toString().startsWith(CODE_NOT_MODIFIED)
173              && buffer.toString().equals(responseCode)) {
174            // remove resopnseCode from buffer
175            buffer.setLength(0);
176          }
177          // /FIXME:
178    
179          buffer.append(part.toString());
180        }
181    
182        // add jsfState to response
183        if (jsfState.length() > 0) {
184          // in case of inputSuggest jsfState.lenght is 0
185          // inputSuggest is a special case, because the form is not included in request.
186          // TODO surround by javascript parsable token
187          buffer.append(writeState(facesContext, renderKit, jsfState));
188        }
189    
190        if (LOG.isTraceEnabled()) {
191          LOG.trace("\nresponse follows ##############################################################\n"
192              + buffer
193              + "\nend response    ##############################################################");
194        }
195    
196    
197        buffer.insert(0, Integer.toHexString(buffer.length()) + "\r\n");
198        buffer.append("\r\n" + 0 + "\r\n\r\n");
199    
200        //TODO: fix this to work in PortletRequest as well
201        if (externalContext.getResponse() instanceof HttpServletResponse) {
202          final HttpServletResponse httpServletResponse
203              = (HttpServletResponse) externalContext.getResponse();
204          httpServletResponse.addHeader("Transfer-Encoding", "chunked");
205          PrintWriter responseWriter = httpServletResponse.getWriter();
206          // buf.delete(buf.indexOf("<"), buf.indexOf(">")+1);
207          responseWriter.print(buffer.toString());
208          responseWriter.flush();
209          responseWriter.close();
210        }
211      }
212    
213      private String encodeState(String state) {
214        state = StringUtils.replace(state, "\"", "\\\"");
215        return StringUtils.replace(state, "\n", "");
216      }
217    }