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