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.orchestra.dynaForm.jsf.guiBuilder.impl.jsf;
20  
21  import org.apache.myfaces.orchestra.dynaForm.jsf.component.DynaForm;
22  import org.apache.myfaces.orchestra.dynaForm.jsf.guiBuilder.GuiBuilder;
23  import org.apache.myfaces.orchestra.dynaForm.lib.ObjectSerializationConverter;
24  import org.apache.myfaces.orchestra.dynaForm.lib._FacesUtils;
25  import org.apache.myfaces.orchestra.dynaForm.metadata.MetaField;
26  import org.apache.myfaces.orchestra.dynaForm.metadata.Selection;
27  import org.apache.myfaces.orchestra.dynaForm.metadata.utils.TypeInfos;
28  import org.apache.myfaces.orchestra.lib.OrchestraException;
29  import org.apache.myfaces.orchestra.lib.jsf.SerializableConverter;
30  
31  import javax.el.ELContext;
32  import javax.faces.component.UIComponent;
33  import javax.faces.component.UIInput;
34  import javax.faces.component.UIOutput;
35  import javax.faces.component.UIParameter;
36  import javax.faces.component.UISelectBoolean;
37  import javax.faces.component.UISelectItem;
38  import javax.faces.component.UISelectItems;
39  import javax.faces.component.html.HtmlInputText;
40  import javax.faces.component.html.HtmlOutputLabel;
41  import javax.faces.component.html.HtmlOutputText;
42  import javax.faces.component.html.HtmlPanelGroup;
43  import javax.faces.component.html.HtmlSelectBooleanCheckbox;
44  import javax.faces.component.html.HtmlSelectManyListbox;
45  import javax.faces.component.html.HtmlSelectOneMenu;
46  import javax.faces.context.FacesContext;
47  import javax.faces.convert.BigDecimalConverter;
48  import javax.faces.convert.BigIntegerConverter;
49  import javax.faces.convert.BooleanConverter;
50  import javax.faces.convert.ByteConverter;
51  import javax.faces.convert.CharacterConverter;
52  import javax.faces.convert.Converter;
53  import javax.faces.convert.DateTimeConverter;
54  import javax.faces.convert.FloatConverter;
55  import javax.faces.convert.IntegerConverter;
56  import javax.faces.convert.LongConverter;
57  import javax.faces.convert.NumberConverter;
58  import javax.faces.convert.ShortConverter;
59  import javax.faces.el.MethodBinding;
60  import javax.faces.validator.DoubleRangeValidator;
61  import javax.faces.validator.LengthValidator;
62  import javax.persistence.TemporalType;
63  
64  import java.math.BigDecimal;
65  import java.math.BigInteger;
66  import java.util.Date;
67  import java.util.List;
68  import java.util.Map;
69  import java.util.TreeMap;
70  
71  /**
72   * A concrete subclass of GuiBuilder which knows how to build JSF components and
73   * add them to the current component tree.
74   */
75  public class JsfGuiBuilder extends GuiBuilder
76  {
77      private FacesContext context;
78      private ELContext elcontext;
79      private NewComponentListener newComponentListener;
80      private String backingEntityPrefix;
81  
82      private static final Map<String, JsfGuiElementBuilder> BUILDER_MAP =
83          new TreeMap<String, JsfGuiElementBuilder>();
84  
85      protected JsfGuiBuilder()
86      {
87      }
88  
89      /**
90       * Add a specialized builder. This builder will be used instead of any default.
91       */
92      public static void addElementBuilder(Class<?> elementType, JsfGuiElementBuilder builder)
93      {
94          BUILDER_MAP.put(elementType.getName(), builder);
95      }
96  
97      public String getBackingEntityPrefix()
98      {
99          return backingEntityPrefix;
100     }
101 
102     public void setBackingEntityPrefix(String backingEntityPrefix)
103     {
104         this.backingEntityPrefix = backingEntityPrefix;
105     }
106 
107     public NewComponentListener getNewComponentListener()
108     {
109         return newComponentListener;
110     }
111 
112     public void setNewComponentListener(NewComponentListener newComponentListener)
113     {
114         this.newComponentListener = newComponentListener;
115     }
116 
117     public void setContext(FacesContext context)
118     {
119         this.context = context;
120     }
121 
122     public FacesContext getContext()
123     {
124         return context;
125     }
126 
127     public void setELContext(ELContext elcontext)
128     {
129         this.elcontext = elcontext;
130     }
131 
132     public ELContext getELContext()
133     {
134         return this.elcontext;
135     }
136 
137     /* fulfill the interface */
138     @Override
139     public void createOutputText(MetaField field)
140     {
141         UIComponent cmp = doCreateOutputText(field);
142         fireNewComponent(field, cmp);
143     }
144 
145     @Override
146     public void createInputDate(MetaField field)
147     {
148         UIComponent cmp;
149         if (!isFieldDisplayOnly(field))
150         {
151             cmp = doCreateInputDate(field);
152         }
153         else
154         {
155             cmp = doCreateOutputText(field);
156         }
157         fireNewComponent(field, cmp);
158     }
159 
160     @Override
161     public void createInputText(MetaField field)
162     {
163         UIComponent cmp;
164         if (!isFieldDisplayOnly(field))
165         {
166             cmp = doCreateInputText(field);
167         }
168         else
169         {
170             cmp = doCreateOutputText(field);
171         }
172         fireNewComponent(field, cmp);
173     }
174 
175     @Override
176     public void createInputNumber(MetaField field)
177     {
178         UIComponent cmp;
179         if (!isFieldDisplayOnly(field))
180         {
181             cmp = doCreateInputNumber(field);
182         }
183         else
184         {
185             cmp = doCreateOutputText(field);
186         }
187         fireNewComponent(field, cmp);
188     }
189 
190     @Override
191     public void createInputBoolean(MetaField field)
192     {
193         UIComponent cmp;
194         if (!isFieldDisplayOnly(field))
195         {
196             cmp = doCreateInputBoolean(field);
197         }
198         else
199         {
200             cmp = doCreateOutputText(field);
201         }
202         fireNewComponent(field, cmp);
203     }
204 
205     @Override
206     public void createSelectOneMenu(MetaField field)
207     {
208         UIComponent cmp;
209         if (!isFieldDisplayOnly(field))
210         {
211             cmp = doCreateSelectOneMenu(field);
212         }
213         else
214         {
215             cmp = doCreateOutputText(field);
216         }
217         fireNewComponent(field, cmp);
218     }
219 
220     @Override
221     public void createSearchFor(MetaField field)
222     {
223         UIComponent cmp;
224         if (!isFieldDisplayOnly(field))
225         {
226             cmp = doCreateSearchFor(field);
227         }
228         else
229         {
230             cmp = doCreateOutputText(field);
231         }
232         fireNewComponent(field, cmp);
233     }
234 
235     @Override
236     public void createSearchForSelectMenu(MetaField field)
237     {
238         UIComponent cmp;
239         if (!isFieldDisplayOnly(field))
240         {
241             cmp = doCreateSearchForSelectMenu(field);
242         }
243         else
244         {
245             cmp = doCreateOutputText(field);
246         }
247         fireNewComponent(field, cmp);
248     }
249 
250     @Override
251     public void createNative(MetaField field)
252     {
253         Object component = field.getWantedComponent();
254         if (!(component instanceof UIComponent))
255         {
256             throw new IllegalArgumentException("wanted component for field " + field.getName() + " not a UIComponent");
257         }
258 
259         UIComponent uinew = cloneComponent((UIComponent) component);
260 
261         doCreateNative(field, uinew);
262 
263         fireNewComponent(field, uinew);
264     }
265 
266     public static UIComponent cloneComponent(UIComponent uicomponent)
267     {
268         // a naive try to clone a component
269         UIComponent uinew;
270         try
271         {
272             uinew = uicomponent.getClass().newInstance();
273         }
274         catch (InstantiationException e)
275         {
276             throw new OrchestraException(e);
277         }
278         catch (IllegalAccessException e)
279         {
280             throw new OrchestraException(e);
281         }
282         uinew.restoreState(FacesContext.getCurrentInstance(), uicomponent.saveState(FacesContext.getCurrentInstance()));
283         return uinew;
284     }
285 
286     /* do the hard work */
287     public HtmlOutputText doCreateOutputText(MetaField field)
288     {
289         HtmlOutputText cmp = doCreateOutputTextComponent();
290         initOutputDefaults(cmp, field);
291         return cmp;
292     }
293 
294     public HtmlOutputText doCreateOutputTextComponent()
295     {
296         HtmlOutputText cmp = (HtmlOutputText) context.getApplication()
297             .createComponent("javax.faces.HtmlOutputText");
298         return cmp;
299     }
300 
301     /**
302      * Create an HtmlOutputLabel component, ie some text that
303      * specifically describes another input component.
304      * <p>
305      * The labelKey parameter is translated using a resource-bundle
306      * if possible. If no translation is available then the labelKey
307      * text is used directly.
308      */
309     public HtmlOutputLabel doCreateOutputLabel(String labelKey)
310     {
311         HtmlOutputLabel cmp = doCreateOutputLabelComponent();
312         initDefaults(cmp, null);
313         cmp.setValue(translateText(labelKey, labelKey));
314         return cmp;
315     }
316 
317     /**
318      * Create an HtmlOutputText component, ie a component that displays
319      * a string.
320      * <p>
321      * Unlike doCreateOutputLabel, the parameter is the exact string to
322      * be displayed, not a key into a resource-bundle. This method is used
323      * to create components wrapping all sorts of strings, only some of
324      * which may be "translatable" via a resource bundle. If an output
325      * text component needs to contain translated text, then pass the
326      * translated value in as the parameter.
327      */
328     public HtmlOutputText doCreateOutputText(String text)
329     {
330         HtmlOutputText cmp = doCreateOutputTextComponent();
331         initDefaults(cmp, null);
332         cmp.setValue(text);
333         return cmp;
334     }
335 
336     public HtmlOutputLabel doCreateOutputLabelComponent()
337     {
338         HtmlOutputLabel cmp = (HtmlOutputLabel) context.getApplication()
339             .createComponent("javax.faces.HtmlOutputLabel");
340         return cmp;
341     }
342 
343     public HtmlInputText doCreateInputDate(MetaField field)
344     {
345         HtmlInputText cmp = doCreateInputDateComponent(field);
346         initInputDefaults(cmp, field);
347         // DateTimeConverter cnv = doCreateDateConverter();
348         // cmp.setConverter(cnv);
349         return cmp;
350     }
351 
352     public HtmlInputText doCreateInputDateComponent(MetaField field)
353     {
354         HtmlInputText cmp = doCreateInputText(field);
355         return cmp;
356     }
357 
358     public Converter doCreateConverter(MetaField field)
359     {
360         if (field.getConverterClass() != null)
361         {
362             return context.getApplication().createConverter(field.getConverterClass());
363         }
364 
365         if (field.getConverterId() != null)
366         {
367             return context.getApplication().createConverter(field.getConverterId());
368         }
369 
370         if (field.getConverterBean() != null)
371         {
372             String beanName = field.getConverterBean();
373             return new SerializableConverter(beanName);
374         }
375 
376         Class<?> type = field.getType();
377         if (type == null)
378         {
379             return null;
380         }
381 
382         if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type))
383         {
384             return context.getApplication().createConverter(BooleanConverter.CONVERTER_ID);
385         }
386         if (Character.class.isAssignableFrom(type) || char.class.isAssignableFrom(type))
387         {
388             return context.getApplication().createConverter(CharacterConverter.CONVERTER_ID);
389         }
390         if (Byte.class.isAssignableFrom(type) || byte.class.isAssignableFrom(type))
391         {
392             return context.getApplication().createConverter(ByteConverter.CONVERTER_ID);
393         }
394         if (Short.class.isAssignableFrom(type) || short.class.isAssignableFrom(type))
395         {
396             return context.getApplication().createConverter(ShortConverter.CONVERTER_ID);
397         }
398         if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type))
399         {
400             return context.getApplication().createConverter(IntegerConverter.CONVERTER_ID);
401         }
402         if (Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type))
403         {
404             return context.getApplication().createConverter(LongConverter.CONVERTER_ID);
405         }
406         if (Float.class.isAssignableFrom(type) || float.class.isAssignableFrom(type))
407         {
408             return context.getApplication().createConverter(FloatConverter.CONVERTER_ID);
409         }
410         if (Double.class.isAssignableFrom(type) || double.class.isAssignableFrom(type))
411         {
412             // use the number converter to have locale sensitive input
413             return context.getApplication().createConverter(NumberConverter.CONVERTER_ID);
414         }
415         if (BigInteger.class.isAssignableFrom(type))
416         {
417             return context.getApplication().createConverter(BigIntegerConverter.CONVERTER_ID);
418         }
419         if (BigDecimal.class.isAssignableFrom(type))
420         {
421             return context.getApplication().createConverter(BigDecimalConverter.CONVERTER_ID);
422         }
423         if (Date.class.isAssignableFrom(type))
424         {
425             return doCreateDateConverter(field);
426         }
427 
428         return null;
429     }
430 
431     public DateTimeConverter doCreateDateConverter(MetaField field)
432     {
433         DateTimeConverter cnv = (DateTimeConverter) context.getApplication()
434             .createConverter("javax.faces.DateTime");
435         TemporalType fieldType = field.getTemporalType();
436         if (fieldType != null)
437         {
438             switch (fieldType)
439             {
440                 case DATE:
441                     cnv.setType("date");
442                     break;
443                 case TIME:
444                     cnv.setType("time");
445                     break;
446                 case TIMESTAMP:
447                     cnv.setType("both");
448                     break;
449                     
450                 default:
451                     throw new IllegalArgumentException("Unsupported TemporalType:" + fieldType);
452             }
453         }
454         else
455         {
456             cnv.setType("both");
457         }
458         cnv.setDateStyle("short");
459         cnv.setTimeStyle("medium");
460         return cnv;
461     }
462 
463     public HtmlInputText doCreateInputText(MetaField field)
464     {
465         HtmlInputText cmp = doCreateInputTextComponent();
466         initInputDefaults(cmp, field);
467         if (Boolean.FALSE.equals(field.getCanWrite()))
468         {
469             cmp.setReadonly(true);
470         }
471         if (Boolean.TRUE.equals(field.getDisabled()))
472         {
473             cmp.setDisabled(true);
474         }
475         return cmp;
476     }
477 
478     public HtmlInputText doCreateInputTextComponent()
479     {
480         HtmlInputText cmp = (HtmlInputText) context.getApplication()
481             .createComponent("javax.faces.HtmlInputText");
482         return cmp;
483     }
484 
485     public UISelectBoolean doCreateInputBoolean(MetaField field)
486     {
487         HtmlSelectBooleanCheckbox cmp = doCreateInputBooleanComponent();
488         initInputDefaults(cmp, field);
489         if (Boolean.FALSE.equals(field.getCanWrite()))
490         {
491             cmp.setReadonly(true);
492         }
493         if (Boolean.TRUE.equals(field.getDisabled()))
494         {
495             cmp.setDisabled(true);
496         }
497         return cmp;
498     }
499 
500     public HtmlSelectBooleanCheckbox doCreateInputBooleanComponent()
501     {
502         HtmlSelectBooleanCheckbox cmp = (HtmlSelectBooleanCheckbox) context
503             .getApplication().createComponent("javax.faces.HtmlSelectBooleanCheckbox");
504         return cmp;
505     }
506 
507     public HtmlInputText doCreateInputNumber(MetaField field)
508     {
509         HtmlInputText cmp = doCreateInputText(field);
510         cmp.setStyleClass("ff_inputNumber");
511         return cmp;
512     }
513 
514     public HtmlSelectOneMenu doCreateSelectOneMenu(MetaField field)
515     {
516         HtmlSelectOneMenu cmp = doCreateSelectOneMenuComponent();
517         initInputDefaults(cmp, field);
518         initSelections(field, cmp);
519 
520         if (Boolean.FALSE.equals(field.getCanWrite()))
521         {
522             cmp.setReadonly(true);
523         }
524         if (Boolean.TRUE.equals(field.getDisabled()))
525         {
526             cmp.setDisabled(true);
527         }
528 
529         return cmp;
530     }
531 
532     public HtmlSelectManyListbox doCreateSelectManyListbox(MetaField field)
533     {
534         HtmlSelectManyListbox cmp = doCreateSelectManyListboxComponent();
535         initInputDefaults(cmp, field);
536         initSelections(field, cmp);
537 
538         if (Boolean.FALSE.equals(field.getCanWrite()))
539         {
540             cmp.setReadonly(true);
541         }
542         if (Boolean.TRUE.equals(field.getDisabled()))
543         {
544             cmp.setDisabled(true);
545         }
546 
547         return cmp;
548     }
549 
550     public HtmlSelectOneMenu doCreateSelectOneMenuComponent()
551     {
552         HtmlSelectOneMenu cmp = (HtmlSelectOneMenu) context.getApplication()
553             .createComponent("javax.faces.HtmlSelectOneMenu");
554         return cmp;
555     }
556 
557     public HtmlSelectManyListbox doCreateSelectManyListboxComponent()
558     {
559         HtmlSelectManyListbox cmp = (HtmlSelectManyListbox) context.getApplication()
560             .createComponent("javax.faces.HtmlSelectManyListbox");
561         return cmp;
562     }
563 
564     @SuppressWarnings("unchecked")
565     public UIComponent doCreateSearchFor(MetaField field)
566     {
567         throw new UnsupportedOperationException();
568         /*
569         HtmlPanelGroup panel = doCreatePanelGroupComponent();
570 
571         HtmlCommandLink command = doCreateCommandLink(field);
572         // avoid duplicate id
573         command.setId("cmd_" + command.getId());
574         command.setValue("...");
575         command.setStyleClass("ff_searchLink");
576         command.setImmediate(true);
577 
578         command.getChildren().add(
579                 createParameter(SEARCH_ENTITY_TYPE, field.getType().getName()));
580         command.getChildren().add(
581                 createParameter(SEARCH_ENTITY_BINDING,
582                         createValueBindingString(field)));
583 
584         Converter converter = context.getApplication().createConverter(field.getType());
585         HtmlOutputText text = doCreateOutputText(field);
586         if (converter != null)
587         {
588             text.setConverter(converter);
589         }
590         panel.getChildren().add(text);
591 
592         panel.getChildren().add(command);
593 
594         panel.setId("pnl_" + command.getId());
595 
596         return panel;
597         */
598     }
599 
600     @SuppressWarnings("unchecked")
601     public UIInput doCreateSearchForSelectMenu(MetaField field)
602     {
603         UIInput select;
604 
605         if (!field.getAllowMultipleSelections())
606         {
607             select = doCreateSelectOneMenu(field);
608         }
609         else
610         {
611             select = doCreateSelectManyListbox(field);
612         }
613 
614         /*
615         SelectionSourceEnum selectionSource;
616         if (field.isEntityType())
617         {
618             selectionSource = SelectionSourceEnum.relation;
619         }
620         else
621         {
622             selectionSource = SelectionSourceEnum.distinct;
623         }
624         if (field.getSelectionSource() != null)
625         {
626             selectionSource = field.getSelectionSource();
627         }
628         if (!field.isEntityType() && SelectionSourceEnum.relation.equals(selectionSource))
629         {
630             throw new IllegalArgumentException("cant use selectionSource 'relation' for property " + field.getName());
631         }
632         */
633 
634         /*
635             String itemSource;
636             String itemSourceName;
637             switch (selectionSource)
638             {
639             case manual:
640                 itemSource=null;
641                 itemSourceName=null;
642                 break;
643             case relation:
644                 itemSource="relationValues";
645                 itemSourceName=field.getType().getName();
646                 break;
647             case distinct:
648                 itemSource="distinctValues";
649                 itemSourceName=field.getName();
650                 break;
651             default:
652                 throw new IllegalArgumentException(
653                     "dont know how to handle selectionSource: " +
654                     field.getSelectionSource());
655             }
656         */
657 
658         if (!field.getAllowMultipleSelections() && !Boolean.TRUE.equals(field.getRequired()))
659         {
660             // not required - and a menu, so add the EMPTY entry
661             UISelectItem item = new UISelectItem();
662             // item.setItemValue(ObjectIdentifierConverter.SELECT_NULL_OBJECT);
663             item.setItemValue(ObjectSerializationConverter.SELECT_NULL_OBJECT);
664 
665             String labelKey = "SelectAll." + field.getName();
666 
667             item.setItemLabel(translateText(labelKey, ""));
668             select.getChildren().add(item);
669         }
670 
671         MethodBinding mbValues = context.getApplication().createMethodBinding(
672             field.getDataSource(),
673             new Class[]{String.class});
674         MethodBinding mbLabels = context.getApplication().createMethodBinding(
675             field.getDataSourceDescription(),
676             new Class[]{field.getType()});
677 
678         UISelectItems items = new UISelectItems();
679         items.setValueBinding("value",
680             new ValueBindingDataSourceAdapter(mbValues, mbLabels));
681         select.getChildren().add(items);
682 
683 /*
684         if (itemSource != null)
685         {
686             UISelectItems items = new UISelectItems();
687             items.setValueBinding("value",
688                     context.getApplication().createValueBinding(
689                         "#{" + backingBeanPrefix + "." + itemSource + "['" + itemSourceName + "']}"));
690             select.getChildren().add(items);
691         }
692 */
693 
694         Converter converter;
695         /*
696         if (field.isEntityType() || SelectionSourceEnum.relation.equals(selectionSource))
697         {
698             // we know this must be an entity, so persist its id only
699             converter = new ObjectIdentifierConverter();
700         }
701         else
702         */
703         {
704             // use the whole object as value - phu - any better idea?
705             converter = new ObjectSerializationConverter();
706         }
707         select.setConverter(converter);
708 
709         return select;
710     }
711 
712     /*
713     public HtmlCommandLink doCreateCommandLink(FieldInterface field)
714     {
715         HtmlCommandLink command = doCreateCommandLinkComponent();
716         iniCommandDefaults(command, field, "searchAction", null);
717         return command;
718     }
719     */
720 
721     /*
722     public HtmlCommandLink doCreateCommandLinkComponent()
723     {
724         HtmlCommandLink command = (HtmlCommandLink) context.getApplication()
725             .createComponent("javax.faces.HtmlCommandLink");
726         return command;
727     }
728     */
729 
730     public HtmlPanelGroup doCreatePanelGroupComponent()
731     {
732         HtmlPanelGroup panelGroup = (HtmlPanelGroup) context.getApplication()
733             .createComponent("javax.faces.HtmlPanelGroup");
734         return panelGroup;
735     }
736 
737     public void doCreateNative(MetaField field, UIComponent uicomponent)
738     {
739         initDefaults(uicomponent, field);
740         initValueBinding(uicomponent, field);
741     }
742 
743     public UIParameter createParameter(String name, String value)
744     {
745         UIParameter parameter = (UIParameter) context.getApplication()
746             .createComponent("javax.faces.Parameter");
747         parameter.setName(name);
748         parameter.setValue(value);
749         return parameter;
750     }
751 
752     /**
753      * jo, we made a component, create a possible label and fire its creation.
754      */
755     @SuppressWarnings("unchecked")
756     public void fireNewComponent(MetaField field, UIComponent cmp)
757     {
758         UIOutput labelCmp = createLabelFor(field.getBaseName(), cmp);
759 
760         cmp.getAttributes().put(DynaForm.DYNA_FORM_CREATED, Boolean.TRUE);
761 
762         labelCmp.getAttributes().put(DynaForm.DYNA_FORM_CREATED, Boolean.TRUE);
763 
764         newComponentListener.newComponent(getDestCmp(), field, field.getName(), labelCmp, cmp);
765     }
766 
767     /**
768      * Create a label for the specified component using the specified text.
769      * <p>
770      * If the component (or one of its children) is an input component, then an
771      * HtmlOutputLabel component is created, with the "for" attribute referencing
772      * the id of the input component. Otherwise a plain HtmlOutputText component
773      * is created.
774      */
775     public UIOutput createLabelFor(String labelKey, UIComponent cmp)
776     {
777         UIOutput labelCmp;
778         UIInput inputCmp = findInputComponent(cmp);
779         if (inputCmp != null)
780         {
781             HtmlOutputLabel label = doCreateOutputLabel(labelKey);
782             label.setFor(cmp.getId());
783             // mojarra will NPE without an id on the label
784             // TODO: make prefix (or suffix?) configurable
785             label.setId("lbl_" + cmp.getId());
786             labelCmp = label;
787         }
788         else
789         {
790             // method doCreateOutputText does not internally translate keys,
791             // so do so here.
792             String labelText = translateText(labelKey, labelKey);
793             labelCmp = doCreateOutputText(labelText);
794         }
795         return labelCmp;
796     }
797 
798     /**
799      * Return the first UIInput component in the tree of components starting at
800      * the specified node (including the node itself).
801      */
802     public UIInput findInputComponent(UIComponent cmp)
803     {
804         if (cmp instanceof UIInput)
805         {
806             return (UIInput) cmp;
807         }
808 
809         @SuppressWarnings("unchecked")
810         List<UIComponent> children = cmp.getChildren();
811         if (children != null)
812         {
813             for (Object child : children)
814             {
815                 if (child instanceof UIComponent)
816                 {
817                     UIInput input = findInputComponent((UIComponent) child);
818                     if (input != null)
819                     {
820                         return input;
821                     }
822                 }
823             }
824         }
825 
826         return null;
827     }
828 
829     /* inits */
830 
831     /**
832      * init global defaults like id
833      */
834     public void initDefaults(UIComponent cmp, MetaField field)
835     {
836         if (field != null)
837         {
838             cmp.setId(getFieldId(field));
839         }
840     }
841 
842     protected String getFieldId(MetaField field)
843     {
844         String idCandidate = field.getName();
845         return getCleanedNameForId(idCandidate);
846     }
847 
848     /**
849      * remove all not allowed characters for an jsf ID from the name
850      */
851     protected String getCleanedNameForId(String name)
852     {
853         StringBuffer ret = new StringBuffer(name.length());
854         for (int i = 0; i < name.length(); i++)
855         {
856             char c = name.charAt(i);
857             if (Character.isDigit(c) || Character.isLetter(c) || c == '_' || c == '-')
858             {
859                 ret.append(c);
860             }
861             else
862             {
863                 ret.append("_");
864             }
865         }
866         return ret.toString();
867     }
868 
869     /**
870      * init global defaults for output fields
871      */
872     public void initOutputDefaults(UIOutput cmp, MetaField field)
873     {
874         initDefaults(cmp, field);
875         initValueBinding(cmp, field);
876         initConverter(cmp, field);
877     }
878 
879     /**
880      * init the default value binding
881      */
882     public void initValueBinding(UIComponent cmp, MetaField field)
883     {
884         String vbString = createValueBindingString(field);
885         _FacesUtils.setValueExpression(cmp, getELContext(), context, vbString);
886     }
887 
888     protected String createValueBindingString(MetaField field)
889     {
890         return "#{" + backingEntityPrefix + "." + field.getName() + "}";
891     }
892 
893     /**
894      * init defaults specifically for commands
895     public void iniCommandDefaults(UICommand cmp, FieldInterface field,
896                                    String action, String actionListener)
897     {
898         initDefaults(cmp, field);
899 
900         if (action == null)
901         {
902             action = "commandAction";
903         }
904 
905         cmp.setAction(getContext().getApplication()
906             .createMethodBinding(
907             "#{" + backingBeanPrefix + "." + action
908                 + "}", null));
909 
910         if (actionListener != null)
911         {
912             cmp.setActionListener(getContext().getApplication()
913                 .createMethodBinding(
914                 "#{" + backingBeanPrefix + "." + actionListener
915                     + "}", new Class[]
916                 {ActionEvent.class}));
917         }
918     }
919      */
920 
921     /**
922      * setup all the validators, maxlength, size, ... for HtmlInputText fields
923      */
924     public void initInputDefaults(HtmlInputText cmp, MetaField field)
925     {
926         initInputDefaults((UIInput) cmp, field);
927 
928         int size = -1;
929         int maxlength = -1;
930         Double minValue = null;
931         Double maxValue = null;
932 
933         TypeInfos.Info typeInfo = TypeInfos.getInfo(field.getType());
934         if (typeInfo != null)
935         {
936             // init from constants
937             maxlength = typeInfo.getLength();
938             size = maxlength;
939             minValue = typeInfo.getMinValue();
940             maxValue = typeInfo.getMaxValue();
941         }
942 
943         if (field.getMaxSize() != null)
944         {
945             maxlength = field.getMaxSize();
946             size = maxlength;
947         }
948         if (field.getMinSize() != null)
949         {
950             size = field.getMinSize();
951         }
952         if (field.getMinValue() != null)
953         {
954             minValue = field.getMinValue();
955         }
956         if (field.getMaxValue() != null)
957         {
958             maxValue = field.getMaxValue();
959         }
960 
961         if (maxlength != -1)
962         {
963             cmp.setMaxlength(maxlength);
964         }
965 
966         if (field.getDisplaySize() != null)
967         {
968             size = field.getDisplaySize();
969         }
970         else
971         {
972             if (field.getMinSize() == null)
973             {
974                 // adjust automatically generated size info
975                 if (typeInfo != null && typeInfo.isNumber() && size > 10)
976                 {
977                     size = 10;
978                 }
979                 else if (size > 60) // max
980                 {
981                     size = 60;
982                 }
983             }
984         }
985 
986         if (size > -1)
987         {
988             cmp.setSize(size);
989         }
990 
991         attachRangeValidator(cmp, minValue, maxValue);
992         attachLengthValidator(cmp, field.getMinSize() != null ? field.getMinSize() : 0, maxlength);
993     }
994 
995     protected void attachRangeValidator(HtmlInputText cmp, Double minValue, Double maxValue)
996     {
997         DoubleRangeValidator vld = null;
998         if (minValue != null)
999         {
1000             if (vld == null)
1001             {
1002                 vld = new DoubleRangeValidator();
1003             }
1004             vld.setMinimum(minValue);
1005         }
1006         if (maxValue != null)
1007         {
1008             if (vld == null)
1009             {
1010                 vld = new DoubleRangeValidator();
1011             }
1012             vld.setMaximum(maxValue);
1013         }
1014 
1015         if (vld != null)
1016         {
1017             cmp.addValidator(vld);
1018         }
1019     }
1020 
1021     protected void attachLengthValidator(HtmlInputText cmp, int minSize, int maxSize)
1022     {
1023         LengthValidator vld = null;
1024         if (minSize != -1)
1025         {
1026             if (vld == null)
1027             {
1028                 vld = new LengthValidator();
1029             }
1030             vld.setMinimum(minSize);
1031         }
1032         if (maxSize != -1)
1033         {
1034             if (vld == null)
1035             {
1036                 vld = new LengthValidator();
1037             }
1038             vld.setMaximum(maxSize);
1039         }
1040 
1041         if (vld != null)
1042         {
1043             cmp.addValidator(vld);
1044         }
1045     }
1046 
1047     /**
1048      * setup defaults for input fields like required
1049      */
1050     public void initInputDefaults(UIInput cmp, MetaField field)
1051     {
1052         initDefaults(cmp, field);
1053         initValueBinding(cmp, field);
1054 
1055         if (Boolean.TRUE.equals(field.getRequired()))
1056         {
1057             cmp.setRequired(true);
1058         }
1059 
1060         initConverter(cmp, field);
1061     }
1062 
1063     /**
1064      * setup a converter if required
1065      */
1066     public void initConverter(UIOutput cmp, MetaField field)
1067     {
1068         // if there is no converter setup one now.
1069         // we need this if the binding point to a map instead to a bean.
1070         // For a map JSF cant determine the wanted value type
1071         if (cmp.getConverter() == null)
1072         {
1073             String dataSourceDescription = field.getDataSourceDescription();
1074             if ((Boolean.TRUE.equals(field.getDisplayOnly()) || isFieldDisplayOnly(field))
1075                 && dataSourceDescription != null)
1076             {
1077                 // create a special converter for display-only based on the dataSource
1078 
1079                 MethodBinding mbLabel = FacesContext.getCurrentInstance().getApplication().createMethodBinding(
1080                     dataSourceDescription,
1081                     new Class[]
1082                         {
1083                             field.getType()
1084                         });
1085 
1086                 cmp.setConverter(new DataSourceLabelConverter(mbLabel));
1087             }
1088             else
1089             {
1090                 Converter converter = doCreateConverter(field);
1091                 if (converter != null)
1092                 {
1093                     cmp.setConverter(converter);
1094                 }
1095             }
1096         }
1097     }
1098 
1099     /**
1100      * insert possible selection items
1101      */
1102     @SuppressWarnings("unchecked")
1103     public void initSelections(MetaField field, UIComponent cmp)
1104     {
1105         if (field.getAllowedSelections() == null)
1106         {
1107             return;
1108         }
1109 
1110         Selection[] selections = field.getAllowedSelections();
1111         for (Selection selection : selections)
1112         {
1113             UISelectItem si = new UISelectItem();
1114             String labelText = selection.getLabel();
1115             si.setItemLabel(translateText(labelText, labelText));
1116             si.setItemValue(selection.getValue());
1117             cmp.getChildren().add(si);
1118         }
1119     }
1120 
1121     @Override
1122     protected boolean buildField(MetaField field)
1123     {
1124         String id = getFieldId(field);
1125         if (newComponentListener.containsComponent(getDestCmp(), id))
1126         {
1127             return true;
1128         }
1129 
1130         if (field.getType() != null)
1131         {
1132             JsfGuiElementBuilder builder = BUILDER_MAP.get(field.getType().getName());
1133             if (builder != null)
1134             {
1135                 if (builder.buildElement(this, field))
1136                 {
1137                     return true;
1138                 }
1139             }
1140         }
1141 
1142         return super.buildField(field);
1143     }
1144 }