001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry.components;
016
017 import java.util.ArrayList;
018 import java.util.Collections;
019 import java.util.HashMap;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.Map;
023
024 import org.apache.tapestry.IBinding;
025 import org.apache.tapestry.IForm;
026 import org.apache.tapestry.IMarkupWriter;
027 import org.apache.tapestry.IRequestCycle;
028 import org.apache.tapestry.Tapestry;
029 import org.apache.tapestry.TapestryUtils;
030 import org.apache.tapestry.coerce.ValueConverter;
031 import org.apache.tapestry.engine.NullWriter;
032 import org.apache.tapestry.form.AbstractFormComponent;
033 import org.apache.tapestry.services.DataSqueezer;
034 import org.apache.tapestry.services.ExpressionEvaluator;
035
036 /**
037 * @author mb
038 * @since 4.0
039 * @see org.apache.tapestry.components.IPrimaryKeyConverter
040 * @see org.apache.tapestry.util.DefaultPrimaryKeyConverter
041 */
042 public abstract class ForBean extends AbstractFormComponent
043 {
044 // constants
045
046 /**
047 * Prefix on the hidden value stored into the field to indicate the the actual value is stored
048 * (this is used when there is no primary key converter). The remainder of the string is a
049 * {@link DataSqueezer squeezed} representation of the value.
050 */
051 private static final char DESC_VALUE = 'V';
052
053 /**
054 * Prefix on the hidden value stored into the field that indicates the primary key of the
055 * iterated value is stored; the remainder of the string is a {@link DataSqueezer squeezed}
056 * representation of the primary key. The {@link IPrimaryKeyConverter converter} is used to
057 * obtain the value from this key.
058 */
059 private static final char DESC_PRIMARY_KEY = 'P';
060
061 private final RepSource _completeRepSource = new CompleteRepSource();
062
063 private final RepSource _keyExpressionRepSource = new KeyExpressionRepSource();
064
065 // intermediate members
066 private Object _value;
067
068 private int _index;
069
070 private boolean _rendering;
071
072 // parameters
073 public abstract String getElement();
074
075 public abstract String getKeyExpression();
076
077 public abstract IPrimaryKeyConverter getConverter();
078
079 public abstract Object getDefaultValue();
080
081 public abstract boolean getMatch();
082
083 public abstract boolean getVolatile();
084
085 // injects
086 public abstract DataSqueezer getDataSqueezer();
087
088 public abstract ValueConverter getValueConverter();
089
090 public abstract ExpressionEvaluator getExpressionEvaluator();
091
092 /**
093 * Gets the source binding and iterates through its values. For each, it updates the value
094 * binding and render's its wrapped elements.
095 */
096 protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
097 {
098 // form may be null if component is not located in a form
099 IForm form = (IForm) cycle.getAttribute(TapestryUtils.FORM_ATTRIBUTE);
100
101 // If the cycle is rewinding, but not this particular form,
102 // then do nothing (don't even render the body).
103 boolean cycleRewinding = cycle.isRewinding();
104 if (cycleRewinding && form != null && !form.isRewinding())
105 return;
106
107 // Get the data to be iterated upon. Store in form if needed.
108 Iterator dataSource = getData(cycle, form);
109
110 // Do not iterate if dataSource is null.
111 // The dataSource was either not convertable to Iterator, or was empty.
112 if (dataSource == null)
113 return;
114
115 if (!cycleRewinding && form != null && !NullWriter.class.isInstance(writer))
116 form.setFormFieldUpdating(true);
117
118 String element = getElement();
119
120 // Perform the iterations
121 try
122 {
123 _index = 0;
124 _rendering = true;
125
126 while (dataSource.hasNext())
127 {
128 // Get current value
129 _value = dataSource.next();
130
131 // Update output component parameters
132 updateOutputParameters();
133
134 // Render component
135 if (element != null)
136 {
137 writer.begin(element);
138 renderInformalParameters(writer, cycle);
139 }
140
141 renderBody(writer, cycle);
142
143 if (element != null)
144 writer.end();
145
146 _index++;
147 }
148 }
149 finally
150 {
151 _rendering = false;
152 _value = null;
153 }
154 }
155
156 /**
157 * Returns the most recent value extracted from the source parameter.
158 *
159 * @throws org.apache.tapestry.ApplicationRuntimeException
160 * if the For is not currently rendering.
161 */
162
163 public final Object getValue()
164 {
165 if (!_rendering)
166 throw Tapestry.createRenderOnlyPropertyException(this, "value");
167
168 return _value;
169 }
170
171 /**
172 * The index number, within the {@link #getSource() source}, of the the current value.
173 *
174 * @throws org.apache.tapestry.ApplicationRuntimeException
175 * if the For is not currently rendering.
176 */
177
178 public int getIndex()
179 {
180 if (!_rendering)
181 throw Tapestry.createRenderOnlyPropertyException(this, "index");
182
183 return _index;
184 }
185
186 public boolean isDisabled()
187 {
188 return false;
189 }
190
191 /**
192 * Updates the index and value output parameters if bound.
193 */
194 protected void updateOutputParameters()
195 {
196 IBinding indexBinding = getBinding("index");
197 if (indexBinding != null)
198 indexBinding.setObject(new Integer(_index));
199
200 IBinding valueBinding = getBinding("value");
201 if (valueBinding != null)
202 valueBinding.setObject(_value);
203 }
204
205 /**
206 * Updates the primaryKeys parameter if bound.
207 */
208 protected void updatePrimaryKeysParameter(String[] stringReps)
209 {
210 IBinding primaryKeysBinding = getBinding("primaryKeys");
211 if (primaryKeysBinding == null)
212 return;
213
214 DataSqueezer squeezer = getDataSqueezer();
215
216 int repsCount = stringReps.length;
217 List primaryKeys = new ArrayList(repsCount);
218 for (int i = 0; i < stringReps.length; i++)
219 {
220 String rep = stringReps[i];
221 if (rep.length() == 0 || rep.charAt(0) != DESC_PRIMARY_KEY)
222 continue;
223 Object primaryKey = squeezer.unsqueeze(rep.substring(1));
224 primaryKeys.add(primaryKey);
225 }
226
227 primaryKeysBinding.setObject(primaryKeys);
228 }
229
230 // Do nothing in those methods, but make the JVM happy
231 protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
232 {
233 }
234
235 protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
236 {
237 }
238
239 /**
240 * Returns a list with the values to be iterated upon. The list is obtained in different ways: -
241 * If the component is not located in a form or 'volatile' is set to true, then the simply the
242 * values passed to 'source' are returned (same as Foreach) - If the component is in a form, and
243 * the form is rewinding, the values stored in the form are returned -- rewind is then always
244 * the same as render. - If the component is in a form, and the form is being rendered, the
245 * values are stored in the form as Hidden fields.
246 *
247 * @param cycle
248 * The current request cycle
249 * @param form
250 * The form within which the component is located (if any)
251 * @return An iterator with the values to be cycled upon
252 */
253 private Iterator getData(IRequestCycle cycle, IForm form)
254 {
255 if (form == null || getVolatile())
256 return evaluateSourceIterator();
257
258 String name = form.getElementId(this);
259 if (cycle.isRewinding())
260 return getStoredData(cycle, name);
261 return storeSourceData(form, name);
262 }
263
264 /**
265 * Returns a list of the values stored as Hidden fields in the form. A conversion is performed
266 * if the primary key of the value is stored.
267 *
268 * @param cycle
269 * The current request cycle
270 * @param name
271 * The name of the HTTP parameter whether the values
272 * @return an iterator with the values stored in the provided Hidden fields
273 */
274 protected Iterator getStoredData(IRequestCycle cycle, String name)
275 {
276 String[] stringReps = cycle.getParameters(name);
277 if (stringReps == null)
278 return null;
279
280 updatePrimaryKeysParameter(stringReps);
281
282 return new ReadSourceDataIterator(stringReps);
283 }
284
285 /**
286 * Pulls data from successive strings (posted by client-side hidden fields); each string
287 * representation may be either a value or a primary key.
288 */
289 private class ReadSourceDataIterator implements Iterator
290 {
291 private final Iterator _sourceIterator = evaluateSourceIterator();
292
293 private final Iterator _fullSourceIterator = evaluateFullSourceIterator();
294
295 private final String[] _stringReps;
296
297 private int _index = 0;
298
299 private final Map _repToValueMap = new HashMap();
300
301 ReadSourceDataIterator(String[] stringReps)
302 {
303 _stringReps = stringReps;
304 }
305
306 public boolean hasNext()
307 {
308 return _index < _stringReps.length;
309 }
310
311 public Object next()
312 {
313 String rep = _stringReps[_index++];
314
315 return getValueFromStringRep(_sourceIterator, _fullSourceIterator, _repToValueMap, rep);
316 }
317
318 public void remove()
319 {
320 throw new UnsupportedOperationException("remove()");
321 }
322
323 }
324
325 /**
326 * Stores the provided data in the form and then returns the data as an iterator. If the primary
327 * key of the value can be determined, then that primary key is saved instead.
328 *
329 * @param form
330 * The form where the data will be stored
331 * @param name
332 * The name under which the data will be stored
333 * @return an iterator with the bound values stored in the form
334 */
335 protected Iterator storeSourceData(IForm form, String name)
336 {
337 return new StoreSourceDataIterator(form, name, evaluateSourceIterator());
338 }
339
340 /**
341 * Iterates over a set of values, using {@link ForBean#getStringRepFromValue(Object)} to obtain
342 * the correct client-side string representation, and working with the form to store each
343 * successive value into the form.
344 */
345 private class StoreSourceDataIterator implements Iterator
346 {
347 private final IForm _form;
348
349 private final String _name;
350
351 private final Iterator _delegate;
352
353 StoreSourceDataIterator(IForm form, String name, Iterator delegate)
354 {
355 _form = form;
356 _name = name;
357 _delegate = delegate;
358 }
359
360 public boolean hasNext()
361 {
362 return _delegate.hasNext();
363 }
364
365 public Object next()
366 {
367 Object value = _delegate.next();
368
369 String rep = getStringRepFromValue(value);
370
371 _form.addHiddenValue(_name, rep);
372
373 return value;
374 }
375
376 public void remove()
377 {
378 throw new UnsupportedOperationException("remove()");
379 }
380 }
381
382 /**
383 * Returns the string representation of the value. The first letter of the string representation
384 * shows whether a value or a primary key is being described.
385 *
386 * @param value
387 * @return
388 */
389 protected String getStringRepFromValue(Object value)
390 {
391 String rep;
392 DataSqueezer squeezer = getDataSqueezer();
393
394 // try to extract the primary key from the value
395 Object pk = getPrimaryKeyFromValue(value);
396 if (pk != null)
397 // Primary key was extracted successfully.
398 rep = DESC_PRIMARY_KEY + squeezer.squeeze(pk);
399 else
400 // primary key could not be extracted. squeeze value.
401 rep = DESC_VALUE + squeezer.squeeze(value);
402
403 return rep;
404 }
405
406 /**
407 * Returns the primary key of the given value. Uses the 'keyExpression' or the 'converter' (if
408 * either is provided).
409 *
410 * @param value
411 * The value from which the primary key should be extracted
412 * @return The primary key of the value, or null if such cannot be extracted.
413 */
414 protected Object getPrimaryKeyFromValue(Object value)
415 {
416 if (value == null)
417 return null;
418
419 Object primaryKey = getKeyExpressionFromValue(value);
420 if (primaryKey == null)
421 primaryKey = getConverterFromValue(value);
422
423 return primaryKey;
424 }
425
426 /**
427 * Uses the 'keyExpression' parameter to determine the primary key of the given value.
428 *
429 * @param value
430 * The value from which the primary key should be extracted
431 * @return The primary key of the value as defined by 'keyExpression', or null if such cannot be
432 * extracted.
433 */
434 protected Object getKeyExpressionFromValue(Object value)
435 {
436 String keyExpression = getKeyExpression();
437 if (keyExpression == null)
438 return null;
439
440 Object primaryKey = getExpressionEvaluator().read(value, keyExpression);
441 return primaryKey;
442 }
443
444 /**
445 * Uses the 'converter' parameter to determine the primary key of the given value.
446 *
447 * @param value
448 * The value from which the primary key should be extracted
449 * @return The primary key of the value as provided by the converter, or null if such cannot be
450 * extracted.
451 */
452 protected Object getConverterFromValue(Object value)
453 {
454 IPrimaryKeyConverter converter = getConverter();
455 if (converter == null)
456 return null;
457
458 Object primaryKey = converter.getPrimaryKey(value);
459 return primaryKey;
460 }
461
462 /**
463 * Determines the value that corresponds to the given string representation. If the 'match'
464 * parameter is true, attempt to find a value in 'source' or 'fullSource' that generates the
465 * same string representation. Otherwise, create a new value from the string representation.
466 *
467 * @param rep
468 * the string representation for which a value should be returned
469 * @return the value that corresponds to the provided string representation
470 */
471 protected Object getValueFromStringRep(Iterator sourceIterator, Iterator fullSourceIterator,
472 Map repToValueMap, String rep)
473 {
474 Object value = null;
475 DataSqueezer squeezer = getDataSqueezer();
476
477 // Check if the string rep is empty. If so, just return the default value.
478 if (rep == null || rep.length() == 0)
479 return getDefaultValue();
480
481 // If required, find a value with an equivalent string representation and return it
482 boolean match = getMatch();
483 if (match)
484 {
485 value = findValueWithStringRep(
486 sourceIterator,
487 fullSourceIterator,
488 repToValueMap,
489 rep,
490 _completeRepSource);
491 if (value != null)
492 return value;
493 }
494
495 // Matching of the string representation was not successful or was disabled.
496 // Use the standard approaches to obtain the value from the rep.
497 char desc = rep.charAt(0);
498 String squeezed = rep.substring(1);
499 switch (desc)
500 {
501 case DESC_VALUE:
502 // If the string rep is just the value itself, unsqueeze it
503 value = squeezer.unsqueeze(squeezed);
504 break;
505
506 case DESC_PRIMARY_KEY:
507 // Perform keyExpression match if not already attempted
508 if (!match && getKeyExpression() != null)
509 value = findValueWithStringRep(
510 sourceIterator,
511 fullSourceIterator,
512 repToValueMap,
513 rep,
514 _keyExpressionRepSource);
515
516 // If 'converter' is defined, try to perform conversion from primary key to value
517 if (value == null)
518 {
519 IPrimaryKeyConverter converter = getConverter();
520 if (converter != null)
521 {
522 Object pk = squeezer.unsqueeze(squeezed);
523 value = converter.getValue(pk);
524 }
525 }
526 break;
527 }
528
529 if (value == null)
530 value = getDefaultValue();
531
532 return value;
533 }
534
535 /**
536 * Attempt to find a value in 'source' or 'fullSource' that generates the provided string
537 * representation. Use the RepSource interface to determine what the string representation of a
538 * particular value is.
539 *
540 * @param rep
541 * the string representation for which a value should be returned
542 * @param repSource
543 * an interface providing the string representation of a given value
544 * @return the value in 'source' or 'fullSource' that corresponds to the provided string
545 * representation
546 */
547 protected Object findValueWithStringRep(Iterator sourceIterator, Iterator fullSourceIterator,
548 Map repToValueMap, String rep, RepSource repSource)
549 {
550 Object value = repToValueMap.get(rep);
551 if (value != null)
552 return value;
553
554 value = findValueWithStringRepInIterator(sourceIterator, repToValueMap, rep, repSource);
555 if (value != null)
556 return value;
557
558 value = findValueWithStringRepInIterator(fullSourceIterator, repToValueMap, rep, repSource);
559 return value;
560 }
561
562 /**
563 * Attempt to find a value in the provided collection that generates the required string
564 * representation. Use the RepSource interface to determine what the string representation of a
565 * particular value is.
566 *
567 * @param rep
568 * the string representation for which a value should be returned
569 * @param repSource
570 * an interface providing the string representation of a given value
571 * @param it
572 * the iterator of the collection in which a value should be searched
573 * @return the value in the provided collection that corresponds to the required string
574 * representation
575 */
576 protected Object findValueWithStringRepInIterator(Iterator it, Map repToValueMap, String rep,
577 RepSource repSource)
578 {
579 while (it.hasNext())
580 {
581 Object sourceValue = it.next();
582 if (sourceValue == null)
583 continue;
584
585 String sourceRep = repSource.getStringRep(sourceValue);
586 repToValueMap.put(sourceRep, sourceValue);
587
588 if (rep.equals(sourceRep))
589 return sourceValue;
590 }
591
592 return null;
593 }
594
595 /**
596 * Returns a new iterator of the values in 'source'.
597 *
598 * @return the 'source' iterator
599 */
600 protected Iterator evaluateSourceIterator()
601 {
602 Iterator it = null;
603 Object source = null;
604
605 IBinding sourceBinding = getBinding("source");
606 if (sourceBinding != null)
607 source = sourceBinding.getObject();
608
609 if (source != null)
610 it = (Iterator) getValueConverter().coerceValue(source, Iterator.class);
611
612 if (it == null)
613 it = Collections.EMPTY_LIST.iterator();
614
615 return it;
616 }
617
618 /**
619 * Returns a new iterator of the values in 'fullSource'.
620 *
621 * @return the 'fullSource' iterator
622 */
623 protected Iterator evaluateFullSourceIterator()
624 {
625 Iterator it = null;
626 Object fullSource = null;
627
628 IBinding fullSourceBinding = getBinding("fullSource");
629 if (fullSourceBinding != null)
630 fullSource = fullSourceBinding.getObject();
631
632 if (fullSource != null)
633 it = (Iterator) getValueConverter().coerceValue(fullSource, Iterator.class);
634
635 if (it == null)
636 it = Collections.EMPTY_LIST.iterator();
637
638 return it;
639 }
640
641 /**
642 * An interface that provides the string representation of a given value.
643 */
644 protected interface RepSource
645 {
646 String getStringRep(Object value);
647 }
648
649 /**
650 * An implementation of RepSource that provides the string representation of the given value
651 * using all methods.
652 */
653 protected class CompleteRepSource implements RepSource
654 {
655 public String getStringRep(Object value)
656 {
657 return getStringRepFromValue(value);
658 }
659 }
660
661 /**
662 * An implementation of RepSource that provides the string representation of the given value
663 * using just the 'keyExpression' parameter.
664 */
665 protected class KeyExpressionRepSource implements RepSource
666 {
667 public String getStringRep(Object value)
668 {
669 Object pk = getKeyExpressionFromValue(value);
670 return DESC_PRIMARY_KEY + getDataSqueezer().squeeze(pk);
671 }
672 }
673
674 /**
675 * For component can not take focus.
676 */
677 protected boolean getCanTakeFocus()
678 {
679 return false;
680 }
681
682 public String getClientId()
683 {
684 return null;
685 }
686
687 public String getDisplayName()
688 {
689 return null;
690 }
691
692 }