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  package org.apache.myfaces.trinidad.webapp;
20  
21  import java.text.DateFormat;
22  import java.text.ParseException;
23  import java.text.SimpleDateFormat;
24  
25  import java.util.ArrayList;
26  import java.util.Date;
27  
28  import javax.el.MethodExpression;
29  
30  import javax.faces.component.UIComponent;
31  import javax.faces.component.UIViewRoot;
32  import javax.faces.el.MethodBinding;
33  import javax.faces.el.ValueBinding;
34  import javax.faces.webapp.UIComponentTag;
35  
36  import javax.servlet.jsp.JspException;
37  import javax.servlet.jsp.tagext.TagSupport;
38  
39  import org.apache.myfaces.trinidad.bean.FacesBean;
40  import org.apache.myfaces.trinidad.bean.PropertyKey;
41  import org.apache.myfaces.trinidad.component.UIXComponent;
42  import org.apache.myfaces.trinidad.event.AttributeChangeEvent;
43  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
44  
45  
46  /**
47   * Subclass of UIComponentTag to add convenience methods,
48   * and optimize where appropriate.
49   */
50  abstract public class UIXComponentTag extends UIComponentTag
51  {
52    public UIXComponentTag()
53    {
54    }
55  
56    @Override
57    public void setId(String id)
58    {
59      _id = id;
60    }
61  
62    @Override
63    public void setRendered(String rendered)
64    {
65      _rendered = rendered;
66    }
67  
68    public void setAttributeChangeListener(String attributeChangeListener)
69    {
70      _attributeChangeListener = attributeChangeListener;
71    }
72  
73    @Override
74    public int doStartTag() throws JspException
75    {
76      _parentELContext = (ELContextTag)
77         TagSupport.findAncestorWithClass(this, ELContextTag.class);
78  
79      // Transform "rendered" on behalf of the UIComponentTag
80      String rendered = _rendered;
81      if (rendered != null)
82      {
83        if ((_parentELContext != null) && isValueReference(rendered))
84          rendered = _parentELContext.transformExpression(rendered);
85  
86        super.setRendered(rendered);
87      }
88  
89  
90      String id = _id;
91      if (id != null)
92      {
93        if (_parentELContext != null)
94          id = _parentELContext.transformId(id);
95  
96        super.setId(id);
97      }
98  
99      int retVal = super.doStartTag();
100 
101     //pu: There could have been some validation error during property setting
102     //  on the bean, this is the closest opportunity to burst out.
103     if (_validationError != null)
104       throw new JspException(_validationError);
105 
106     return retVal;
107   }
108 
109   @Override
110   protected final void setProperties(UIComponent component)
111   {
112     if (component instanceof UIViewRoot)
113     {
114       throw new IllegalStateException(_LOG.getMessage(
115         "VIEW_TAG_NOT_PRESENT", this));
116     }
117 
118     super.setProperties(component);
119     
120     UIXComponent uixComponent = (UIXComponent) component;
121 
122     if (_attributeChangeListener != null)
123     {
124       MethodExpression me = getFacesContext().getApplication().
125          getExpressionFactory().createMethodExpression(
126              getFacesContext().getELContext(),
127              _attributeChangeListener,
128              null,
129              new Class[]{AttributeChangeEvent.class});
130 
131       uixComponent.setAttributeChangeListener(me);
132     }
133 
134 
135     setProperties(uixComponent.getFacesBean());
136   }
137 
138   protected void setProperty(
139     FacesBean   bean,
140     PropertyKey key,
141     String      value)
142   {
143     if (value == null)
144       return;
145 
146     if (isValueReference(value))
147     {
148       bean.setValueBinding(key, createValueBinding(value));
149     }
150     else
151     {
152       bean.setProperty(key, value);
153     }
154   }
155 
156   /**
157    * Set a property of type java.lang.Boolean.  If the value
158    * is an EL expression, it will be stored as a ValueBinding.
159    * Otherwise, it will parsed with Integer.valueOf().
160    * Null values are ignored.
161    */
162   protected void setBooleanProperty(
163     FacesBean   bean,
164     PropertyKey key,
165     String      value)
166   {
167     if (value == null)
168       return;
169 
170     if (isValueReference(value))
171     {
172       bean.setValueBinding(key, createValueBinding(value));
173     }
174     else
175     {
176       bean.setProperty(key, Boolean.valueOf(value));
177     }
178   }
179 
180   /**
181    * Set a property of type java.lang.Number.  If the value
182    * is an EL expression, it will be stored as a ValueBinding.
183    * Otherwise, it will parsed with Integer.valueOf() or Double.valueOf() .
184    * Null values are ignored.
185    */
186   protected void setNumberProperty(
187     FacesBean   bean,
188     PropertyKey key,
189     String      value)
190   {
191     if (value == null)
192       return;
193 
194     if (isValueReference(value))
195     {
196       bean.setValueBinding(key, createValueBinding(value));
197     }
198     else
199     {
200       if(value.indexOf('.') == -1)
201         bean.setProperty(key, Integer.valueOf(value));
202       else
203         bean.setProperty(key, Double.valueOf(value));
204     }
205   }
206  /**
207   * Set a property of type java.lang.Integer.  If the value
208   * is an EL expression, it will be stored as a ValueBinding.
209   * Otherwise, it will parsed with Integer.valueOf().
210   * Null values are ignored.
211   */
212  protected void setIntegerProperty(
213    FacesBean   bean,
214    PropertyKey key,
215    String      value)
216  {
217    if (value == null)
218      return;
219 
220    if (isValueReference(value))
221    {
222      bean.setValueBinding(key, createValueBinding(value));
223    }
224    else
225    {
226      bean.setProperty(key, Integer.valueOf(value));
227    }
228  }
229 
230 
231   /**
232    * Set a property of type java.lang.Character.  If the value
233    * is an EL expression, it will be stored as a ValueBinding.
234    * Otherwise, its first character will be stored (unless
235    * it is an empty string, in which case it will be ignored).
236    * Null values are ignored.
237    */
238   protected void setCharacterProperty(
239     FacesBean   bean,
240     PropertyKey key,
241     String      value)
242   {
243     if (value == null)
244       return;
245 
246     if (isValueReference(value))
247     {
248       bean.setValueBinding(key, createValueBinding(value));
249     }
250     else
251     {
252       if (value.length() >= 1)
253         bean.setProperty(key, Character.valueOf(value.charAt(0)));
254     }
255   }
256 
257   /**
258    * Set a property of type java.lang.Long.  If the value
259    * is an EL expression, it will be stored as a ValueBinding.
260    * Otherwise, it will parsed with Long.valueOf().
261    * Null values are ignored.
262    */
263   protected void setLongProperty(
264     FacesBean   bean,
265     PropertyKey key,
266     String      value)
267   {
268     if (value == null)
269       return;
270 
271     if (isValueReference(value))
272     {
273       bean.setValueBinding(key, createValueBinding(value));
274     }
275     else
276     {
277       bean.setProperty(key, Long.valueOf(value));
278     }
279   }
280 
281   /**
282    * Set a property of type java.lang.Double.  If the value
283    * is an EL expression, it will be stored as a ValueBinding.
284    * Otherwise, it will parsed with Double.valueOf().
285    * Null values are ignored.
286    */
287   protected void setDoubleProperty(
288     FacesBean   bean,
289     PropertyKey key,
290     String      value)
291   {
292     if (value == null)
293       return;
294 
295     if (isValueReference(value))
296     {
297       bean.setValueBinding(key, createValueBinding(value));
298     }
299     else
300     {
301       bean.setProperty(key, Double.valueOf(value));
302     }
303   }
304 
305   /**
306    * Set a property of type java.lang.Float.  If the value
307    * is an EL expression, it will be stored as a ValueBinding.
308    * Otherwise, it will parsed with Float.valueOf().
309    * Null values are ignored.
310    */
311   protected void setFloatProperty(
312     FacesBean   bean,
313     PropertyKey key,
314     String      value)
315   {
316     if (value == null)
317       return;
318 
319     if (isValueReference(value))
320     {
321       bean.setValueBinding(key, createValueBinding(value));
322     }
323     else
324     {
325       bean.setProperty(key, Float.valueOf(value));
326     }
327   }
328 
329 
330 
331   /**
332    * Set a property of type java.lang.String[].  If the value
333    * is an EL expression, it will be stored as a ValueBinding.
334    * Otherwise, it will parsed as a whitespace-separated series
335    * of strings.
336    * Null values are ignored.
337    */
338   protected void setStringArrayProperty(
339     FacesBean   bean,
340     PropertyKey key,
341     String      value)
342   {
343     if (value == null)
344       return;
345 
346     if (isValueReference(value))
347     {
348       bean.setValueBinding(key, createValueBinding(value));
349     }
350     else
351     {
352       bean.setProperty(key, _parseNameTokens(value));
353     }
354   }
355 
356   /**
357    * Set a property of type int[].  If the value
358    * is an EL expression, it will be stored as a ValueBinding.
359    * Otherwise, it will parsed as a whitespace-separated series
360    * of ints.
361    * Null values are ignored.
362    */
363   protected void setIntArrayProperty(
364     FacesBean   bean,
365     PropertyKey key,
366     String      value)
367   {
368     if (value == null)
369       return;
370 
371     if (isValueReference(value))
372     {
373       bean.setValueBinding(key, createValueBinding(value));
374     }
375     else
376     {
377       String[] strings = _parseNameTokens(value);
378       final int[] ints;
379       if (strings != null)
380       {
381         try
382         {
383           ints = new int[strings.length];
384           for(int i=0; i<strings.length; i++)
385           {
386             int j = Integer.parseInt(strings[i]);
387             ints[i] = j;
388           }
389         }
390         catch (NumberFormatException e)
391         {
392           _LOG.severe("CANNOT_CONVERT_INTO_INT_ARRAY",value);
393           _LOG.severe(e);
394           return;
395         }
396       }
397       else
398         ints = null;
399 
400       bean.setProperty(key, ints);
401     }
402   }
403 
404   /**
405    * Set a property of type java.util.Date.  If the value
406    * is an EL expression, it will be stored as a ValueBinding.
407    * Otherwise, it will parsed as an ISO 8601 date (yyyy-MM-dd).
408    * Null values are ignored.
409    */
410   protected void setDateProperty(
411     FacesBean   bean,
412     PropertyKey key,
413     String      value)
414   {
415     if (value == null)
416       return;
417 
418     if (isValueReference(value))
419     {
420       bean.setValueBinding(key, createValueBinding(value));
421     }
422     else
423     {
424       bean.setProperty(key, _parseISODate(value));
425     }
426   }
427 
428 
429   // TODO Handle syntax exceptions gracefully?
430   protected final ValueBinding createValueBinding(String string)
431   {
432     if (_parentELContext != null)
433       string = _parentELContext.transformExpression(string);
434 
435     return getFacesContext().getApplication().createValueBinding(string);
436   }
437 
438   // TODO Handle syntax exceptions gracefully?
439   protected final MethodBinding createMethodBinding(
440     String   string,
441     Class [] types)
442   {
443     if (_parentELContext != null)
444       string = _parentELContext.transformExpression(string);
445 
446     return getFacesContext().getApplication().createMethodBinding(string,
447                                                                   types);
448   }
449 
450   protected void setProperties(FacesBean bean)
451   {
452     // Could be abstract, but it's easier to *always* call super.setProperties(),
453     // and perhaps we'll have something generic in here, esp. if we take
454     // over "rendered" from UIComponentTag
455   }
456 
457   /**
458    * Sets any fatal validation error that could have happened during property
459    *  setting. If this is set, tag execution aborts with a JspException at the
460    *  end of doStartTag().
461    * @param validationError
462    */
463   protected void setValidationError(String validationError)
464   {
465     _validationError = validationError;
466   }
467 
468   /**
469    * Parse a string into a java.util.Date object.  The
470    * string must be in ISO 9601 format (yyyy-MM-dd).
471    */
472   static private final Date _parseISODate(String stringValue)
473   {
474     try
475     {
476       return _getDateFormat().parse(stringValue);
477     }
478     catch (ParseException pe)
479     {
480       _LOG.info("CANNOT_PARSE_VALUE_INTO_DATE", stringValue);
481       return null;
482     }
483   }
484 
485   /**
486    * Parses a whitespace separated series of name tokens.
487    * @param stringValue the full string
488    * @return an array of each constituent value, or null
489    *  if there are no tokens (that is, the string is empty or
490    *  all whitespace)
491    */
492   // TODO Move to utility function somewhere 
493   static private final String[] _parseNameTokens(String stringValue)
494   {
495     if (stringValue == null)
496       return null;
497 
498     ArrayList<String> list = new ArrayList<String>(5);
499 
500     int     length = stringValue.length();
501     boolean inSpace = true;
502     int     start = 0;
503     for (int i = 0; i < length; i++)
504     {
505       char ch = stringValue.charAt(i);
506 
507       // We're in whitespace;  if we've just departed
508       // a run of non-whitespace, append a string.
509       // Now, why do we use the supposedly deprecated "Character.isSpace()"
510       // function instead of "isWhitespace"?  We're following XML rules
511       // here for the meaning of whitespace, which specifically
512       // EXCLUDES general Unicode spaces.
513       if (Character.isWhitespace(ch))
514       {
515         if (!inSpace)
516         {
517           list.add(stringValue.substring(start, i));
518           inSpace = true;
519         }
520       }
521       // We're out of whitespace;  if we've just departed
522       // a run of whitespace, start keeping track of this string
523       else
524       {
525         if (inSpace)
526         {
527           start = i;
528           inSpace = false;
529         }
530       }
531     }
532 
533     if (!inSpace)
534       list.add(stringValue.substring(start));
535 
536     if (list.isEmpty())
537       return null;
538 
539     return list.toArray(new String[list.size()]);
540   }
541 
542   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(UIXComponentTag.class);
543 
544   // We rely strictly on ISO 8601 formats
545   private static DateFormat _getDateFormat()
546   {
547     return new SimpleDateFormat("yyyy-MM-dd");
548   }
549 
550   private String       _rendered;
551   private String       _id;
552   private String       _attributeChangeListener;
553   private String       _validationError;
554   private ELContextTag _parentELContext;
555 }