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