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