1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.internal.renderkit.renderer;
21
22 import org.apache.myfaces.tobago.internal.component.AbstractUISelectManyBase;
23 import org.apache.myfaces.tobago.internal.util.ArrayUtils;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import javax.el.ValueExpression;
28 import javax.faces.FacesException;
29 import javax.faces.application.ProjectStage;
30 import javax.faces.component.UIComponent;
31 import javax.faces.component.UIInput;
32 import javax.faces.component.UISelectItem;
33 import javax.faces.component.UISelectItems;
34 import javax.faces.component.UISelectMany;
35 import javax.faces.component.UIViewRoot;
36 import javax.faces.context.FacesContext;
37 import javax.faces.convert.Converter;
38 import javax.faces.convert.ConverterException;
39 import javax.faces.model.SelectItem;
40 import javax.faces.model.SelectItemGroup;
41 import java.lang.invoke.MethodHandles;
42 import java.lang.reflect.Array;
43 import java.lang.reflect.Method;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collection;
47 import java.util.HashSet;
48 import java.util.Iterator;
49 import java.util.LinkedList;
50 import java.util.Map;
51 import java.util.NoSuchElementException;
52 import java.util.Queue;
53 import java.util.Set;
54 import java.util.SortedSet;
55 import java.util.TreeSet;
56
57 public abstract class SelectManyRendererBase<T extends AbstractUISelectManyBase> extends MessageLayoutRendererBase<T> {
58
59 private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
60
61 @Override
62 protected boolean isOutputOnly(T component) {
63 return component.isDisabled() || component.isReadonly();
64 }
65
66 @Override
67 public void decodeInternal(final FacesContext facesContext, final T component) {
68 if (isOutputOnly(component)) {
69 return;
70 }
71
72 String[] newValues =
73 facesContext.getExternalContext().getRequestParameterValuesMap().get(component.getClientId(facesContext));
74 if (LOG.isDebugEnabled()) {
75 LOG.debug("decode: key='" + component.getClientId(facesContext)
76 + "' value='" + Arrays.toString(newValues) + "'");
77 LOG.debug("size ... '" + (newValues != null ? newValues.length : -1) + "'");
78 if (newValues != null) {
79 for (final String newValue : newValues) {
80 LOG.debug("newValues[i] = '" + newValue + "'");
81 }
82 }
83 }
84
85 if (newValues == null) {
86 newValues = ArrayUtils.EMPTY_STRING_ARRAY;
87 }
88 component.setSubmittedValue(newValues);
89
90 decodeClientBehaviors(facesContext, component);
91 }
92
93 public String[] getSubmittedValues(final UIInput input) {
94 return (String[]) input.getSubmittedValue();
95 }
96
97 @Override
98 public Object getConvertedValueInternal(
99 final FacesContext facesContext, final T component, final Object submittedValue)
100 throws ConverterException {
101
102 if (submittedValue == null) {
103 return null;
104 } else {
105 if (!(submittedValue instanceof String[])) {
106 throw new ConverterException("Submitted value not of type String[] for component : "
107 + component.getClientId(facesContext) + "expected");
108 }
109 }
110 return getConvertedUISelectManyValue(facesContext, component, (String[]) submittedValue);
111 }
112
113
114
115
116
117
118
119
120
121
122
123
124
125 static final String COLLECTION_TYPE_KEY = "collectionType";
126 static final String VALUE_TYPE_KEY = "valueType";
127
128 static Object getConvertedUISelectManyValue(final FacesContext facesContext, final UISelectMany component,
129 final String[] submittedValue) throws ConverterException {
130 return getConvertedUISelectManyValue(facesContext, component,
131 submittedValue, false);
132 }
133
134
135
136
137
138
139
140
141
142
143
144
145
146 static Object getConvertedUISelectManyValue(final FacesContext facesContext, final UISelectMany component,
147 final String[] submittedValue, final boolean considerValueType)
148 throws ConverterException {
149
150
151
152
153 if (submittedValue == null) {
154 throw new NullPointerException("submittedValue");
155 }
156
157 final ValueExpression expression = component.getValueExpression("value");
158 Object targetForConvertedValues = null;
159
160
161 Converter converter = component.getConverter();
162
163 if (converter == null && considerValueType) {
164
165 converter = getValueTypeConverter(facesContext, component);
166 }
167
168 if (expression != null) {
169 final Class<?> modelType = expression
170 .getType(facesContext.getELContext());
171 if (modelType == null) {
172
173 return submittedValue;
174 } else if (modelType.isArray()) {
175
176 final Class<?> componentType = modelType.getComponentType();
177
178
179 if (String.class.equals(componentType)) {
180 return submittedValue;
181 }
182 if (converter == null) {
183
184
185 converter = facesContext.getApplication().createConverter(
186 componentType);
187
188 if (converter == null && !Object.class.equals(componentType)) {
189
190
191
192
193
194
195 throw new ConverterException(
196 "Could not obtain a Converter for "
197 + componentType.getName());
198 }
199 }
200
201 targetForConvertedValues = Array.newInstance(componentType,
202 submittedValue.length);
203 } else if (Collection.class.isAssignableFrom(modelType) || Object.class.equals(modelType)) {
204 if (converter == null) {
205
206 final SelectItemsIterator iterator = new SelectItemsIterator(component, facesContext);
207 converter = getSelectItemsValueConverter(iterator, facesContext);
208 }
209
210 final Object collectionTypeAttr = component.getAttributes().get(
211 COLLECTION_TYPE_KEY);
212 if (collectionTypeAttr != null) {
213 final Class<?> collectionType = getClassFromAttribute(facesContext, collectionTypeAttr);
214 if (collectionType == null) {
215 throw new FacesException(
216 "The attribute "
217 + COLLECTION_TYPE_KEY
218 + " of component "
219 + component.getClientId(facesContext)
220 + " does not evaluate to a "
221 + "String, a Class object or a ValueExpression pointing "
222 + "to a String or a Class object.");
223 }
224
225 if (!Collection.class.isAssignableFrom(collectionType)) {
226 throw new FacesException("The attribute "
227 + COLLECTION_TYPE_KEY + " of component "
228 + component.getClientId(facesContext)
229 + " does not point to a valid type of Collection.");
230 }
231
232 try {
233 targetForConvertedValues = collectionType.newInstance();
234 } catch (final Exception e) {
235 throw new FacesException("The Collection "
236 + collectionType.getName()
237 + "can not be instantiated.", e);
238 }
239 } else if (Collection.class.isAssignableFrom(modelType)) {
240
241 final Collection<?> componentValue = (Collection<?>) component.getValue();
242
243 if (componentValue instanceof Cloneable) {
244
245 try {
246 final Method cloneMethod = componentValue.getClass()
247 .getMethod("clone");
248 final Collection<?> clone = (Collection<?>) cloneMethod
249 .invoke(componentValue);
250 clone.clear();
251 targetForConvertedValues = clone;
252 } catch (final Exception e) {
253 LOG.error("Could not clone " + componentValue.getClass().getName(), e);
254 }
255 }
256
257
258 if (targetForConvertedValues == null) {
259
260
261 try {
262 targetForConvertedValues = (componentValue != null
263 ? componentValue.getClass()
264 : modelType).newInstance();
265 } catch (final Exception e) {
266
267
268 if (SortedSet.class.isAssignableFrom(modelType)) {
269 targetForConvertedValues = new TreeSet();
270 } else if (Queue.class.isAssignableFrom(modelType)) {
271 targetForConvertedValues = new LinkedList();
272 } else if (Set.class.isAssignableFrom(modelType)) {
273 targetForConvertedValues = new HashSet(
274 submittedValue.length);
275 } else {
276 targetForConvertedValues = new ArrayList(
277 submittedValue.length);
278 }
279 }
280 }
281 } else {
282
283
284
285
286 if (converter == null) {
287 return submittedValue;
288 }
289
290 targetForConvertedValues = new Object[submittedValue.length];
291 }
292 } else {
293
294 throw new ConverterException(
295 "ValueExpression for UISelectMany must be of type Collection or Array.");
296 }
297 } else {
298 targetForConvertedValues = new Object[submittedValue.length];
299 }
300
301
302
303 final boolean isArray = targetForConvertedValues.getClass().isArray();
304 for (int i = 0; i < submittedValue.length; i++) {
305
306 final Object value;
307 if (converter != null) {
308 value = converter.getAsObject(facesContext, component,
309 submittedValue[i]);
310 } else {
311 value = submittedValue[i];
312 }
313
314 if (isArray) {
315 Array.set(targetForConvertedValues, i, value);
316 } else {
317 ((Collection) targetForConvertedValues).add(value);
318 }
319 }
320
321 return targetForConvertedValues;
322 }
323
324
325
326
327
328
329
330
331
332
333
334
335 static Class<?> getClassFromAttribute(final FacesContext facesContext,
336 final Object attribute) throws FacesException {
337
338
339
340
341 Class<?> type = null;
342
343
344
345 final Object attr = attribute instanceof ValueExpression
346
347 ? ((ValueExpression) attribute).getValue(facesContext.getELContext())
348 : attribute;
349
350 if (attr instanceof String) {
351 try {
352 type = Class.forName((String) attr);
353 } catch (final ClassNotFoundException cnfe) {
354 throw new FacesException("Unable to find class " + attr + " on the classpath.", cnfe);
355 }
356 } else if (attr instanceof Class) {
357
358 type = (Class<?>) attr;
359 }
360
361 return type;
362 }
363
364
365
366
367
368
369
370
371
372 static Converter getValueTypeConverter(final FacesContext facesContext, final UISelectMany component) {
373 Converter converter = null;
374
375 final Object valueTypeAttr = component.getAttributes().get(VALUE_TYPE_KEY);
376 if (valueTypeAttr != null) {
377
378 final Class<?> valueType = getClassFromAttribute(facesContext, valueTypeAttr);
379 if (valueType == null) {
380 throw new FacesException(
381 "The attribute "
382 + VALUE_TYPE_KEY
383 + " of component "
384 + component.getClientId(facesContext)
385 + " does not evaluate to a "
386 + "String, a Class object or a ValueExpression pointing "
387 + "to a String or a Class object.");
388 }
389
390
391 converter = facesContext.getApplication().createConverter(valueType);
392
393 if (converter == null) {
394 facesContext.getExternalContext().log("Found attribute valueType on component "
395 + getPathToComponent(component)
396 + ", but could not get a by-type converter for type "
397 + valueType.getName());
398 }
399 }
400
401 return converter;
402 }
403
404
405
406
407
408
409
410
411
412 static Converter getSelectItemsValueConverter(final Iterator<SelectItem> iterator, final FacesContext facesContext) {
413
414
415
416
417 Converter converter = null;
418 while (converter == null && iterator.hasNext()) {
419 final SelectItem item = iterator.next();
420 if (item instanceof SelectItemGroup) {
421 final Iterator<SelectItem> groupIterator = Arrays.asList(
422 ((SelectItemGroup) item).getSelectItems()).iterator();
423 converter = getSelectItemsValueConverter(groupIterator, facesContext);
424 } else {
425 final Class<?> selectItemsType = item.getValue().getClass();
426
427
428 if (String.class.equals(selectItemsType)) {
429 return null;
430 }
431
432 try {
433 converter = facesContext.getApplication().createConverter(selectItemsType);
434 } catch (final FacesException e) {
435
436 }
437 }
438 }
439 return converter;
440 }
441
442
443
444
445
446
447
448
449
450 static String getPathToComponent(final UIComponent component) {
451 final StringBuilder builder = new StringBuilder();
452
453 if (component == null) {
454 builder.append("{Component-Path : ");
455 builder.append("[null]}");
456 return builder.toString();
457 }
458
459 getPathToComponent(component, builder);
460
461 builder.insert(0, "{Component-Path : ");
462 builder.append("}");
463
464 return builder.toString();
465 }
466
467 private static void getPathToComponent(final UIComponent component, final StringBuilder builder) {
468 if (component == null) {
469 return;
470 }
471
472 final StringBuilder intBuilder = new StringBuilder();
473
474 intBuilder.append("[Class: ");
475 intBuilder.append(component.getClass().getName());
476 if (component instanceof UIViewRoot) {
477 intBuilder.append(",ViewId: ");
478 intBuilder.append(((UIViewRoot) component).getViewId());
479 } else {
480 intBuilder.append(",Id: ");
481 intBuilder.append(component.getId());
482 }
483 intBuilder.append("]");
484
485 builder.insert(0, intBuilder.toString());
486
487 getPathToComponent(component.getParent(), builder);
488 }
489
490
491
492
493
494
495
496
497
498 private static class SelectItemsIterator implements Iterator<SelectItem> {
499
500 private static final Iterator<UIComponent> EMPTY_UICOMPONENT_ITERATOR = new EmptyIterator<>();
501
502
503 private static final String VAR_ATTR = "var";
504 private static final String ITEM_VALUE_ATTR = "itemValue";
505 private static final String ITEM_LABEL_ATTR = "itemLabel";
506 private static final String ITEM_DESCRIPTION_ATTR = "itemDescription";
507 private static final String ITEM_DISABLED_ATTR = "itemDisabled";
508 private static final String ITEM_LABEL_ESCAPED_ATTR = "itemLabelEscaped";
509 private static final String NO_SELECTION_VALUE_ATTR = "noSelectionValue";
510
511 private final Iterator<UIComponent> children;
512 private Iterator<?> nestedItems;
513 private SelectItem nextItem;
514 private UIComponent currentComponent;
515 private UISelectItems currentUISelectItems;
516 private FacesContext facesContext;
517
518 SelectItemsIterator(final UIComponent selectItemsParent, final FacesContext facesContext) {
519 children = selectItemsParent.getChildCount() > 0
520 ? selectItemsParent.getChildren().iterator()
521 : EMPTY_UICOMPONENT_ITERATOR;
522 this.facesContext = facesContext;
523 }
524
525 @Override
526 @SuppressWarnings("unchecked")
527 public boolean hasNext() {
528 if (nextItem != null) {
529 return true;
530 }
531 if (nestedItems != null) {
532 if (nestedItems.hasNext()) {
533 return true;
534 }
535 nestedItems = null;
536 currentComponent = null;
537 }
538 if (children.hasNext()) {
539 UIComponent child = children.next();
540
541
542
543
544
545
546 while (!(child instanceof UISelectItem) && !(child instanceof UISelectItems)) {
547
548 if (children.hasNext()) {
549
550 child = children.next();
551 } else {
552
553
554 return false;
555 }
556 }
557 if (child instanceof UISelectItem) {
558 final UISelectItem uiSelectItem = (UISelectItem) child;
559 Object item = uiSelectItem.getValue();
560 if (item == null) {
561
562 final Object itemValue = uiSelectItem.getItemValue();
563 String label = uiSelectItem.getItemLabel();
564 final String description = uiSelectItem.getItemDescription();
565 final boolean disabled = uiSelectItem.isItemDisabled();
566 final boolean escape = uiSelectItem.isItemEscaped();
567 final boolean noSelectionOption = uiSelectItem.isNoSelectionOption();
568 if (label == null) {
569 label = itemValue.toString();
570 }
571 item = new SelectItem(itemValue, label, description, disabled, escape, noSelectionOption);
572 } else if (!(item instanceof SelectItem)) {
573 final ValueExpression expression = uiSelectItem.getValueExpression("value");
574 throw new IllegalArgumentException("ValueExpression '"
575 + (expression == null ? null : expression.getExpressionString()) + "' of UISelectItem : "
576 + getPathToComponent(child) + " does not reference an Object of type SelectItem");
577 }
578 nextItem = (SelectItem) item;
579 currentComponent = child;
580 return true;
581 } else if (child instanceof UISelectItems) {
582 currentUISelectItems = (UISelectItems) child;
583 final Object value = currentUISelectItems.getValue();
584 currentComponent = child;
585
586 if (value instanceof SelectItem) {
587 nextItem = (SelectItem) value;
588 return true;
589 } else if (value != null && value.getClass().isArray()) {
590
591
592 final int length = Array.getLength(value);
593 final Collection<Object> items = new ArrayList<>(length);
594 for (int i = 0; i < length; i++) {
595 items.add(Array.get(value, i));
596 }
597 nestedItems = items.iterator();
598 return hasNext();
599 } else if (value instanceof Iterable) {
600
601 nestedItems = ((Iterable<?>) value).iterator();
602 return hasNext();
603 } else if (value instanceof Map) {
604 final Map<Object, Object> map = (Map<Object, Object>) value;
605 final Collection<SelectItem> items = new ArrayList<>(map.size());
606 for (final Map.Entry<Object, Object> entry : map.entrySet()) {
607 items.add(new SelectItem(entry.getValue(), entry.getKey().toString()));
608 }
609
610 nestedItems = items.iterator();
611 return hasNext();
612 } else {
613
614 if ((facesContext.isProjectStage(ProjectStage.Production) && LOG.isDebugEnabled())
615 || LOG.isWarnEnabled()) {
616 final ValueExpression expression = currentUISelectItems.getValueExpression("value");
617 final Object[] objects = {
618 expression == null ? null : expression.getExpressionString(),
619 getPathToComponent(child),
620 value == null ? null : value.getClass().getName()
621 };
622 final String message = "ValueExpression {0} of UISelectItems with component-path {1}"
623 + " does not reference an Object of type SelectItem,"
624 + " array, Iterable or Map, but of type: {2}";
625 if (facesContext.isProjectStage(ProjectStage.Production)) {
626 LOG.debug(message, objects);
627 } else {
628 LOG.warn(message, objects);
629 }
630 }
631 }
632 } else {
633 currentComponent = null;
634 }
635 }
636 return false;
637 }
638
639 @Override
640 public SelectItem next() {
641 if (!hasNext()) {
642 throw new NoSuchElementException();
643 }
644 if (nextItem != null) {
645 final SelectItem value = nextItem;
646 nextItem = null;
647 return value;
648 }
649 if (nestedItems != null) {
650 Object item = nestedItems.next();
651
652 if (!(item instanceof SelectItem)) {
653
654
655
656 final Map<String, Object> attributeMap = currentUISelectItems.getAttributes();
657
658
659 boolean wroteRequestMapVarValue = false;
660 Object oldRequestMapVarValue = null;
661 final String var = (String) attributeMap.get(VAR_ATTR);
662 if (var != null && !"".equals(var)) {
663
664 oldRequestMapVarValue = facesContext.getExternalContext().getRequestMap().put(var, item);
665 wroteRequestMapVarValue = true;
666 }
667
668
669 Object itemValue = attributeMap.get(ITEM_VALUE_ATTR);
670 if (itemValue == null) {
671
672
673 itemValue = item;
674 }
675
676
677
678 Object itemLabel = attributeMap.get(ITEM_LABEL_ATTR);
679 if (itemLabel == null) {
680 if (itemValue != null) {
681 itemLabel = itemValue.toString();
682 }
683 } else {
684 itemLabel = itemLabel.toString();
685 }
686 Object itemDescription = attributeMap.get(ITEM_DESCRIPTION_ATTR);
687 if (itemDescription != null) {
688 itemDescription = itemDescription.toString();
689 }
690 final Boolean itemDisabled = getBooleanAttribute(currentUISelectItems, ITEM_DISABLED_ATTR, false);
691 final Boolean itemLabelEscaped = getBooleanAttribute(currentUISelectItems, ITEM_LABEL_ESCAPED_ATTR, true);
692 final Object noSelectionValue = attributeMap.get(NO_SELECTION_VALUE_ATTR);
693 item = new SelectItem(itemValue,
694 (String) itemLabel,
695 (String) itemDescription,
696 itemDisabled,
697 itemLabelEscaped,
698 itemValue.equals(noSelectionValue));
699
700
701 if (wroteRequestMapVarValue) {
702
703 if (oldRequestMapVarValue != null) {
704 facesContext.getExternalContext()
705 .getRequestMap().put(var, oldRequestMapVarValue);
706 } else {
707 facesContext.getExternalContext()
708 .getRequestMap().remove(var);
709 }
710 }
711 }
712 return (SelectItem) item;
713 }
714 throw new NoSuchElementException();
715 }
716
717 @Override
718 public void remove() {
719 throw new UnsupportedOperationException();
720 }
721
722 public UIComponent getCurrentComponent() {
723 return currentComponent;
724 }
725
726 private boolean getBooleanAttribute(
727 final UIComponent component, final String attrName, final boolean defaultValue) {
728 final Object value = component.getAttributes().get(attrName);
729 if (value == null) {
730 return defaultValue;
731 } else if (value instanceof Boolean) {
732 return (Boolean) value;
733 } else {
734
735
736
737 return Boolean.valueOf(value.toString());
738 }
739 }
740
741 private String getPathToComponent(final UIComponent component) {
742 final StringBuilder builder = new StringBuilder();
743
744 if (component == null) {
745 builder.append("{Component-Path : ");
746 builder.append("[null]}");
747 return builder.toString();
748 }
749
750 getPathToComponent(component, builder);
751
752 builder.insert(0, "{Component-Path : ");
753 builder.append("}");
754
755 return builder.toString();
756 }
757
758 private void getPathToComponent(final UIComponent component, final StringBuilder builder) {
759 if (component == null) {
760 return;
761 }
762
763 final StringBuilder intBuilder = new StringBuilder();
764
765 intBuilder.append("[Class: ");
766 intBuilder.append(component.getClass().getName());
767 if (component instanceof UIViewRoot) {
768 intBuilder.append(",ViewId: ");
769 intBuilder.append(((UIViewRoot) component).getViewId());
770 } else {
771 intBuilder.append(",Id: ");
772 intBuilder.append(component.getId());
773 }
774 intBuilder.append("]");
775
776 builder.insert(0, intBuilder);
777
778 getPathToComponent(component.getParent(), builder);
779 }
780 }
781
782
783
784
785
786
787
788
789
790 private static class EmptyIterator<T> implements Iterator<T> {
791
792 @Override
793 public boolean hasNext() {
794 return false;
795 }
796
797 @Override
798 public T next() {
799 throw new NoSuchElementException();
800 }
801
802 @Override
803 public void remove() {
804 throw new UnsupportedOperationException();
805 }
806 }
807
808
809
810
811 }