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.Calendar;
27  import java.util.Date;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Set;
31  import java.util.TimeZone;
32  
33  import javax.el.MethodExpression;
34  import javax.el.ValueExpression;
35  
36  import javax.faces.component.UIComponent;
37  import javax.faces.component.UIViewRoot;
38  import javax.faces.context.FacesContext;
39  import javax.faces.webapp.UIComponentELTag;
40  
41  import javax.servlet.jsp.JspException;
42  
43  import org.apache.myfaces.trinidad.bean.FacesBean;
44  import org.apache.myfaces.trinidad.bean.PropertyKey;
45  import org.apache.myfaces.trinidad.change.ChangeManager;
46  import org.apache.myfaces.trinidad.component.UIXComponent;
47  import org.apache.myfaces.trinidad.component.UIXDocument;
48  import org.apache.myfaces.trinidad.context.RequestContext;
49  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
50  
51  
52  /**
53   * Subclass of UIComponentTag to add convenience methods,
54   * and optimize where appropriate.
55   */
56  abstract public class UIXComponentELTag extends UIComponentELTag
57  {
58    public UIXComponentELTag()
59    {
60    }
61  
62    public void setAttributeChangeListener(MethodExpression attributeChangeListener)
63    {
64      _attributeChangeListener = attributeChangeListener;
65    }
66  
67    @Override
68    public int doStartTag() throws JspException
69    {
70      int retVal = super.doStartTag();
71  
72      //pu: There could have been some validation error during property setting
73      //  on the bean, this is the closest opportunity to burst out.
74      if (_validationError != null)
75        throw new JspException(_validationError);
76  
77      return retVal;
78    }
79  
80    @Override
81    public int doEndTag() throws JspException
82    {
83      UIComponent component = getComponentInstance();
84      
85      // Apply changes once we have a stable UIComponent subtree is completely 
86      //  created. End of document tag is a best bet.
87      if (component instanceof UIXDocument)
88      {
89        ChangeManager cm = RequestContext.getCurrentInstance().getChangeManager();
90        cm.applyComponentChangesForCurrentView(FacesContext.getCurrentInstance());
91      }
92      return super.doEndTag();
93    }
94  
95  
96    @Override
97    protected final void setProperties(UIComponent component)
98    {
99      if (component instanceof UIViewRoot)
100     {
101       throw new IllegalStateException(
102          "<f:view> was not present on this page; tag " + this +
103          "encountered without an <f:view> being processed.");
104     }
105 
106     super.setProperties(component);
107 
108     UIXComponent uixComponent = (UIXComponent) component;
109 
110     if (_attributeChangeListener != null)
111     {
112       uixComponent.setAttributeChangeListener(_attributeChangeListener);
113     }
114 
115     setProperties(uixComponent.getFacesBean());
116   }
117 
118   protected void setProperty(
119     FacesBean   bean,
120     PropertyKey key,
121     ValueExpression expression)
122   {
123     if (expression == null)
124       return;
125 
126     if (expression.isLiteralText())
127     {
128       bean.setProperty(key, expression.getValue(null));
129     }
130     else
131     {
132       bean.setValueExpression(key, expression);
133     }
134   }
135 
136   /**
137    * Set a property of type java.lang.String[].  If the value
138    * is an EL expression, it will be stored as a ValueExpression.
139    * Otherwise, it will parsed as a whitespace-separated series
140    * of strings.
141    * Null values are ignored.
142    */
143   protected void setStringArrayProperty(
144     FacesBean       bean,
145     PropertyKey     key,
146     ValueExpression expression)
147   {
148     if (expression == null)
149       return;
150 
151     if (expression.isLiteralText())
152     {
153       bean.setProperty(key, _parseNameTokens(expression.getValue(null)));
154     }
155     else
156     {
157       bean.setValueExpression(key, expression);
158     }
159   }
160 
161   /**
162    * Set a property of type java.util.List<java.lang.String>.  If the value
163    * is an EL expression, it will be stored as a ValueExpression.
164    * Otherwise, it will parsed as a whitespace-separated series
165    * of strings.
166    * Null values are ignored.
167    */
168   protected void setStringListProperty(
169     FacesBean       bean,
170     PropertyKey     key,
171     ValueExpression expression)
172   {
173     if (expression == null)
174       return;
175 
176     if (expression.isLiteralText())
177     {
178       bean.setProperty(key, 
179                        _parseNameTokensAsList(expression.getValue(null)));
180     }
181     else
182     {
183       bean.setValueExpression(key, expression);
184     }
185   }
186 
187   /**
188    * Set a property of type java.util.Set<java.lang.String>.  If the value
189    * is an EL expression, it will be stored as a ValueExpression.
190    * Otherwise, it will parsed as a whitespace-separated series
191    * of strings.
192    * Null values are ignored.
193    */
194   protected void setStringSetProperty(
195     FacesBean       bean,
196     PropertyKey     key,
197     ValueExpression expression)
198   {
199     if (expression == null)
200       return;
201 
202     if (expression.isLiteralText())
203     {
204       bean.setProperty(key, 
205                        _parseNameTokensAsSet(expression.getValue(null)));
206     }
207     else
208     {
209       bean.setValueExpression(key, expression);
210     }
211   }
212 
213   /**
214    * Set a property of type java.lang.Number.  If the value
215    * is an EL expression, it will be stored as a ValueBinding.
216    * Otherwise, it will parsed with Integer.valueOf() or Double.valueOf() .
217    * Null values are ignored.
218    */
219   protected void setNumberProperty(
220     FacesBean   bean,
221     PropertyKey key,
222     ValueExpression expression)
223   {
224     if (expression == null)
225       return;
226 
227     if (expression.isLiteralText())
228     {
229       Object value = expression.getValue(null);
230       if (value != null)
231       { 
232         if (value instanceof Number)
233         {
234           bean.setProperty(key, value);
235         }
236         else
237         {
238           String valueStr = value.toString();
239           if(valueStr.indexOf('.') == -1)
240             bean.setProperty(key, Integer.valueOf(valueStr));
241           else
242             bean.setProperty(key, Double.valueOf(valueStr));
243         }
244       }
245     }
246     else
247     {
248       bean.setValueExpression(key, expression);
249     }
250   }
251 
252   /**
253    * Set a property of type int[].  If the value
254    * is an EL expression, it will be stored as a ValueExpression.
255    * Otherwise, it will parsed as a whitespace-separated series
256    * of ints.
257    * Null values are ignored.
258    */
259   protected void setIntArrayProperty(
260     FacesBean   bean,
261     PropertyKey key,
262     ValueExpression expression)
263   {
264     if (expression == null)
265       return;
266 
267     if (expression.isLiteralText())
268     {
269       Object value = expression.getValue(null);
270       if (value != null)
271       {
272         String[] strings = _parseNameTokens(value);
273         final int[] ints;
274         if (strings != null)
275         {
276           try
277           {
278             ints = new int[strings.length];
279             for(int i=0; i<strings.length; i++)
280             {
281               int j = Integer.parseInt(strings[i]);
282               ints[i] = j;
283             }
284           }
285           catch (NumberFormatException e)
286           {
287             _LOG.severe("CANNOT_CONVERT_INTO_INT_ARRAY",value);
288             _LOG.severe(e);
289             return;
290           }
291         }
292       }
293     }
294     else
295     {
296       bean.setValueExpression(key, expression);
297     }
298   }
299 
300   /**
301    * Set a property of type java.util.Date.  If the value
302    * is an EL expression, it will be stored as a ValueExpression.
303    * Otherwise, it will parsed as an ISO 8601 date (yyyy-MM-dd).
304    * Null values are ignored.
305    */
306   protected void setDateProperty(
307     FacesBean   bean,
308     PropertyKey key,
309     ValueExpression expression)
310   {
311     if (expression == null)
312       return;
313 
314     if (expression.isLiteralText())
315     {
316       bean.setProperty(key, _parseISODate(expression.getValue(null)));
317     }
318     else
319     {
320       bean.setValueExpression(key, expression);
321     }
322   }
323 
324     /**
325    * Set a property of type java.util.Date.  If the value
326    * is an EL expression, it will be stored as a ValueBinding.
327    * Otherwise, it will parsed as an ISO 8601 date (yyyy-MM-dd)
328    * and the time components (hour, min, second, millisecond) maximized.
329    * Null values are ignored.
330    */
331     protected void setMaxDateProperty(
332     FacesBean   bean,
333     PropertyKey key,
334     ValueExpression expression)
335   {
336     if (expression == null)
337       return;
338 
339     if (expression.isLiteralText())
340     {
341       Date d = _parseISODate(expression.getValue(null));
342       Calendar c = Calendar.getInstance();
343       TimeZone tz = RequestContext.getCurrentInstance().getTimeZone();
344       if (tz != null)
345         c.setTimeZone(tz);
346       c.setTime(d);
347       // Original value had 00:00:00 for hours,mins, seconds now maximize those
348       // to get the latest time value for the date supplied.
349       c.set (Calendar.HOUR_OF_DAY, 23);
350       c.set (Calendar.MINUTE, 59);
351       c.set (Calendar.SECOND, 59);
352       c.set (Calendar.MILLISECOND, 999);
353       bean.setProperty(key, c.getTime());
354     }
355     else
356     {
357       bean.setValueExpression(key, expression);
358     }
359   }
360 
361   protected void setProperties(FacesBean bean)
362   {
363     // Could be abstract, but it's easier to *always* call super.setProperties(),
364     // and perhaps we'll have something generic in here, esp. if we take
365     // over "rendered" from UIComponentTag
366   }
367 
368   /**
369    * Sets any fatal validation error that could have happened during property
370    *  setting. If this is set, tag execution aborts with a JspException at the
371    *  end of doStartTag().
372    * @param validationError
373    */
374   protected void setValidationError(String validationError)
375   {
376     _validationError = validationError;
377   }
378 
379   /**
380    * Parse a string into a java.util.Date object.  The
381    * string must be in ISO 9601 format (yyyy-MM-dd).
382    */
383   static private final Date _parseISODate(Object o)
384   {
385     if (o == null)
386       return null;
387 
388     String stringValue = o.toString();
389     try
390     {
391       return _getDateFormat().parse(stringValue);
392     }
393     catch (ParseException pe)
394     {
395       _LOG.info("CANNOT_PARSE_VALUE_INTO_DATE", stringValue);
396       return null;
397     }
398   }
399 
400   /**
401    * Parses a whitespace separated series of name tokens.
402    * @param stringValue the full string
403    * @return an array of each constituent value, or null
404    *  if there are no tokens (that is, the string is empty or
405    *  all whitespace)
406    * @todo Move to utility function somewhere (ADF Share?)
407    */
408   static private final String[] _parseNameTokens(Object o)
409   {
410     List<String> list = _parseNameTokensAsList (o);
411 
412     if (list == null)
413       return null;
414 
415     return list.toArray(new String[list.size()]);
416   }
417 
418   static private final List<String> _parseNameTokensAsList (Object o)
419   {
420     if (o == null)
421       return null;
422 
423     String stringValue = o.toString();
424     ArrayList<String> list = new ArrayList<String>(5);
425 
426     int     length = stringValue.length();
427     boolean inSpace = true;
428     int     start = 0;
429     for (int i = 0; i < length; i++)
430     {
431       char ch = stringValue.charAt(i);
432 
433       // We're in whitespace;  if we've just departed
434       // a run of non-whitespace, append a string.
435       // Now, why do we use the supposedly deprecated "Character.isSpace()"
436       // function instead of "isWhitespace"?  We're following XML rules
437       // here for the meaning of whitespace, which specifically
438       // EXCLUDES general Unicode spaces.
439       if (Character.isWhitespace(ch))
440       {
441         if (!inSpace)
442         {
443           list.add(stringValue.substring(start, i));
444           inSpace = true;
445         }
446       }
447       // We're out of whitespace;  if we've just departed
448       // a run of whitespace, start keeping track of this string
449       else
450       {
451         if (inSpace)
452         {
453           start = i;
454           inSpace = false;
455         }
456       }
457     }
458 
459     if (!inSpace)
460       list.add(stringValue.substring(start));
461 
462     if (list.isEmpty())
463       return null;
464 
465     return list;
466   }
467 
468   static private final Set<String> _parseNameTokensAsSet (Object o)
469   {
470     List<String> list = _parseNameTokensAsList(o);
471 
472     if (list == null)
473       return null;
474     else
475       return new HashSet(list);
476   }
477 
478   private static final TrinidadLogger _LOG = 
479     TrinidadLogger.createTrinidadLogger(UIXComponentELTag.class);
480 
481   // We rely strictly on ISO 8601 formats
482   private static DateFormat _getDateFormat()
483   {
484     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
485     TimeZone tz = RequestContext.getCurrentInstance().getTimeZone();
486     if (tz != null)
487       sdf.setTimeZone(tz);
488     return sdf;
489   }
490 
491   private MethodExpression  _attributeChangeListener;
492   private String            _validationError;
493 }