View Javadoc
1   package org.andromda.cartridges.jsf.component.html;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.Iterator;
6   import java.util.List;
7   import java.util.ListIterator;
8   import javax.faces.context.FacesContext;
9   import javax.faces.el.ValueBinding;
10  import org.apache.commons.beanutils.PropertyUtils;
11  import org.apache.commons.lang.ObjectUtils;
12  import org.apache.commons.lang.StringUtils;
13  import org.apache.myfaces.component.html.ext.HtmlDataTable;
14  
15  /**
16   * Extends the datatable and provides the ability to have a backing value: this is
17   * useful when submitting tables of information.
18   *
19   * @author Chad Brandon
20   */
21  public class HtmlExtendedDataTable
22      extends HtmlDataTable
23  {
24      /**
25       * Overridden to provide population of the backingValue's items with the value's items (and
26       * updating the model afterwards).
27       *
28       * @see javax.faces.component.UIData#getValue()
29       */
30      public Object getValue()
31      {
32          Object value = super.getValue();
33          Object backingValue = this.getBackingValue();
34          if (backingValue != null)
35          {
36              final Class valueType = this.getValueType();
37              final Class backingValueType = this.getBackingValueType();
38              if (valueType != backingValueType)
39              {
40                  throw new IllegalArgumentException("The 'value' and 'backingValue' must be of the same type");
41              }
42              final String identifierColumnsString = this.getIdentifierColumns();
43              final String[] identifierColumns =
44                  StringUtils.isBlank(identifierColumnsString) ? new String[0]
45                                                               : StringUtils.trimToEmpty(identifierColumnsString).split(
46                      "\\s*,\\s*");
47  
48              // - handle collections
49              if (backingValue instanceof Collection)
50              {
51                  if (!(backingValue instanceof List))
52                  {
53                      backingValue = new ArrayList((Collection)backingValue);
54                  }
55                  if (!(value instanceof List))
56                  {
57                      value = new ArrayList((Collection)value);
58                  }
59                  final List backingValues = (List)backingValue;
60                  final List values = (List)value;
61                  for (final ListIterator iterator = backingValues.listIterator(); iterator.hasNext();)
62                  {
63                      final Object backingValueItem = iterator.next();
64                      for (final Iterator valueIterator = values.iterator(); valueIterator.hasNext();)
65                      {
66                          final Object valueItem = valueIterator.next();
67                          if (this.equal(
68                                  backingValueItem,
69                                  valueItem,
70                                  identifierColumns))
71                          {
72                              iterator.set(valueItem);
73                              break;
74                          }
75                      }
76                  }
77                  value = backingValues;
78              }
79  
80              // - handle arrays
81              else if (backingValue.getClass().isArray())
82              {
83                  final Object[] backingValues = (Object[])backingValue;
84                  final Object[] values = value != null ? (Object[])value : new Object[0];
85                  for (int backingValueCtr = 0; backingValueCtr < backingValues.length; backingValueCtr++)
86                  {
87                      final Object backingValueItem = backingValues[backingValueCtr];
88                      for (int valueCtr = 0; valueCtr < values.length; valueCtr++)
89                      {
90                          final Object valueItem = values[valueCtr];
91                          if (this.equal(
92                                  backingValueItem,
93                                  valueItem,
94                                  identifierColumns))
95                          {
96                              backingValues[backingValueCtr] = valueItem;
97                              break;
98                          }
99                      }
100                 }
101                 value = backingValues;
102             }
103             else
104             {
105                 throw new IllegalArgumentException("The backing value must be a collection or array");
106             }
107         }
108         this.updateModelValue(value);
109         return value;
110     }
111 
112     /**
113      * Indicates whether or not this objects are equal, first by comparing them directly for
114      * equality and if not equal in that sense compares the columns returned by {@link #getIdentifierColumns()}.
115      * @param object1 the first object.
116      * @param object2 the second object.
117      * @param properties if equality fails, then these properties are compared for equality (all of them must
118      *        be equal or else this operation will return false).
119      * @return equal true/false
120      */
121     private boolean equal(
122         final Object object1,
123         final Object object2,
124         final String[] properties)
125     {
126         boolean equal = object1 == object2;
127         if (!equal && object1 != null && object2 != null)
128         {
129             for (int ctr = 0; ctr < properties.length; ctr++)
130             {
131                 final String property = properties[ctr];
132                 if (StringUtils.isNotBlank(property))
133                 {
134                     final Object value1 = this.getProperty(
135                             object1,
136                             property);
137                     final Object value2 = this.getProperty(
138                             object2,
139                             property);
140                     if (value1 != null && value2 != null)
141                     {
142                         equal = value1.equals(value2);
143                         if (!equal)
144                         {
145                             break;
146                         }
147                     }
148                 }
149             }
150         }
151         return equal;
152     }
153 
154     /**
155      * Updates the model (i.e. underlying managed bean's value).
156      *
157      * @param value the value from which to update the model.
158      */
159     public void updateModelValue(final Object value)
160     {
161         final ValueBinding binding = getValueBinding(VALUE);
162         if (binding != null)
163         {
164             binding.setValue(
165                 this.getFacesContext(),
166                 value);
167         }
168     }
169 
170     /**
171      * The value attribute.
172      */
173     private static final String VALUE = "value";
174 
175     /**
176      * Gets the type of the backing value attribute.
177      * @return Class The backing value's type or null if the backing value
178      *         isn't defined.
179      */
180     private Class getBackingValueType()
181     {
182         return this.getBindingType(BACKING_VALUE);
183     }
184 
185     /**
186      * Gets the value's type attribute or null if value was not defined.
187      * Tomahawk 1.1.10 or later: org.apache.myfaces.component.html.ext.HtmlDataTable.getValueType returns String, not Class
188      *
189      * @return Class the value's type or null if undefined.
190      */
191     public Class getValueType()
192     {
193         return this.getBindingType(VALUE);
194     }
195 
196     /**
197      * Gets the binding type given the attribute name.
198      *
199      * @param name the name of the component's attribute.
200      * @return type Class the binding type or null if the binding wasn't found.
201      */
202     private Class getBindingType(final String name)
203     {
204         Class type = null;
205         ValueBinding binding = getValueBinding(name);
206         if (binding != null)
207         {
208             final FacesContext context = this.getFacesContext();
209             type = binding.getType(context);
210         }
211         return type;
212     }
213 
214     private Object getProperty(
215         final Object bean,
216         final String property)
217     {
218         try
219         {
220             return PropertyUtils.getProperty(
221                 bean,
222                 property);
223         }
224         catch (final Throwable throwable)
225         {
226             throw new RuntimeException(throwable);
227         }
228     }
229 
230     private Object backingValue = null;
231 
232     /**
233      * The attribute that stores the backing value.
234      */
235     public static final String BACKING_VALUE = "backingValue";
236 
237     /**
238      * Retrieves the backing value of this extended data table (the backing value contains
239      * the values which the result of the value attribute are compared against).
240      * @return this.backingValue
241      */
242     protected Object getBackingValue()
243     {
244         if (this.backingValue == null)
245         {
246             final ValueBinding binding = this.getValueBinding(BACKING_VALUE);
247             this.backingValue = binding == null ? null : binding.getValue(getFacesContext());
248         }
249         return this.backingValue;
250     }
251 
252     /**
253      * Stores the identifier columns attribute.
254      */
255     private String identifierColumns;
256 
257     /**
258      * The attribute that stores the identifier columns.
259      */
260     public static final String IDENTIFIER_COLUMNS = "identifierColumns";
261 
262     /**
263      * Retrieves the identifier columns component attribute.
264      *
265      * @return this.identifierColumns the identifier columns component attribute.
266      */
267     protected String getIdentifierColumns()
268     {
269         if (this.identifierColumns == null)
270         {
271             this.identifierColumns = (String)this.getAttributes().get(IDENTIFIER_COLUMNS);
272             if (this.identifierColumns == null)
273             {
274                 final ValueBinding binding = this.getValueBinding(IDENTIFIER_COLUMNS);
275                 this.identifierColumns =
276                     binding == null ? null : ObjectUtils.toString(binding.getValue(getFacesContext()));
277             }
278         }
279         return this.identifierColumns;
280     }
281 }