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