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