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