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.logging.Log;
023    import org.apache.commons.logging.LogFactory;
024    import org.apache.myfaces.tobago.component.ComponentUtil;
025    import org.apache.myfaces.tobago.component.UIViewRoot;
026    import org.apache.myfaces.tobago.util.ResponseUtils;
027    
028    import javax.faces.FactoryFinder;
029    import javax.faces.application.Application;
030    import javax.faces.application.ViewHandler;
031    import javax.faces.component.UIComponent;
032    import javax.faces.context.FacesContext;
033    import javax.faces.context.ResponseWriter;
034    import javax.faces.event.PhaseId;
035    import javax.faces.render.RenderKit;
036    import javax.faces.render.RenderKitFactory;
037    import javax.faces.render.Renderer;
038    import javax.servlet.http.HttpServletResponse;
039    import java.io.IOException;
040    import java.io.PrintWriter;
041    import java.util.Collections;
042    import java.util.HashMap;
043    import java.util.HashSet;
044    import java.util.Iterator;
045    import java.util.Map;
046    import java.util.Set;
047    import java.util.StringTokenizer;
048    
049    public class AjaxUtils {
050    
051      private static final Log LOG = LogFactory.getLog(AjaxUtils.class);
052    
053      public static final String AJAX_COMPONENTS = AjaxUtils.class.getName() + ".AJAX_COMPONENTS";
054    
055      public static boolean isAjaxRequest(FacesContext facesContext) {
056        Map parameterMap = facesContext.getExternalContext().getRequestParameterMap();
057        String ajaxComponentIds = (String) parameterMap.get(AjaxPhaseListener.AJAX_COMPONENT_ID);
058        return ajaxComponentIds != null;
059      }
060    
061      public static void checkParamValidity(FacesContext facesContext, UIComponent uiComponent, Class compClass) {
062        if (facesContext == null) {
063          throw new NullPointerException("facesContext may not be null");
064        }
065        if (uiComponent == null) {
066          throw new NullPointerException("uiComponent may not be null");
067        }
068        //if (compClass != null && !(compClass.isAssignableFrom(uiComponent.getClass())))
069        // why isAssignableFrom with additional getClass method call if isInstance does the same?
070        if (compClass != null && !(compClass.isInstance(uiComponent))) {
071          throw new IllegalArgumentException("uiComponent : "
072              + uiComponent.getClass().getName() + " is not instance of "
073              + compClass.getName() + " as it should be");
074        }
075      }
076    
077      public static void encodeAjaxComponent(FacesContext facesContext, UIComponent component) throws IOException {
078        if (facesContext == null) {
079          throw new NullPointerException("facesContext");
080        }
081        if (!component.isRendered()) {
082          return;
083        }
084        Renderer renderer = ComponentUtil.getRenderer(facesContext, component);
085        if (renderer != null && renderer instanceof AjaxRenderer) {
086          ((AjaxRenderer) renderer).encodeAjax(facesContext, component);
087        }
088      }
089    
090      public static void processAjax(FacesContext facesContext, UIComponent component)
091          throws IOException {
092        if (component instanceof AjaxComponent) {
093          ((AjaxComponent) component).processAjax(facesContext);
094        } else {
095          processAjaxOnChildren(facesContext, component);
096        }
097      }
098    
099      public static void processActiveAjaxComponent(FacesContext facesContext,
100          UIComponent component)
101          throws IOException {
102    
103        if (component instanceof AjaxComponent) {
104          final UIViewRoot viewRoot = (UIViewRoot) facesContext.getViewRoot();
105    
106          // TODO: handle phaseListeners ??
107    
108          if (!facesContext.getRenderResponse()) {
109            component.processValidators(facesContext);
110            viewRoot.broadcastEventsForPhase(facesContext, PhaseId.PROCESS_VALIDATIONS);
111          } else if (LOG.isDebugEnabled()) {
112            LOG.debug("Skipping validate");
113          }
114    
115          if (!facesContext.getRenderResponse()) {
116            component.processUpdates(facesContext);
117            viewRoot.broadcastEventsForPhase(facesContext, PhaseId.UPDATE_MODEL_VALUES);
118          } else if (LOG.isDebugEnabled()) {
119            LOG.debug("Skipping updates");
120          }
121    
122          if (!facesContext.getRenderResponse()) {
123            viewRoot.processApplication(facesContext);
124          } else if (LOG.isDebugEnabled()) {
125            LOG.debug("Skipping application");
126          }
127    
128          ((AjaxComponent) component).encodeAjax(facesContext);
129        } else {
130          LOG.error("Can't process non AjaxComponent : \""
131              + component.getClientId(facesContext) + "\" = "
132              + component.getClass().getName());
133        }
134      }
135    
136      public static void processAjaxOnChildren(FacesContext facesContext,
137          UIComponent component) throws IOException {
138    
139        final Iterator<UIComponent> facetsAndChildren = component.getFacetsAndChildren();
140        while (facetsAndChildren.hasNext() && !facesContext.getResponseComplete()) {
141          AjaxUtils.processAjax(facesContext, facetsAndChildren.next());
142        }
143      }
144    
145      public static Set<String> getRequestPartialIds(FacesContext facesContext) {
146        Map parameterMap = facesContext.getExternalContext().getRequestParameterMap();
147        String ajaxComponentIds = (String) parameterMap.get(AjaxPhaseListener.AJAX_COMPONENT_ID);
148        if (ajaxComponentIds != null) {
149          StringTokenizer tokenizer = new StringTokenizer(ajaxComponentIds, ",");
150          Set<String> ajaxComponents = new HashSet<String>(tokenizer.countTokens());
151          while (tokenizer.hasMoreTokens()) {
152            String ajaxId = tokenizer.nextToken();
153            ajaxComponents.add(ajaxId);
154          }
155          return ajaxComponents;
156        }
157        return Collections.EMPTY_SET;
158      }
159    
160      public static Map<String, UIComponent> parseAndStoreComponents(FacesContext facesContext) {
161        Map parameterMap = facesContext.getExternalContext().getRequestParameterMap();
162        String ajaxComponentIds = (String) parameterMap.get(AjaxPhaseListener.AJAX_COMPONENT_ID);
163        if (ajaxComponentIds != null) {
164          if (LOG.isDebugEnabled()) {
165            LOG.debug("ajaxComponentIds = \"" + ajaxComponentIds + "\"");
166          }
167          StringTokenizer tokenizer = new StringTokenizer(ajaxComponentIds, ",");
168          Map<String, UIComponent> ajaxComponents = new HashMap<String, UIComponent>(tokenizer.countTokens());
169          //noinspection unchecked
170          facesContext.getExternalContext().getRequestMap().put(AJAX_COMPONENTS, ajaxComponents);
171          javax.faces.component.UIViewRoot viewRoot = facesContext.getViewRoot();
172          while (tokenizer.hasMoreTokens()) {
173            String ajaxId = tokenizer.nextToken();
174            UIComponent ajaxComponent = viewRoot.findComponent(ajaxId);
175            if (ajaxComponent != null) {
176              if (LOG.isDebugEnabled()) {
177                LOG.debug("ajaxComponent for \"" + ajaxId + "\" = \"" + ajaxComponent + "\"");
178              }
179              ajaxComponents.put(ajaxId, ajaxComponent);
180            }
181          }
182          return ajaxComponents;
183        }
184        return null;
185      }
186    
187      public static Map<String, UIComponent> getAjaxComponents(FacesContext facesContext) {
188        //noinspection unchecked
189        return (Map<String, UIComponent>)
190            facesContext.getExternalContext().getRequestMap().get(AJAX_COMPONENTS);
191      }
192    
193      public static void ensureDecoded(FacesContext facesContext, String clientId) {
194        ensureDecoded(facesContext, facesContext.getViewRoot().findComponent(clientId));
195      }
196    
197      public static void ensureDecoded(FacesContext facesContext, UIComponent component) {
198        if (component == null) {
199          LOG.warn("Ignore AjaxComponent: null");
200          return;
201        }
202        Map<String, UIComponent> ajaxComponents = getAjaxComponents(facesContext);
203        if (ajaxComponents != null) {
204          for (UIComponent uiComponent : ajaxComponents.values()) {
205            // is component or a parent of it in the list?
206            UIComponent parent = component;
207            while (parent != null) {
208              if (component == uiComponent) {
209                // nothing to do, because it was already decoded (in the list)
210                return;
211              }
212              parent = parent.getParent();
213            }
214          }
215          component.processDecodes(facesContext);
216        }
217      }
218    
219      public static boolean redirect(FacesContext facesContext, String url) throws IOException {
220        if (!isAjaxRequest(facesContext)) {
221          return false;
222        }
223        ResponseWriter writer = facesContext.getResponseWriter();
224        if (writer == null) {
225          RenderKit renderKit = facesContext.getRenderKit();
226          if (renderKit == null) {
227            RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
228            Application application = facesContext.getApplication();
229            ViewHandler applicationViewHandler = application.getViewHandler();
230            String renderKitId = applicationViewHandler.calculateRenderKitId(facesContext);
231            renderKit = renderFactory.getRenderKit(facesContext, renderKitId);
232          }
233          writer = renderKit.createResponseWriter(((HttpServletResponse)
234                  facesContext.getExternalContext().getResponse()).getWriter(), null, null);
235        }
236        ResponseUtils.ensureNoCacheHeader(facesContext);
237        writer.startElement("redirect", null);
238        writer.writeAttribute("url", url, null);
239        writer.endElement("redirect");
240        writer.flush();
241        facesContext.responseComplete();
242        return true;
243      }
244    
245      public static void redirect(HttpServletResponse response, String url) throws IOException {
246        PrintWriter out = response.getWriter();
247        out.print("<redirect url=");
248        out.print(url);
249        out.println("</redirect>");
250      }
251    }