View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.renderkit.util;
21  
22  import org.apache.myfaces.tobago.context.ResourceManagerUtils;
23  import org.apache.myfaces.tobago.internal.component.AbstractUICommand;
24  import org.apache.myfaces.tobago.internal.component.AbstractUIData;
25  import org.apache.myfaces.tobago.internal.util.StringUtils;
26  import org.apache.myfaces.tobago.model.ExpandedState;
27  import org.apache.myfaces.tobago.model.SelectedState;
28  import org.apache.myfaces.tobago.model.TreePath;
29  import org.apache.myfaces.tobago.renderkit.html.AjaxClientBehaviorRenderer;
30  import org.apache.myfaces.tobago.renderkit.html.CommandMap;
31  import org.apache.myfaces.tobago.renderkit.html.JsonUtils;
32  import org.apache.myfaces.tobago.util.ComponentUtils;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import javax.faces.application.Application;
37  import javax.faces.application.ViewHandler;
38  import javax.faces.component.EditableValueHolder;
39  import javax.faces.component.UIComponent;
40  import javax.faces.component.UIParameter;
41  import javax.faces.component.ValueHolder;
42  import javax.faces.component.behavior.ClientBehavior;
43  import javax.faces.component.behavior.ClientBehaviorBase;
44  import javax.faces.component.behavior.ClientBehaviorContext;
45  import javax.faces.component.behavior.ClientBehaviorHolder;
46  import javax.faces.context.ExternalContext;
47  import javax.faces.context.FacesContext;
48  import javax.faces.render.ClientBehaviorRenderer;
49  import java.io.IOException;
50  import java.io.UnsupportedEncodingException;
51  import java.net.URLEncoder;
52  import java.util.List;
53  import java.util.Map;
54  
55  public final class RenderUtils {
56  
57    private static final Logger LOG = LoggerFactory.getLogger(RenderUtils.class);
58  
59    private RenderUtils() {
60      // to prevent instantiation
61    }
62  
63    public static boolean contains(final Object[] list, final Object value) {
64      if (list == null) {
65        return false;
66      }
67      for (final Object aList : list) {
68        if (aList != null && aList.equals(value)) {
69          return true;
70        }
71      }
72      return false;
73    }
74  
75    public static void encodeChildren(final FacesContext facesContext, final UIComponent panel) throws IOException {
76      for (final UIComponent child : panel.getChildren()) {
77        encode(facesContext, child);
78      }
79    }
80  
81    public static void encode(final FacesContext facesContext, final UIComponent component) throws IOException {
82      encode(facesContext, component, null);
83    }
84  
85    public static void encode(
86        final FacesContext facesContext, final UIComponent component,
87        final List<? extends Class<? extends UIComponent>> only)
88        throws IOException {
89  
90      if (only != null && !matchFilter(component, only)) {
91        return;
92      }
93  
94      if (component.isRendered()) {
95        if (LOG.isDebugEnabled()) {
96          LOG.debug("rendering " + component.getRendererType() + " " + component);
97        }
98        component.encodeBegin(facesContext);
99        if (component.getRendersChildren()) {
100         component.encodeChildren(facesContext);
101       } else {
102         for (final UIComponent child : component.getChildren()) {
103           encode(facesContext, child, only);
104         }
105       }
106       component.encodeEnd(facesContext);
107     }
108   }
109 
110   private static boolean matchFilter(
111       final UIComponent component, final List<? extends Class<? extends UIComponent>> only) {
112     for (final Class<? extends UIComponent> clazz : only) {
113       if (clazz.isAssignableFrom(component.getClass())) {
114         return true;
115       }
116     }
117     return false;
118   }
119 
120   public static String currentValue(final UIComponent component) {
121     String currentValue = null;
122     if (component instanceof ValueHolder) {
123       Object value;
124       if (component instanceof EditableValueHolder) {
125         value = ((EditableValueHolder) component).getSubmittedValue();
126         if (value != null) {
127           return (String) value;
128         }
129       }
130 
131       value = ((ValueHolder) component).getValue();
132       if (value != null) {
133         currentValue = ComponentUtils.getFormattedValue(FacesContext.getCurrentInstance(), component, value);
134       }
135     }
136     return currentValue;
137   }
138 
139   public static void decodedStateOfTreeData(final FacesContext facesContext, final AbstractUIData data) {
140 
141     if (!data.isTreeModel()) {
142       return;
143     }
144 
145     // selected
146     final List<Integer> selectedIndices = decodeIndices(facesContext, data, AbstractUIData.SUFFIX_SELECTED);
147 
148     // expanded
149     final List<Integer> expandedIndices = decodeIndices(facesContext, data, AbstractUIData.SUFFIX_EXPANDED);
150 
151     final int last = data.isRowsUnlimited() ? Integer.MAX_VALUE : data.getFirst() + data.getRows();
152     for (int rowIndex = data.getFirst(); rowIndex < last; rowIndex++) {
153       data.setRowIndex(rowIndex);
154       if (!data.isRowAvailable()) {
155         break;
156       }
157 
158       final TreePath path = data.getPath();
159 
160       // selected
161       if (selectedIndices != null) {
162         final SelectedState selectedState = data.getSelectedState();
163         final boolean oldSelected = selectedState.isSelected(path);
164         final boolean newSelected = selectedIndices.contains(rowIndex);
165         if (newSelected != oldSelected) {
166           if (newSelected) {
167             selectedState.select(path);
168           } else {
169             selectedState.unselect(path);
170           }
171         }
172       }
173 
174       // expanded
175       if (expandedIndices != null) {
176         final ExpandedState expandedState = data.getExpandedState();
177         final boolean oldExpanded = expandedState.isExpanded(path);
178         final boolean newExpanded = expandedIndices.contains(rowIndex);
179         if (newExpanded != oldExpanded) {
180           if (newExpanded) {
181             expandedState.expand(path);
182           } else {
183             expandedState.collapse(path);
184           }
185         }
186       }
187 
188     }
189     data.setRowIndex(-1);
190   }
191 
192   private static List<Integer> decodeIndices(
193       final FacesContext facesContext, final AbstractUIData data, final String suffix) {
194     String string = null;
195     final String key = data.getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + suffix;
196     try {
197       string = facesContext.getExternalContext().getRequestParameterMap().get(key);
198       if (string != null) {
199         return StringUtils.parseIntegerList(string);
200       }
201     } catch (final Exception e) {
202       // should not happen
203       LOG.warn("Can't parse " + suffix + ": '" + string + "' from parameter '" + key + "'", e);
204     }
205     return null;
206   }
207 
208   public static String generateUrl(final FacesContext facesContext, final AbstractUICommand component) {
209 
210     final Application application = facesContext.getApplication();
211     final ViewHandler viewHandler = application.getViewHandler();
212     final ExternalContext externalContext = facesContext.getExternalContext();
213 
214     String url = null;
215 
216     if (component.getResource() != null) {
217       final boolean jsfResource = component.isJsfResource();
218       url = ResourceManagerUtils.getPageWithoutContextPath(facesContext, component.getResource());
219       if (url != null) {
220         if (jsfResource) {
221           url = viewHandler.getActionURL(facesContext, url);
222           url = externalContext.encodeActionURL(url);
223         } else {
224           url = viewHandler.getResourceURL(facesContext, url);
225           url = externalContext.encodeResourceURL(url);
226         }
227       } else {
228         url = "";
229       }
230     } else if (component.getLink() != null) {
231 
232       final String link = component.getLink();
233       if (link.startsWith("/")) { // internal absolute link
234         url = externalContext.encodeResourceURL(externalContext.getRequestContextPath() + link);
235       } else if (StringUtils.isUrl(link)) { // external link
236         url = link;
237       } else { // internal relative link
238         url = externalContext.encodeResourceURL(link);
239       }
240 
241       final StringBuilder builder = new StringBuilder(url);
242       boolean firstParameter = !url.contains("?");
243       for (final UIComponent child : component.getChildren()) {
244         if (child instanceof UIParameter) {
245           final UIParameter parameter = (UIParameter) child;
246           if (firstParameter) {
247             builder.append("?");
248             firstParameter = false;
249           } else {
250             builder.append("&");
251           }
252           builder.append(parameter.getName());
253           builder.append("=");
254           final Object value = parameter.getValue();
255           if (value != null) {
256             final String characterEncoding = facesContext.getResponseWriter().getCharacterEncoding();
257             try {
258               builder.append(URLEncoder.encode(value.toString(), characterEncoding));
259             } catch (UnsupportedEncodingException e) {
260               LOG.error("", e);
261             }
262           }
263         }
264       }
265       url = builder.toString();
266     }
267 
268     return url;
269   }
270 
271   public static String getBehaviorCommands(final FacesContext facesContext, final ClientBehaviorHolder holder) {
272 
273     final CommandMap map = new CommandMap();
274     addBehaviorCommands(facesContext, holder, map);
275     if (map.isEmpty()) {
276       return null;
277     } else {
278       return JsonUtils.encode(map);
279     }
280   }
281 
282   public static void addBehaviorCommands(
283       final FacesContext facesContext, final ClientBehaviorHolder holder, final CommandMap commandMap) {
284     final Map<String, List<ClientBehavior>> behaviors = holder.getClientBehaviors();
285     for (Map.Entry<String, List<ClientBehavior>> behavior : behaviors.entrySet()) {
286       final String key = behavior.getKey();
287       final ClientBehaviorContext context = ClientBehaviorContext.createClientBehaviorContext(
288           facesContext, (UIComponent) holder, key, ((UIComponent) holder).getClientId(facesContext), null);
289       for (ClientBehavior clientBehavior : behavior.getValue()) {
290         if (clientBehavior instanceof ClientBehaviorBase) {
291           final String type = ((ClientBehaviorBase) clientBehavior).getRendererType();
292           final ClientBehaviorRenderer clientBehaviorRenderer
293               = facesContext.getRenderKit().getClientBehaviorRenderer(type);
294           final String marker = clientBehaviorRenderer.getScript(context, clientBehavior);
295           if (AjaxClientBehaviorRenderer.COMMAND_MAP.equals(marker)) {
296             commandMap.merge((CommandMap) facesContext.getAttributes().get(AjaxClientBehaviorRenderer.COMMAND_MAP));
297           } else {
298             LOG.error("Can't find prepared command map in faces context.");
299           }
300         } else {
301           LOG.warn("Ignoring: '{}'", clientBehavior);
302         }
303       }
304     }
305   }
306 
307   public static void decodeClientBehaviors(final FacesContext facesContext, final UIComponent component) {
308     if (component instanceof ClientBehaviorHolder) {
309       final ClientBehaviorHolder clientBehaviorHolder = (ClientBehaviorHolder) component;
310       final Map<String, List<ClientBehavior>> clientBehaviors = clientBehaviorHolder.getClientBehaviors();
311       if (clientBehaviors != null && !clientBehaviors.isEmpty()) {
312         final Map<String, String> paramMap = facesContext.getExternalContext().getRequestParameterMap();
313         final String behaviorEventName = paramMap.get("javax.faces.behavior.event");
314         if (behaviorEventName != null) {
315           final List<ClientBehavior> clientBehaviorList = clientBehaviors.get(behaviorEventName);
316           if (clientBehaviorList != null && !clientBehaviorList.isEmpty()) {
317             final String clientId = paramMap.get("javax.faces.source");
318             if (component.getClientId(facesContext).equals(clientId)) {
319               for (ClientBehavior clientBehavior : clientBehaviorList) {
320                 clientBehavior.decode(facesContext, component);
321               }
322             }
323           }
324         }
325       }
326     }
327   }
328 }