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.custom.ExtendedComponentBase;
45
46 /**
47 * Reimplement all UIData functionality to be able to have (protected) access
48 * the internal DataModel.
49 *
50 * @author Manfred Geiler (latest modification by $Author: grantsmith $)
51 * @version $Revision: 472630 $ $Date: 2006-11-08 21:40:03 +0100 (Mi, 08 Nov 2006) $
52 */
53 public abstract class HtmlDataTableHack extends
54 javax.faces.component.html.HtmlDataTable implements ExtendedComponentBase
55 {
56 private Map _dataModelMap = new HashMap();
57
58 // will be set to false if the data should not be refreshed at the beginning of the encode phase
59 private boolean _isValidChilds = true;
60
61 // holds for each row the states of the child components of this UIData
62 private Map _rowStates = new HashMap();
63
64 // contains the initial row state which is used to initialize each row
65 private Object _initialDescendantComponentState = null;
66
67 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
68 // Every field and method from here is identical to UIData !!!!!!!!!
69 // EXCEPTION: we can create a DataModel from a Collection
70
71 private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
72
73 private static final boolean DEFAULT_PRESERVEROWSTATES = false;
74
75 private int _rowIndex = -1;
76 private Boolean _forceId;
77
78 private Boolean _preserveRowStates;
79
80 public boolean isRowAvailable()
81 {
82 return getDataModel().isRowAvailable();
83 }
84
85 public int getRowCount()
86 {
87 return getDataModel().getRowCount();
88 }
89
90 public Object getRowData()
91 {
92 return getDataModel().getRowData();
93 }
94
95 public int getRowIndex()
96 {
97 return _rowIndex;
98 }
99
100 /**
101 * Hack since RI does not call getRowIndex()
102 */
103 public String getClientId(FacesContext context)
104 {
105 String clientId = super.getClientId(context);
106 int rowIndex = getRowIndex();
107 if (rowIndex == -1)
108 {
109 return clientId;
110 }
111 // the following code tries to avoid rowindex to be twice in the client id
112 int index = clientId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
113 if(index != -1)
114 {
115 String rowIndexString = clientId.substring(index + 1);
116 try
117 {
118 if(Integer.parseInt(rowIndexString) == rowIndex)
119 {
120 return clientId;
121 }
122 }
123 catch(NumberFormatException e)
124 {
125 return clientId + NamingContainer.SEPARATOR_CHAR + rowIndex;
126 }
127 }
128 return clientId + NamingContainer.SEPARATOR_CHAR + rowIndex;
129 }
130
131 /**
132 * @see javax.faces.component.UIData#processUpdates(javax.faces.context.FacesContext)
133 */
134 public void processUpdates(FacesContext context)
135 {
136 super.processUpdates(context);
137 // check if a update model error forces the render response for our data
138 if (context.getRenderResponse())
139 {
140 _isValidChilds = false;
141 }
142 }
143
144 /**
145 * @see javax.faces.component.UIData#processValidators(javax.faces.context.FacesContext)
146 */
147 public void processValidators(FacesContext context)
148 {
149 super.processValidators(context);
150 // check if a validation error forces the render response for our data
151 if (context.getRenderResponse())
152 {
153 _isValidChilds = false;
154 }
155 }
156
157 /**
158 * @see javax.faces.component.UIData#encodeBegin(javax.faces.context.FacesContext)
159 */
160 public void encodeBegin(FacesContext context) throws IOException
161 {
162 _initialDescendantComponentState = null;
163 if (_isValidChilds && !hasErrorMessages(context))
164 {
165 //Refresh DataModel for rendering:
166 _dataModelMap.clear();
167 if (!isPreserveRowStates())
168 {
169 _rowStates.clear();
170 }
171 }
172 super.encodeBegin(context);
173 }
174
175 public void setPreserveRowStates(boolean preserveRowStates)
176 {
177 _preserveRowStates = Boolean.valueOf(preserveRowStates);
178 }
179
180 public boolean isPreserveRowStates()
181 {
182 if (_preserveRowStates != null)
183 return _preserveRowStates.booleanValue();
184 ValueBinding vb = getValueBinding("preserveRowStates");
185 Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
186 return v != null ? v.booleanValue() : DEFAULT_PRESERVEROWSTATES;
187 }
188
189 protected boolean hasErrorMessages(FacesContext context)
190 {
191 for(Iterator iter = context.getMessages(); iter.hasNext();)
192 {
193 FacesMessage message = (FacesMessage) iter.next();
194 if(FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0)
195 {
196 return true;
197 }
198 }
199 return false;
200 }
201
202 /**
203 * @see javax.faces.component.UIComponentBase#encodeEnd(javax.faces.context.FacesContext)
204 */
205 public void encodeEnd(FacesContext context) throws IOException
206 {
207 setRowIndex(-1);
208 super.encodeEnd(context);
209 }
210
211 public void setRowIndex(int rowIndex)
212 {
213 if (rowIndex < -1)
214 {
215 throw new IllegalArgumentException("rowIndex is less than -1");
216 }
217
218 if (_rowIndex == rowIndex)
219 {
220 return;
221 }
222
223 FacesContext facesContext = getFacesContext();
224
225 if (_rowIndex == -1)
226 {
227 if (_initialDescendantComponentState == null)
228 {
229 _initialDescendantComponentState = saveDescendantComponentStates(getChildren()
230 .iterator(), false);
231 }
232 }
233 else
234 {
235 _rowStates.put(getClientId(facesContext),
236 saveDescendantComponentStates(getChildren()
237 .iterator(), false));
238 }
239
240 _rowIndex = rowIndex;
241
242 DataModel dataModel = getDataModel();
243 dataModel.setRowIndex(rowIndex);
244
245 String var = getVar();
246 if (rowIndex == -1)
247 {
248 if (var != null)
249 {
250 facesContext.getExternalContext().getRequestMap().remove(var);
251 }
252 }
253 else
254 {
255 if (var != null)
256 {
257 if (isRowAvailable())
258 {
259 Object rowData = dataModel.getRowData();
260 facesContext.getExternalContext().getRequestMap().put(var,
261 rowData);
262 }
263 else
264 {
265 facesContext.getExternalContext().getRequestMap().remove(
266 var);
267 }
268 }
269 }
270
271 if (_rowIndex == -1)
272 {
273 restoreDescendantComponentStates(getChildren().iterator(),
274 _initialDescendantComponentState, false);
275 }
276 else
277 {
278 Object rowState = _rowStates.get(getClientId(facesContext));
279 if (rowState == null)
280 {
281 restoreDescendantComponentStates(getChildren().iterator(),
282 _initialDescendantComponentState, false);
283 }
284 else
285 {
286 restoreDescendantComponentStates(getChildren().iterator(),
287 rowState, false);
288 }
289 }
290 }
291
292 protected void restoreDescendantComponentStates(Iterator childIterator,
293 Object state, boolean restoreChildFacets)
294 {
295 Iterator descendantStateIterator = null;
296 while (childIterator.hasNext())
297 {
298 if (descendantStateIterator == null && state != null)
299 {
300 descendantStateIterator = ((Collection) state).iterator();
301 }
302 UIComponent component = (UIComponent) childIterator.next();
303 // reset the client id (see spec 3.1.6)
304 component.setId(component.getId());
305 if(!component.isTransient())
306 {
307 Object childState = null;
308 Object descendantState = null;
309 if (descendantStateIterator != null
310 && descendantStateIterator.hasNext())
311 {
312 Object[] object = (Object[]) descendantStateIterator.next();
313 childState = object[0];
314 descendantState = object[1];
315 }
316 if (childState != null && component instanceof EditableValueHolder)
317 {
318 ((EditableValueHolderState) childState)
319 .restoreState((EditableValueHolder) component);
320 }
321 Iterator childsIterator;
322 if (restoreChildFacets)
323 {
324 childsIterator = component.getFacetsAndChildren();
325 }
326 else
327 {
328 childsIterator = component.getChildren().iterator();
329 }
330 restoreDescendantComponentStates(childsIterator, descendantState,
331 true);
332 }
333 }
334 }
335
336 protected Object saveDescendantComponentStates(Iterator childIterator,
337 boolean saveChildFacets)
338 {
339 Collection childStates = null;
340 while (childIterator.hasNext())
341 {
342 if (childStates == null)
343 {
344 childStates = new ArrayList();
345 }
346 UIComponent child = (UIComponent) childIterator.next();
347 if(!child.isTransient())
348 {
349 Iterator childsIterator;
350 if (saveChildFacets)
351 {
352 childsIterator = child.getFacetsAndChildren();
353 }
354 else
355 {
356 childsIterator = child.getChildren().iterator();
357 }
358 Object descendantState = saveDescendantComponentStates(
359 childsIterator, true);
360 Object state = null;
361 if (child instanceof EditableValueHolder)
362 {
363 state = new EditableValueHolderState(
364 (EditableValueHolder) child);
365 }
366 childStates.add(new Object[] { state, descendantState });
367 }
368 }
369 return childStates;
370 }
371
372 public void setValueBinding(String name, ValueBinding binding)
373 {
374 if (name == null)
375 {
376 throw new NullPointerException("name");
377 }
378 else if (name.equals("value"))
379 {
380 _dataModelMap.clear();
381 }
382 else if (name.equals("var") || name.equals("rowIndex"))
383 {
384 throw new IllegalArgumentException(
385 "You can never set the 'rowIndex' or the 'var' attribute as a value-binding. Set the property directly instead. Name " + name);
386 }
387 super.setValueBinding(name, binding);
388 }
389
390 /**
391 * @see javax.faces.component.UIData#setValue(java.lang.Object)
392 */
393 public void setValue(Object value)
394 {
395 super.setValue(value);
396 _dataModelMap.clear();
397 _rowStates.clear();
398 _isValidChilds = true;
399 }
400
401 protected DataModel getDataModel()
402 {
403 DataModel dataModel = null;
404 String clientID = "";
405
406 UIComponent parent = getParent();
407 if (parent != null)
408 {
409 clientID = parent.getClientId(getFacesContext());
410 }
411 dataModel = (DataModel) _dataModelMap.get(clientID);
412 if (dataModel == null)
413 {
414 dataModel = createDataModel();
415 _dataModelMap.put(clientID, dataModel);
416 }
417
418 return dataModel;
419 }
420
421 protected void setDataModel(DataModel datamodel)
422 {
423 UIComponent parent = getParent();
424 String clientID = "";
425 if(parent != null)
426 {
427 clientID = parent.getClientId(getFacesContext());
428 }
429 _dataModelMap.put(clientID, datamodel);
430 }
431
432 /**
433 * Creates a new DataModel around the current value.
434 */
435 protected DataModel createDataModel()
436 {
437 Object value = getValue();
438 if (value == null)
439 {
440 return EMPTY_DATA_MODEL;
441 }
442 else if (value instanceof DataModel)
443 {
444 return (DataModel) value;
445 }
446 else if (value instanceof List)
447 {
448 return new ListDataModel((List) value);
449 }
450 // accept a Collection is not supported in the Spec
451 else if (value instanceof Collection)
452 {
453 return new ListDataModel(new ArrayList((Collection) value));
454 }
455 else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass()))
456 {
457 return new ArrayDataModel((Object[]) value);
458 }
459 else if (value instanceof ResultSet)
460 {
461 return new ResultSetDataModel((ResultSet) value);
462 }
463 else if (value instanceof Result)
464 {
465 return new ResultDataModel((Result) value);
466 }
467 else
468 {
469 return new ScalarDataModel(value);
470 }
471 }
472
473 public Object saveState(FacesContext context)
474 {
475 Object[] values = new Object[2];
476 values[0] = super.saveState(context);
477 values[1] = _preserveRowStates;
478 return values;
479 }
480
481 public void restoreState(FacesContext context, Object state)
482 {
483 Object[] values = (Object[])state;
484 super.restoreState(context, values[0]);
485 _preserveRowStates = (Boolean) values[1];
486 }
487
488 private static final DataModel EMPTY_DATA_MODEL = new _SerializableDataModel()
489 {
490 public boolean isRowAvailable()
491 {
492 return false;
493 }
494
495 public int getRowCount()
496 {
497 return 0;
498 }
499
500 public Object getRowData()
501 {
502 throw new IllegalArgumentException();
503 }
504
505 public int getRowIndex()
506 {
507 return -1;
508 }
509
510 public void setRowIndex(int i)
511 {
512 if (i < -1)
513 throw new IndexOutOfBoundsException("Index < 0 : " + i);
514 }
515
516 public Object getWrappedData()
517 {
518 return null;
519 }
520
521 public void setWrappedData(Object obj)
522 {
523 if (obj == null)
524 return; //Clearing is allowed
525 throw new UnsupportedOperationException(this.getClass().getName()
526 + " UnsupportedOperationException");
527 }
528 };
529
530 private class EditableValueHolderState
531 {
532 private final Object _value;
533 private final boolean _localValueSet;
534 private final boolean _valid;
535 private final Object _submittedValue;
536
537 public EditableValueHolderState(EditableValueHolder evh)
538 {
539 _value = evh.getLocalValue();
540 _localValueSet = evh.isLocalValueSet();
541 _valid = evh.isValid();
542 _submittedValue = evh.getSubmittedValue();
543 }
544
545 public void restoreState(EditableValueHolder evh)
546 {
547 evh.setValue(_value);
548 evh.setLocalValueSet(_localValueSet);
549 evh.setValid(_valid);
550 evh.setSubmittedValue(_submittedValue);
551 }
552 }
553
554 public void setForceId(boolean b)
555 {
556 _forceId = Boolean.valueOf(b);
557 }
558
559 public boolean isForceId()
560 {
561 if (_forceId != null) return _forceId.booleanValue();
562 ValueBinding vb = getValueBinding("forceId");
563 return vb != null && booleanFromObject(vb.getValue(getFacesContext()), false);
564 }
565
566 private static boolean booleanFromObject(Object obj, boolean defaultValue)
567 {
568 if(obj instanceof Boolean)
569 {
570 return ((Boolean) obj).booleanValue();
571 }
572 else if(obj instanceof String)
573 {
574 return Boolean.valueOf(((String) obj)).booleanValue();
575 }
576
577 return defaultValue;
578 }
579
580 }