001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.myfaces.tobago.ajax.api;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.apache.commons.logging.Log;
024    import org.apache.commons.logging.LogFactory;
025    import org.apache.myfaces.tobago.context.ResourceManagerUtil;
026    import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
027    import org.apache.myfaces.tobago.renderkit.html.HtmlConstants;
028    import org.apache.myfaces.tobago.util.FastStringWriter;
029    import org.apache.myfaces.tobago.util.RequestUtils;
030    import org.apache.myfaces.tobago.util.ResponseUtils;
031    
032    import javax.faces.FactoryFinder;
033    import javax.faces.application.StateManager;
034    import javax.faces.component.UIComponent;
035    import javax.faces.component.UIViewRoot;
036    import javax.faces.context.ExternalContext;
037    import javax.faces.context.FacesContext;
038    import javax.faces.context.ResponseWriter;
039    import javax.faces.event.PhaseEvent;
040    import javax.faces.event.PhaseId;
041    import javax.faces.event.PhaseListener;
042    import javax.faces.render.RenderKit;
043    import javax.faces.render.RenderKitFactory;
044    import javax.servlet.http.HttpServletResponse;
045    import java.io.IOException;
046    import java.io.PrintWriter;
047    import java.util.Map;
048    
049    /**
050     * !! adapted copy of sandbox org.apache.myfaces.custom.ajax.api.AjaxPhaseListener !!
051     */
052    public class AjaxPhaseListener implements PhaseListener {
053      private static final Log LOG = LogFactory.getLog(AjaxPhaseListener.class);
054      public static final String AJAX_COMPONENT_ID = "affectedAjaxComponent";
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      public static final String TOBAGO_AJAX_STATUS_CODE = "org.apache.myfaces.tobago.StatusCode";
060    
061      public static Object getValueForComponent(
062          FacesContext facesContext, UIComponent component) {
063        String possibleClientId = component.getClientId(facesContext);
064    
065        final Map requestParameterMap
066            = facesContext.getExternalContext().getRequestParameterMap();
067        if (requestParameterMap.containsKey(possibleClientId)) {
068          return requestParameterMap.get(possibleClientId);
069        } else {
070          possibleClientId = (String) requestParameterMap.get(AJAX_COMPONENT_ID);
071    
072          UIViewRoot root = facesContext.getViewRoot();
073    
074          UIComponent ajaxComponent = root.findComponent(possibleClientId);
075    
076          if (ajaxComponent == component) {
077            return requestParameterMap.get(possibleClientId);
078          } else {
079            LOG.error("No value found for this component : " + possibleClientId);
080            return null;
081          }
082        }
083      }
084    
085    
086      public void afterPhase(PhaseEvent event) {
087    
088        if (event.getPhaseId().getOrdinal() != PhaseId.APPLY_REQUEST_VALUES.getOrdinal()) {
089          return;
090        }
091    
092        FacesContext facesContext = event.getFacesContext();
093    
094        final ExternalContext externalContext = facesContext.getExternalContext();
095        if (externalContext.getRequestParameterMap().containsKey(AJAX_COMPONENT_ID)) {
096          try {
097            if (LOG.isDebugEnabled()) {
098              LOG.debug("AJAX: componentID found :"
099                  + externalContext.getRequestParameterMap().get(AJAX_COMPONENT_ID));
100            }
101    
102            RequestUtils.ensureEncoding(facesContext);
103            ResponseUtils.ensureNoCacheHeader(externalContext);
104            final UIViewRoot viewRoot = facesContext.getViewRoot();
105            FastStringWriter content = new FastStringWriter(1024 * 10);
106            RenderKitFactory renderFactory = (RenderKitFactory)
107                FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
108            RenderKit renderKit = renderFactory.getRenderKit(
109                facesContext, viewRoot.getRenderKitId());
110            ResponseWriter contentWriter = renderKit.createResponseWriter(content, null, null);
111            facesContext.setResponseWriter(contentWriter);
112    
113            AjaxUtils.processAjax(facesContext, viewRoot);
114    
115            FastStringWriter jsfState = new FastStringWriter();
116            ResponseWriter jsfStateWriter = contentWriter.cloneWithWriter(jsfState);
117            facesContext.setResponseWriter(jsfStateWriter);
118    
119            final StateManager stateManager
120                = facesContext.getApplication().getStateManager();
121            StateManager.SerializedView serializedView = stateManager.saveSerializedView(facesContext);
122            stateManager.writeState(facesContext, serializedView);
123    
124            String stateValue = jsfState.toString();
125            if (stateValue.length() > 0) {
126              // in case of inputSuggest jsfState.lenght is 0
127              // inputSuggest is a special case, because the form is not included in request.
128              contentWriter.startElement(HtmlConstants.SCRIPT, null);
129              contentWriter.writeAttribute(HtmlAttributes.TYPE, "text/javascript", null);
130              contentWriter.flush();
131              contentWriter.write("Tobago.replaceJsfState(\"");
132              contentWriter.write(StringUtils.replace(StringUtils.replace(stateValue, "\"", "\\\""), "\n", ""));
133              contentWriter.write("\");");
134              contentWriter.endElement(HtmlConstants.SCRIPT);
135            }
136    
137            writeAjaxResponse(facesContext, content.toString());
138            facesContext.responseComplete();
139    
140          } catch (IOException e) {
141            LOG.error("Exception while processing Ajax", e);
142          }
143        }
144      }
145    
146      private void writeAjaxResponse(FacesContext facesContext, String content)
147          throws IOException {
148    
149        ExternalContext externalContext = facesContext.getExternalContext();
150        StringBuilder buf = new StringBuilder(content);
151    
152        if (LOG.isDebugEnabled()) {
153          LOG.debug("Size of AjaxResponse:\n" + buf.length()
154              + " = 0x" + Integer.toHexString(buf.length()));
155        }
156        if (facesContext.getExternalContext().getRequestMap().containsKey(TOBAGO_AJAX_STATUS_CODE)) {
157          buf.insert(0, facesContext.getExternalContext().getRequestMap().get(TOBAGO_AJAX_STATUS_CODE));
158        } else {
159          buf.insert(0, CODE_SUCCESS);
160        }
161    
162        buf.insert(0, Integer.toHexString(buf.length()) + "\r\n");
163        buf.append("\r\n" + 0 + "\r\n\r\n");
164    
165        //TODO: fix this to work in PortletRequest as well
166        if (externalContext.getResponse() instanceof HttpServletResponse) {
167          final HttpServletResponse httpServletResponse
168              = (HttpServletResponse) externalContext.getResponse();
169          httpServletResponse.addHeader("Transfer-Encoding", "chunked");
170          PrintWriter responseWriter = httpServletResponse.getWriter();
171          // buf.delete(buf.indexOf("<"), buf.indexOf(">")+1);
172          responseWriter.print(buf.toString());
173          responseWriter.flush();
174          responseWriter.close();
175        }
176      }
177    
178      public void beforePhase(PhaseEvent event) {
179    
180        if (event.getPhaseId().getOrdinal() != PhaseId.RENDER_RESPONSE.getOrdinal()) {
181          return;
182        }
183    
184        try {
185          FacesContext facesContext = event.getFacesContext();
186          final ExternalContext externalContext = facesContext.getExternalContext();
187          final Map requestParameterMap = externalContext.getRequestParameterMap();
188          if (requestParameterMap.containsKey(AJAX_COMPONENT_ID)) {
189            LOG.error("Ignoring AjaxRequest without valid component tree!");
190    
191            final String message = ResourceManagerUtil.getPropertyNotNull(
192                facesContext, "tobago", "tobago.ajax.response.error");
193    
194            writeAjaxResponse(facesContext, message);
195    
196            facesContext.responseComplete();
197          }
198    
199        } catch (IOException e) {
200          LOG.error("Exception while processing Ajax", e);
201        }
202      }
203    
204      public PhaseId getPhaseId() {
205        return PhaseId.ANY_PHASE;
206        //return PhaseId.RESTORE_VIEW;
207    //        return PhaseId.INVOKE_APPLICATION;
208    //    return PhaseId.APPLY_REQUEST_VALUES;
209      }
210    
211    }