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