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.component.html.ext;
20  
21  import java.io.IOException;
22  import java.sql.ResultSet;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  
30  import javax.faces.application.FacesMessage;
31  import javax.faces.component.EditableValueHolder;
32  import javax.faces.component.NamingContainer;
33  import javax.faces.component.UIComponent;
34  import javax.faces.context.FacesContext;
35  import javax.faces.el.ValueBinding;
36  import javax.faces.model.ArrayDataModel;
37  import javax.faces.model.DataModel;
38  import javax.faces.model.ListDataModel;
39  import javax.faces.model.ResultDataModel;
40  import javax.faces.model.ResultSetDataModel;
41  import javax.faces.model.ScalarDataModel;
42  import javax.servlet.jsp.jstl.sql.Result;
43  
44  import org.apache.myfaces.component.html.util.HtmlComponentUtils;
45  import org.apache.myfaces.custom.ExtendedComponentBase;
46  
47  /**
48   * Reimplement all UIData functionality to be able to have (protected) access
49   * the internal DataModel.
50   *
51   * @JSFComponent
52   *  configExcluded = "true"
53   *
54   * @author Manfred Geiler (latest modification by $Author: lu4242 $)
55   * @version $Revision: 1327684 $ $Date: 2012-04-18 16:07:35 -0500 (Wed, 18 Apr 2012) $
56   */
57  public abstract class HtmlDataTableHack extends
58                  javax.faces.component.html.HtmlDataTable implements
59                  ExtendedComponentBase
60                  
61  {
62      private Map _dataModelMap = new HashMap();
63  
64      // will be set to false if the data should not be refreshed at the beginning of the encode phase
65      private boolean _isValidChilds = true;
66  
67      // holds for each row the states of the child components of this UIData
68      private Map _rowStates = new HashMap();
69  
70      // contains the initial row state which is used to initialize each row
71      private Object _initialDescendantComponentState = null;
72  
73      // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
74      // Every field and method from here is identical to UIData !!!!!!!!!
75      // EXCEPTION: we can create a DataModel from a Collection
76  
77      private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
78  
79      private static final boolean DEFAULT_PRESERVEROWSTATES = false;
80      
81      private static final String UNIQUE_ROW_ID_PREFIX = "r_id_";
82  
83      private int _rowIndex = -1;
84  
85      private Boolean _preserveRowStates;
86  
87      public boolean isRowAvailable()
88      {
89          return getDataModel().isRowAvailable();
90      }
91  
92      public int getRowCount()
93      {
94          return getDataModel().getRowCount();
95      }
96  
97      public Object getRowData()
98      {
99          return getDataModel().getRowData();
100     }
101 
102     public int getRowIndex()
103     {
104         return _rowIndex;
105     }
106     
107     /**
108      * Hack since RI does not call getRowIndex()
109      */
110     public String getClientId(FacesContext context)
111     {
112         String clientId = HtmlComponentUtils.getClientId(this, getRenderer(context), context);
113         if (clientId == null)
114         {
115             clientId = super.getClientId(context);
116         }
117         int rowIndex = getRowIndex();
118         if (rowIndex == -1)
119         {
120             return clientId;
121         }
122         
123         // the following code tries to avoid rowindex to be twice in the client id
124         int index = clientId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
125         if(index != -1)
126         {
127             String rowIndexString = clientId.substring(index + 1);
128             try
129             {
130                 if(Integer.parseInt(rowIndexString) == rowIndex)
131                 {
132                     return clientId.substring(0, index+1) + getDerivedSubClientId();
133                 }
134             }
135             catch(NumberFormatException e)
136             {
137                 return clientId + NamingContainer.SEPARATOR_CHAR + getDerivedSubClientId();
138             }
139         }
140         return clientId + NamingContainer.SEPARATOR_CHAR + getDerivedSubClientId();
141     }
142 
143     /**
144      * @see javax.faces.component.UIData#processUpdates(javax.faces.context.FacesContext)
145      */
146     public void processUpdates(FacesContext context)
147     {
148         super.processUpdates(context);
149         // check if a update model error forces the render response for our data
150         if (context.getRenderResponse())
151         {
152             _isValidChilds = false;
153         }
154     }
155     
156     /**
157      * This method is used when a custom processUpdates and processValidators
158      * is defined, to check if a update model error forces the render 
159      * response for our data, because _isValidChilds is a private field
160      * and is not available on child components that inherits this 
161      * component class like t:dataList. (TOMAHAWK-1225)
162      */
163     protected void checkUpdateModelError(FacesContext context)
164     {
165         if (context.getRenderResponse())
166         {
167             _isValidChilds = false;
168         }        
169     }
170 
171     /**
172      * @see javax.faces.component.UIData#processValidators(javax.faces.context.FacesContext)
173      */
174     public void processValidators(FacesContext context)
175     {
176         super.processValidators(context);
177         // check if a validation error forces the render response for our data
178         if (context.getRenderResponse())
179         {
180             _isValidChilds = false;
181         }
182     }
183 
184     /**
185      * @see javax.faces.component.UIData#encodeBegin(javax.faces.context.FacesContext)
186      */
187     public void encodeBegin(FacesContext context) throws IOException
188     {
189         _initialDescendantComponentState = null;
190         if (_isValidChilds && !hasErrorMessages(context))
191         {
192             //Refresh DataModel for rendering:
193             _dataModelMap.clear();
194             if (!isPreserveRowStates())
195             {
196                 _rowStates.clear();
197             }
198         }
199         super.encodeBegin(context);
200     }
201 
202     public void setPreserveRowStates(boolean preserveRowStates)
203     {
204         _preserveRowStates = Boolean.valueOf(preserveRowStates);
205     }
206 
207     /**
208      * Indicates whether the state for each row should not be 
209      * discarded before the datatable is rendered again. 
210      * 
211      * Setting this to true might be hepful if an input 
212      * component inside the datatable has no valuebinding and 
213      * the value entered in there should be displayed again.
214      *  
215      * This will only work reliable if the datamodel of the 
216      * datatable did not change either by sorting, removing or 
217      * adding rows. Default: false
218      * 
219      * @JSFProperty
220      *   defaultValue="false"
221      */
222     public boolean isPreserveRowStates()
223     {
224         if (_preserveRowStates != null)
225             return _preserveRowStates.booleanValue();
226         ValueBinding vb = getValueBinding("preserveRowStates");
227         Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
228         return v != null ? v.booleanValue() : DEFAULT_PRESERVEROWSTATES;
229     }
230 
231     protected boolean hasErrorMessages(FacesContext context)
232     {
233         for(Iterator iter = context.getMessages(); iter.hasNext();)
234         {
235             FacesMessage message = (FacesMessage) iter.next();
236             if(FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0)
237             {
238                 return true;
239             }
240         }
241         return false;
242     }
243 
244     /**
245      * @see javax.faces.component.UIComponentBase#encodeEnd(javax.faces.context.FacesContext)
246      */
247     public void encodeEnd(FacesContext context) throws IOException
248     {
249         setRowIndex(-1);
250         super.encodeEnd(context);
251     }
252 
253     public void setRowIndex(int rowIndex)
254     {
255         if (rowIndex < -1)
256         {
257             throw new IllegalArgumentException("rowIndex is less than -1");
258         }
259 
260         if (_rowIndex == rowIndex)
261         {
262             return;
263         }
264 
265         FacesContext facesContext = getFacesContext();
266 
267         if (_rowIndex == -1)
268         {
269             if (_initialDescendantComponentState == null)
270             {
271                 _initialDescendantComponentState = saveDescendantComponentStates();
272             }
273         }
274         else
275         {
276             _rowStates.put(getClientId(facesContext),
277                             saveDescendantComponentStates());
278         }
279 
280         _rowIndex = rowIndex;
281 
282         DataModel dataModel = getDataModel();
283         dataModel.setRowIndex(rowIndex);
284 
285         String var = getVar();
286         if (rowIndex == -1)
287         {
288             if (var != null)
289             {
290                 facesContext.getExternalContext().getRequestMap().remove(var);
291             }
292         }
293         else
294         {
295             if (var != null)
296             {
297                 if (isRowAvailable())
298                 {
299                     Object rowData = dataModel.getRowData();
300                     facesContext.getExternalContext().getRequestMap().put(var,
301                                     rowData);
302                 }
303                 else
304                 {
305                     facesContext.getExternalContext().getRequestMap().remove(
306                                     var);
307                 }
308             }
309         }
310 
311         if (_rowIndex == -1)
312         {
313             restoreDescendantComponentStates(_initialDescendantComponentState);
314         }
315         else
316         {
317             Object rowState = _rowStates.get(getClientId(facesContext));
318             if (rowState == null)
319             {
320                 restoreDescendantComponentStates(_initialDescendantComponentState);
321             }
322             else
323             {
324                 restoreDescendantComponentStates(rowState);
325             }
326         }
327     }
328 
329     protected void restoreDescendantComponentStates(Object state)
330     {
331         restoreDescendantComponentStates(getChildren().iterator(), state, false);
332     }
333 
334     protected void restoreDescendantComponentStates(Iterator childIterator,
335             Object state, boolean restoreChildFacets)
336     {
337         Iterator descendantStateIterator = null;
338         while (childIterator.hasNext())
339         {
340             if (descendantStateIterator == null && state != null)
341             {
342                 descendantStateIterator = ((Collection) state).iterator();
343             }
344             UIComponent component = (UIComponent) childIterator.next();
345             // reset the client id (see spec 3.1.6)
346             component.setId(component.getId());
347             if(!component.isTransient())
348             {
349                 Object childState = null;
350                 Object descendantState = null;
351                 if (descendantStateIterator != null
352                         && descendantStateIterator.hasNext())
353                 {
354                     Object[] object = (Object[]) descendantStateIterator.next();
355                     childState = object[0];
356                     descendantState = object[1];
357                 }
358                 if (childState != null && component instanceof EditableValueHolder)
359                 {
360                     ((EditableValueHolderState) childState)
361                             .restoreState((EditableValueHolder) component);
362                 }
363                 Iterator childsIterator;
364                 if (restoreChildFacets)
365                 {
366                     childsIterator = component.getFacetsAndChildren();
367                 }
368                 else
369                 {
370                     childsIterator = component.getChildren().iterator();
371                 }
372                 restoreDescendantComponentStates(childsIterator, descendantState,
373                         true);
374             }
375         }
376     }
377 
378     protected Object saveDescendantComponentStates()
379     {
380         return saveDescendantComponentStates(getChildren().iterator(), false);
381     }
382 
383     protected Object saveDescendantComponentStates(Iterator childIterator,
384             boolean saveChildFacets)
385     {
386         Collection childStates = null;
387         while (childIterator.hasNext())
388         {
389             if (childStates == null)
390             {
391                 childStates = new ArrayList();
392             }
393             UIComponent child = (UIComponent) childIterator.next();
394             if(!child.isTransient())
395             {
396                 Iterator childsIterator;
397                 if (saveChildFacets)
398                 {
399                     childsIterator = child.getFacetsAndChildren();
400                 }
401                 else
402                 {
403                     childsIterator = child.getChildren().iterator();
404                 }
405                 Object descendantState = saveDescendantComponentStates(
406                         childsIterator, true);
407                 Object state = null;
408                 if (child instanceof EditableValueHolder)
409                 {
410                     state = new EditableValueHolderState(
411                             (EditableValueHolder) child);
412                 }
413                 childStates.add(new Object[] { state, descendantState });
414             }
415         }
416         return childStates;
417     }
418 
419     public void setValueBinding(String name, ValueBinding binding)
420     {
421         if (name == null)
422         {
423             throw new NullPointerException("name");
424         }
425         else if (name.equals("value"))
426         {
427             _dataModelMap.clear();
428         }
429         else if (name.equals("var") || name.equals("rowIndex"))
430         {
431             throw new IllegalArgumentException(
432                     "You can never set the 'rowIndex' or the 'var' attribute as a value-binding. Set the property directly instead. Name " + name);
433         }
434         super.setValueBinding(name, binding);
435     }
436 
437     /**
438      * @see javax.faces.component.UIData#setValue(java.lang.Object)
439      */
440     public void setValue(Object value)
441     {
442         super.setValue(value);
443         _dataModelMap.clear();
444         _rowStates.clear();
445         _isValidChilds = true;
446     }
447 
448     protected DataModel getDataModel()
449     {
450         DataModel dataModel = null;
451         String clientID = "";
452         
453         UIComponent parent = getParent();
454         if (parent != null) 
455         {
456             clientID = parent.getClientId(getFacesContext());
457         }
458         dataModel = (DataModel) _dataModelMap.get(clientID);
459         if (dataModel == null)
460         {
461             dataModel = createDataModel();
462             _dataModelMap.put(clientID, dataModel);
463         }               
464         
465         return dataModel;
466     }
467 
468     protected void setDataModel(DataModel datamodel)
469     {
470         UIComponent parent = getParent();
471         String clientID = "";
472         if(parent != null)
473         {
474             clientID = parent.getClientId(getFacesContext());
475         }
476         _dataModelMap.put(clientID, datamodel);
477     }
478 
479     /**
480      * Creates a new DataModel around the current value.
481      */
482     protected DataModel createDataModel()
483     {
484         Object value = getValue();
485         if (value == null)
486         {
487             return EMPTY_DATA_MODEL;
488         }
489         else if (value instanceof DataModel)
490         {
491             return (DataModel) value;
492         }
493         else if (value instanceof List)
494         {
495             return new ListDataModel((List) value);
496         }
497         // accept a Collection is not supported in the Spec
498         else if (value instanceof Collection)
499         {
500             return new ListDataModel(new ArrayList((Collection) value));
501         }
502         else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass()))
503         {
504             return new ArrayDataModel((Object[]) value);
505         }
506         else if (value instanceof ResultSet)
507         {
508             return new ResultSetDataModel((ResultSet) value);
509         }
510         else if (value instanceof Result)
511         {
512             return new ResultDataModel((Result) value);
513         }
514         else
515         {
516             return new ScalarDataModel(value);
517         }
518     }
519     
520     public Object saveState(FacesContext context)
521     {
522         Object[] values = new Object[5];
523         values[0] = super.saveState(context);
524         values[1] = _preserveRowStates;
525         values[2] = _forceId;
526         values[3] = _forceIdIndex;
527         values[4] = _derivedRowKeyPrefix;
528         return values;
529     }
530     
531     public void restoreState(FacesContext context, Object state)
532     {
533         Object[] values = (Object[])state;
534         super.restoreState(context, values[0]);
535         _preserveRowStates = (Boolean) values[1];
536         _forceId = (Boolean) values[2];
537         _forceIdIndex = (Boolean) values[3];
538         _derivedRowKeyPrefix = (String) values[4];
539     }
540 
541     private static final DataModel EMPTY_DATA_MODEL = new _SerializableDataModel()
542     {
543         public boolean isRowAvailable()
544         {
545             return false;
546         }
547 
548         public int getRowCount()
549         {
550             return 0;
551         }
552 
553         public Object getRowData()
554         {
555             throw new IllegalArgumentException();
556         }
557 
558         public int getRowIndex()
559         {
560             return -1;
561         }
562 
563         public void setRowIndex(int i)
564         {
565             if (i < -1)
566                 throw new IndexOutOfBoundsException("Index < 0 : " + i);
567         }
568 
569         public Object getWrappedData()
570         {
571             return null;
572         }
573 
574         public void setWrappedData(Object obj)
575         {
576             if (obj == null)
577                 return; //Clearing is allowed
578             throw new UnsupportedOperationException(this.getClass().getName()
579                             + " UnsupportedOperationException");
580         }
581     };
582 
583     private class EditableValueHolderState
584     {
585         private final Object _value;
586         private final boolean _localValueSet;
587         private final boolean _valid;
588         private final Object _submittedValue;
589 
590         public EditableValueHolderState(EditableValueHolder evh)
591         {
592             _value = evh.getLocalValue();
593             _localValueSet = evh.isLocalValueSet();
594             _valid = evh.isValid();
595             _submittedValue = evh.getSubmittedValue();
596         }
597 
598         public void restoreState(EditableValueHolder evh)
599         {
600             evh.setValue(_value);
601             evh.setLocalValueSet(_localValueSet);
602             evh.setValid(_valid);
603             evh.setSubmittedValue(_submittedValue);
604         }
605     }
606     
607     // Property: forceId
608     private Boolean _forceId  = Boolean.valueOf(false);
609     
610     /**
611      * If true, this component will force the use of the specified id when rendering.
612      * 
613      * @JSFProperty
614      *   literalOnly = "true"
615      *   defaultValue = "false"
616      *   
617      * @return
618      */
619     public boolean isForceId()
620     {
621         return _forceId.booleanValue();
622     }
623 
624     public void setForceId(boolean forceId)
625     {
626         this._forceId = Boolean.valueOf(forceId);
627     }
628     // Property: forceIdIndex
629     private Boolean _forceIdIndex  = Boolean.valueOf(true);
630     
631     /**
632      * If false, this component will not append a '[n]' suffix 
633      * (where 'n' is the row index) to components that are 
634      * contained within a "list." This value will be true by 
635      * default and the value will be ignored if the value of 
636      * forceId is false (or not specified.)
637      * 
638      * @JSFProperty
639      *   literalOnly = "true"
640      *   defaultValue = "true"
641      *   
642      * @return
643      */
644     public boolean isForceIdIndex()
645     {
646         return _forceIdIndex.booleanValue();
647     }
648 
649     public void setForceIdIndex(boolean forceIdIndex)
650     {
651         this._forceIdIndex = Boolean.valueOf(forceIdIndex);
652     }
653     
654     private static boolean booleanFromObject(Object obj, boolean defaultValue)
655     {
656         if(obj instanceof Boolean)
657         {
658             return ((Boolean) obj).booleanValue();
659         }
660         else if(obj instanceof String)
661         {
662             return Boolean.valueOf(((String) obj)).booleanValue();
663         }
664 
665         return defaultValue;
666     }
667 
668     /**
669      * Remove all preserved row state for the dataTable
670      */
671     public void clearRowStates()
672     {
673         _rowStates.clear();
674     }
675     
676     /**
677      * Remove preserved row state for deleted row and adjust row state to reflect deleted row.
678      * @param deletedIndex index of row to delete
679      */
680     public void deleteRowStateForRow(int deletedIndex)
681     {
682         // save row index
683         int savedRowIndex = getRowIndex();
684         
685         FacesContext facesContext = getFacesContext();
686          setRowIndex(deletedIndex);
687         String currentRowStateKey = getClientId(facesContext);
688 
689         Object rowKey = getRowKey(); 
690         if (rowKey != null)
691         {
692             setRowIndex(deletedIndex);
693             _rowStates.remove(currentRowStateKey);
694             setRowIndex(savedRowIndex);
695         }
696         else
697         {
698             // copy next rowstate to current row for each row from deleted row onward.
699             int rowCount = getRowCount();
700             for (int index = deletedIndex + 1; index < rowCount; ++index)
701             {
702                 setRowIndex(index);
703                 String nextRowStateKey = getClientId(facesContext);
704 
705                 Object nextRowState = _rowStates.get(nextRowStateKey);
706                 if (nextRowState == null)
707                 {
708                     _rowStates.remove(currentRowStateKey);
709                 }
710                 else
711                 {
712                     _rowStates.put(currentRowStateKey, nextRowState);
713                 }
714                 currentRowStateKey = nextRowStateKey;
715             }
716 
717             // Remove last row
718             _rowStates.remove(currentRowStateKey);
719 
720             // restore saved row index
721             setRowIndex(savedRowIndex);
722         }
723     }
724     
725     //Since it should be unique, no need to store it as a local var
726     //private Object _rowKey;
727     
728     /**
729      * Used to assign a value expression that identify in a unique way a row. This value
730      * will be used later instead of rowIndex as a key to be appended to the container 
731      * client id using getDerivedSubClientId() method.  
732      *
733      * @JSFProperty
734      * @return
735      */
736     public Object getRowKey()
737     {
738         //if (_rowKey != null)
739         //{
740         //    return _rowKey;
741         //}
742         ValueBinding vb = getValueBinding("rowKey");
743         if (vb != null)
744         {
745             Object value = vb.getValue(getFacesContext());
746             if (value == null)
747             {
748                 return null;
749             }
750             else
751             {
752                 return (Object) value;
753             }
754         }
755         return null;
756     }
757     
758     public void setRowKey(Object rowKey)
759     {
760         //_rowKey = rowKey;
761     }
762     
763     private String _derivedRowKeyPrefix;
764     
765     /**
766      * This attribute is used to append an unique prefix when rowKey is not used, to prevent
767      * a key match a existing component id (note two different components can't have the
768      * same unique id).
769      * 
770      * @JSFProperty defaultValue="r_id_"
771      * @return
772      */
773     public String getDerivedRowKeyPrefix()
774     {
775         if (_derivedRowKeyPrefix != null)
776         {
777             return _derivedRowKeyPrefix;
778         }
779         ValueBinding vb = getValueBinding("derivedRowKeyPrefix");
780         if (vb != null)
781         {
782             Object value = vb.getValue(getFacesContext());
783             if (value == null)
784             {
785                 return UNIQUE_ROW_ID_PREFIX;
786             }
787             else
788             {
789                 return (String) value.toString();
790             }
791         }
792         return UNIQUE_ROW_ID_PREFIX;
793     }
794 
795     public void setDerivedRowKeyPrefix(String derivedRowKeyPrefix)
796     {
797         this._derivedRowKeyPrefix = derivedRowKeyPrefix;
798     }
799 
800     /**
801      * Return the fragment to be used on the container client id to
802      * identify a row. As a side effect, it will be used to indicate 
803      * a row component state and a datamodel in nested datatable case.
804      * 
805      * <p>
806      * The returned value must comply with the following rules:
807      * </p>
808      * <ul>
809      * <li> Can be followed by: letters (A-Za-z), digits (0-9), hyphens ("-"), 
810      *   underscores ("_"), colons (":"), and periods (".") </li>
811      * <li> Values are case-sensitive </li>
812      * </ul>
813      * 
814      * @return
815      */
816     protected String getDerivedSubClientId()
817     {
818         Object key = getRowKey();
819         if (key == null)
820         {
821             return _SubIdConverter.encode(Integer.toString(getRowIndex()));
822         }
823         else
824         {
825             return getDerivedRowKeyPrefix() + _SubIdConverter.encode(key.toString());
826         }
827     }
828 
829 }