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.view.facelets.tag.jsf.core;
20  
21  import java.io.IOException;
22  import java.lang.reflect.Field;
23  
24  import javax.faces.component.UIInput;
25  import javax.faces.component.UIViewRoot;
26  import javax.faces.validator.BeanValidator;
27  import javax.faces.validator.Validator;
28  
29  import org.apache.myfaces.util.ExternalSpecifications;
30  import org.apache.myfaces.view.facelets.FaceletTestCase;
31  import org.junit.Assert;
32  import org.junit.Test;
33  
34  /**
35   * Tests for <f:validateBean />.
36   * 
37   * @author Jakob Korherr (latest modification by $Author: lu4242 $)
38   * @version $Revision: 1195560 $ $Date: 2011-10-31 11:40:09 -0500 (Mon, 31 Oct 2011) $
39   */
40  public class ValidateBeanTestCase extends FaceletTestCase
41  {
42      
43      @Override
44      public void setUp() throws Exception
45      {
46          super.setUp();
47          
48          // configure bean validation to be available
49          _setBeanValidationAvailable(true);
50      }
51  
52      /**
53       * Sets the cached field in ExternalSpecifications to enable or 
54       * disable bean validation for testing purposes.
55       * 
56       * @param available
57       */
58      private void _setBeanValidationAvailable(boolean available)
59      {
60          try
61          {
62              Field field = ExternalSpecifications.class.getDeclaredField("beanValidationAvailable");
63              field.setAccessible(true);
64              field.set(ExternalSpecifications.class, available);
65          }
66          catch (Exception e)
67          {
68              throw new IllegalStateException("Could not configure BeanValidation for the test case.", e);
69          }
70      }
71      
72      /**
73       * Gets the validator from the given UIInput with the given validatorClass
74       * or null if the UIInput does not have such a validator installed.
75       * @param <T>
76       * @param input
77       * @param validatorClass
78       * @return
79       */
80      @SuppressWarnings("unchecked")
81      private <T> T _getValidator(UIInput input, Class<T> validatorClass)
82      {
83          Validator[] validators = input.getValidators();
84          if (validators != null)
85          {
86              for (Validator validator : validators)
87              {
88                  if (validatorClass.isAssignableFrom(validator.getClass()))
89                  {
90                      return (T) validator;
91                  }
92              }
93          }
94          
95          return null;
96      }
97      
98      /**
99       * Tests if the given UIInput has a validator of the given validatorClass installed.
100      * @param input
101      * @param validatorClass
102      * @return
103      */
104     private boolean _hasValidator(UIInput input, Class<?> validatorClass)
105     {
106         return (_getValidator(input, validatorClass) != null);
107     }
108     
109     /**
110      * Gets the attached BeanValidator from the given UIInput and returns its
111      * validationGroups. Returns null if no BeanValidator is attached.
112      * @param input
113      * @return
114      */
115     private String _getValidationGroups(UIInput input)
116     {
117         BeanValidator validator = _getValidator(input, BeanValidator.class);
118         if (validator != null)
119         {
120             return validator.getValidationGroups();
121         }
122         return null;
123     }
124 
125     /**
126      * Tests the case that the BeanValidator is not a default validator,
127      * but the UIInput has a <f:validateBean /> child tag on the facelet.
128      * In this case the BeanValidator has to be installed.
129      * @throws IOException
130      */
131     @Test
132     @SuppressWarnings("unchecked")
133     public void testBeanValidatorInstalledManually() throws IOException
134     {
135         // put the disabled value on the request scope
136         externalContext.getRequestMap().put("validateBeanDisabled", Boolean.FALSE);
137         
138         // build testValidateBean.xhtml
139         UIViewRoot root = facesContext.getViewRoot();
140         vdl.buildView(facesContext, root, "testValidateBean.xhtml");
141         
142         // get the component instances
143         UIInput input = (UIInput) root.findComponent("form:input");
144         
145         // the UIInput has to have the BeanValidator installed
146         Assert.assertTrue(_hasValidator(input, BeanValidator.class));
147     }
148     
149     /**
150      * Tests the case that the UIInput has no nested <f:validateBean>
151      * on the facelet, but the BeanValidator is a default validator.
152      * In this case the BeanValidator has to be installed.
153      * @throws IOException
154      */
155     @Test
156     public void testBeanValidatorInstalledAutomatically() throws IOException
157     {
158         // add the BeanValidator as default-validator
159         application.addDefaultValidatorId(BeanValidator.VALIDATOR_ID);
160         
161         // build testValidateBeanEmptyInput.xhtml
162         UIViewRoot root = facesContext.getViewRoot();
163         vdl.buildView(facesContext, root, "testValidateBeanEmptyInput.xhtml");
164         
165         // get the component instances
166         UIInput input = (UIInput) root.findComponent("form:input");
167         
168         // the UIInput has to have the BeanValidator installed
169         Assert.assertTrue(_hasValidator(input, BeanValidator.class));
170     }
171     
172     /**
173      * Tests the case that the BeanValidator is a default validator, but the
174      * UIInput has a nested <f:validateBean disabled="true" /> on the facelet.
175      * In this case the BeanValidator must not be installed.
176      * @throws IOException
177      */
178     @Test
179     @SuppressWarnings("unchecked")
180     public void testDisabledBeanValidatorNotInstalled() throws IOException
181     {
182         // add the BeanValidator as default-validator
183         application.addDefaultValidatorId(BeanValidator.VALIDATOR_ID);
184         
185         // put the disabled value on the request scope
186         externalContext.getRequestMap().put("validateBeanDisabled", Boolean.TRUE);
187         
188         // build testValidateBean.xhtml
189         UIViewRoot root = facesContext.getViewRoot();
190         vdl.buildView(facesContext, root, "testValidateBean.xhtml");
191         
192         // get the component instances
193         UIInput input = (UIInput) root.findComponent("form:input");
194         
195         // the UIInput must not have the BeanValidator installed
196         Assert.assertFalse(_hasValidator(input, BeanValidator.class));
197     }
198     
199     /**
200      * Tests the case when <f:validateBean> is used in wrapping mode
201      * and the BeanValidator is not a default-validator.
202      * In this case the BeanValidator has to be installed on all
203      * EditableValueHolders which he is nesting and must not be installed
204      * on any other EditableValueHolder on the facelet.
205      * @throws IOException
206      */
207     @Test
208     @SuppressWarnings("unchecked")
209     public void testNestingValidateBean() throws IOException
210     {
211         // put the disabled value on the request scope
212         externalContext.getRequestMap().put("validateBeanDisabled", Boolean.FALSE);
213         
214         // build testValidateBeanNesting.xhtml
215         UIViewRoot root = facesContext.getViewRoot();
216         vdl.buildView(facesContext, root, "testValidateBeanNesting.xhtml");
217         
218         // get the component instances
219         UIInput nestedinput = (UIInput) root.findComponent("form:nestedinput");
220         UIInput doublenestedinput = (UIInput) root.findComponent("form:doublenestedinput");
221         UIInput nestedinput2 = (UIInput) root.findComponent("form:nestedinput2");
222         UIInput nonnestedinput = (UIInput) root.findComponent("form:nonnestedinput");
223         
224         // all wrapped UIInputs have to have the BeanValidator installed
225         Assert.assertTrue(_hasValidator(nestedinput, BeanValidator.class));
226         Assert.assertTrue(_hasValidator(doublenestedinput, BeanValidator.class));
227         Assert.assertTrue(_hasValidator(nestedinput2, BeanValidator.class));
228         Assert.assertFalse(_hasValidator(nonnestedinput, BeanValidator.class));
229     }
230     
231     /**
232      * Tests the case when <f:validateBean> is used in wrapping mode,
233      * its disabled attribute is true and the BeanValidator is a default-validator.
234      * In this case the BeanValidator has to be installed on all
235      * EditableValueHolders which are not nested and must not be installed
236      * on all nested EditableValueHolders on the facelet.
237      * @throws IOException
238      */
239     @Test
240     @SuppressWarnings("unchecked")
241     public void testNestingValidateBeanDisabled() throws IOException
242     {
243         // put the disabled value on the request scope
244         externalContext.getRequestMap().put("validateBeanDisabled", Boolean.TRUE);
245         
246         // add the BeanValidator as default-validator
247         application.addDefaultValidatorId(BeanValidator.VALIDATOR_ID);
248         
249         // build testValidateBeanNesting.xhtml
250         UIViewRoot root = facesContext.getViewRoot();
251         vdl.buildView(facesContext, root, "testValidateBeanNesting.xhtml");
252         
253         // get the component instances
254         UIInput nestedinput = (UIInput) root.findComponent("form:nestedinput");
255         UIInput doublenestedinput = (UIInput) root.findComponent("form:doublenestedinput");
256         UIInput nestedinput2 = (UIInput) root.findComponent("form:nestedinput2");
257         UIInput nonnestedinput = (UIInput) root.findComponent("form:nonnestedinput");
258         
259         // all wrapped UIInputs have to have the BeanValidator installed
260         Assert.assertFalse(_hasValidator(nestedinput, BeanValidator.class));
261         Assert.assertFalse(_hasValidator(doublenestedinput, BeanValidator.class));
262         Assert.assertFalse(_hasValidator(nestedinput2, BeanValidator.class));
263         Assert.assertTrue(_hasValidator(nonnestedinput, BeanValidator.class));
264     }
265     
266     /**
267      * Tests the case that there are two <f:validateBean> tags, one disabled and
268      * one not. The disabled one should add the validator id of the BeanValidator
269      * to an default-validator exclusion list on the parent (<h:inputText>) 
270      * whereas the enabled one should manually add the BeanValidator no matter what.
271      * In this case the BeanValidator has to be installed (see also MYFACES-2731).
272      * @throws IOException
273      */
274     @Test
275     public void testValidateBeanDisabledAndEnabled() throws IOException
276     {
277         // build testValidateBeanDisabledAndEnabled.xhtml
278         UIViewRoot root = facesContext.getViewRoot();
279         vdl.buildView(facesContext, root, "testValidateBeanDisabledAndEnabled.xhtml");
280         
281         // get the component instances
282         UIInput input = (UIInput) root.findComponent("form:input");
283         
284         // the UIInput has to have the BeanValidator installed
285         Assert.assertTrue(_hasValidator(input, BeanValidator.class));
286     }
287     
288     /**
289      * Tests the case that there are a wrapping f:validateBean with disabled
290      * set to true and one nested f:validateBean. Also the BeanValidator is
291      * a default-validator.
292      * In this case all EditableValueHolders outside the wrapping f:validateBean
293      * and the one EditableValueHolder which is wrapped, but has a f:validateBean
294      * itself should have the BeanValidator installed.
295      * @throws IOException
296      */
297     @Test
298     @SuppressWarnings("unchecked")
299     public void testValidateBeanNestingAndNested() throws IOException
300     {
301         // add the BeanValidator as default-validator
302         application.addDefaultValidatorId(BeanValidator.VALIDATOR_ID);
303         
304         // put the disabled value on the request scope
305         externalContext.getRequestMap().put("validateBeanDisabled", Boolean.TRUE);
306         
307         // build testValidateBeanNestingAndNested.xhtml
308         UIViewRoot root = facesContext.getViewRoot();
309         vdl.buildView(facesContext, root, "testValidateBeanNestingAndNested.xhtml");
310         
311         // get the component instances
312         UIInput nestedinput = (UIInput) root.findComponent("form:nestedinput");
313         UIInput nestedinputWithValidator = (UIInput) root.findComponent("form:nestedinputWithValidator");
314         UIInput nonnestedinput = (UIInput) root.findComponent("form:nonnestedinput");
315         
316         // no wrapped UIInput has to have the BeanValidator installed,
317         // except the one nesting <f:validateBean /> itself.
318         Assert.assertFalse(_hasValidator(nestedinput, BeanValidator.class));
319         Assert.assertTrue(_hasValidator(nestedinputWithValidator, BeanValidator.class));
320         Assert.assertTrue(_hasValidator(nonnestedinput, BeanValidator.class));
321     }
322     
323     /**
324      * Tests if the validationGroups are set correctly.
325      * @throws IOException
326      */
327     @Test
328     @SuppressWarnings("unchecked")
329     public void testValidateBeanValidationGroups() throws IOException
330     {
331         final String validationGroups = "org.apache.myfaces.beanvalidation.Group1," +
332                 "org.apache.myfaces.beanvalidation.Group2";
333         
334         // put the validationGroups on the request scope
335         externalContext.getRequestMap().put("validationGroups", validationGroups);
336         
337         // build testValidateBean.xhtml
338         UIViewRoot root = facesContext.getViewRoot();
339         vdl.buildView(facesContext, root, "testValidateBean.xhtml");
340         
341         // get the component instances
342         UIInput input = (UIInput) root.findComponent("form:input");
343         
344         // the validationGroups have to match
345         Assert.assertEquals(validationGroups, _getValidationGroups(input));
346     }
347     
348     /**
349      * Tests the case that the BeanValidator is a default validator and the
350      * UIInput on the facelet does not have a custom <f:validateBean> tag.
351      * In this case the Default validation group has to be set on the BeanValidator.
352      * @throws IOException
353      */
354     @Test
355     public void testValidateBeanDefaultValidationGroup() throws IOException
356     {
357         // add the BeanValidator as default-validator
358         application.addDefaultValidatorId(BeanValidator.VALIDATOR_ID);
359         
360         // build testValidateBeanEmptyInput.xhtml
361         UIViewRoot root = facesContext.getViewRoot();
362         vdl.buildView(facesContext, root, "testValidateBeanEmptyInput.xhtml");
363         
364         // get the component instances
365         UIInput input = (UIInput) root.findComponent("form:input");
366         
367         // the validationGroups have to match the Default ones
368         Assert.assertEquals(javax.validation.groups.Default.class.getName(),
369                 _getValidationGroups(input));
370     }
371     
372     /**
373      * Tests the case that there is a wrapping <f:validateBean> on the facelet
374      * with some validationGroups. These validationGroups should be applied to
375      * all automatically added BeanValidators of all nested UIInput components.
376      * The UIInput outside of the wrapping <f:validateBean> should get the 
377      * Default validation group.
378      * @throws IOException
379      */
380     @Test
381     @SuppressWarnings("unchecked")
382     public void testValidateBeanValidationGroupsNested() throws IOException
383     {
384         final String validationGroups = "org.apache.myfaces.beanvalidation.Group1";
385         
386         // put the validationGroups on the request scope
387         externalContext.getRequestMap().put("validationGroups", validationGroups);
388         
389         // add the BeanValidator as default-validator
390         application.addDefaultValidatorId(BeanValidator.VALIDATOR_ID);
391         
392         // build testValidateBeanNesting.xhtml
393         UIViewRoot root = facesContext.getViewRoot();
394         vdl.buildView(facesContext, root, "testValidateBeanNesting.xhtml");
395         
396         // get the component instances
397         UIInput nestedinput = (UIInput) root.findComponent("form:nestedinput");
398         UIInput doublenestedinput = (UIInput) root.findComponent("form:doublenestedinput");
399         UIInput nestedinput2 = (UIInput) root.findComponent("form:nestedinput2");
400         UIInput nonnestedinput = (UIInput) root.findComponent("form:nonnestedinput");
401         
402         // the validationGroups in the wrapped components have to match
403         // org.apache.myfaces.beanvalidation.Group1 and the non-nested ones
404         // have to match the Default group.
405         Assert.assertEquals(validationGroups, _getValidationGroups(nestedinput));
406         Assert.assertEquals(validationGroups, _getValidationGroups(doublenestedinput));
407         Assert.assertEquals(validationGroups, _getValidationGroups(nestedinput2));
408         Assert.assertEquals(javax.validation.groups.Default.class.getName(),
409                 _getValidationGroups(nonnestedinput));
410     }
411     
412     /**
413      * Tests the case that there is a wrapping <f:validateBean> with some validationGroups
414      * and one nested component has its own <f:validateBean> with different validationGroups.
415      * In this case the nested component without a validator must get the wrapping validationGroups,
416      * the nested component with the validator must get the validationGroups from the 
417      * validator and the non-nested component must get the Default validationGroups.
418      * @throws IOException
419      */
420     @Test
421     @SuppressWarnings("unchecked")
422     public void testValidateBeanValidationGroupsNestingAndNested() throws IOException
423     {
424         final String wrappingValidationGroups = "org.apache.myfaces.beanvalidation.Group1";
425         final String componentValidationGroups = "org.apache.myfaces.beanvalidation.ACompletelyOtherGroup";
426         
427         // add the BeanValidator as default-validator
428         application.addDefaultValidatorId(BeanValidator.VALIDATOR_ID);
429         
430         // put the disabled value on the request scope
431         externalContext.getRequestMap().put("validateBeanDisabled", Boolean.FALSE);
432         
433         // put the validationGroups on the request scope
434         externalContext.getRequestMap().put("validationGroups", wrappingValidationGroups);
435         externalContext.getRequestMap().put("validationGroupsComponent", componentValidationGroups);
436         
437         // build testValidateBeanNestingAndNested.xhtml
438         UIViewRoot root = facesContext.getViewRoot();
439         vdl.buildView(facesContext, root, "testValidateBeanNestingAndNested.xhtml");
440         
441         // get the component instances
442         UIInput nestedinput = (UIInput) root.findComponent("form:nestedinput");
443         UIInput nestedinputWithValidator = (UIInput) root.findComponent("form:nestedinputWithValidator");
444         UIInput nonnestedinput = (UIInput) root.findComponent("form:nonnestedinput");
445         
446         // the nested component without a validator must get the wrapping validationGroups,
447         // the nested component with the validator must get the validationGroups from the 
448         // validator and the non-nested component must get the Default validationGroups.
449         Assert.assertEquals(wrappingValidationGroups, _getValidationGroups(nestedinput));
450         Assert.assertEquals(componentValidationGroups, _getValidationGroups(nestedinputWithValidator));
451         Assert.assertEquals(javax.validation.groups.Default.class.getName(),
452                 _getValidationGroups(nonnestedinput));
453     }
454     
455     /**
456      * Tests the case that there are two wrapping <f:validateBean> with different
457      * validationGroups.
458      * In this case the nested component must get the outer wrapping validationGroups,
459      * the double-nested component must get the inner wrapping validationGroups
460      * and the non-nested component must get the Default validationGroups.
461      * @throws IOException
462      */
463     @Test
464     @SuppressWarnings("unchecked")
465     public void testValidateBeanValidationGroupsDoubleNesting() throws IOException
466     {
467         final String validationGroupsOuter = "org.apache.myfaces.beanvalidation.OuterGroup";
468         final String validationGroupsInner = "org.apache.myfaces.beanvalidation.InnerGroup";
469         
470         // add the BeanValidator as default-validator
471         application.addDefaultValidatorId(BeanValidator.VALIDATOR_ID);
472         
473         // put the validationGroups on the request scope
474         externalContext.getRequestMap().put("validationGroupsOuter", validationGroupsOuter);
475         externalContext.getRequestMap().put("validationGroupsInner", validationGroupsInner);
476         
477         // build testValidateBeanDoubleNesting.xhtml
478         UIViewRoot root = facesContext.getViewRoot();
479         vdl.buildView(facesContext, root, "testValidateBeanDoubleNesting.xhtml");
480         
481         // get the component instances
482         UIInput nestedinput = (UIInput) root.findComponent("form:nestedinput");
483         UIInput doublenestedinput = (UIInput) root.findComponent("form:doublenestedinput");
484         UIInput nonnestedinput = (UIInput) root.findComponent("form:nonnestedinput");
485         
486         // the nested component must get the outer wrapping validationGroups,
487         // the double-nested component must get the inner wrapping validationGroups
488         // and the non-nested component must get the Default validationGroups.
489         Assert.assertEquals(validationGroupsOuter, _getValidationGroups(nestedinput));
490         Assert.assertEquals(validationGroupsInner, _getValidationGroups(doublenestedinput));
491         Assert.assertEquals(javax.validation.groups.Default.class.getName(),
492                 _getValidationGroups(nonnestedinput));
493     }
494     
495     /**
496      * Tests the case that the BeanValidator is installed as a default-validator,
497      * but bean validation is not available in the classpath.
498      * In this case the BeanValidator must not be installed. However MyFaces
499      * provides a log message for this scenario.
500      * @throws IOException
501      */
502     @Test
503     public void testValidateBeanWithBeanValidationNotAvailable() throws IOException
504     {
505         // add the BeanValidator as default-validator
506         application.addDefaultValidatorId(BeanValidator.VALIDATOR_ID);
507         
508         // configure bean validation to be not available
509         _setBeanValidationAvailable(false);
510         
511         // build testValidateBeanEmptyInput.xhtml
512         UIViewRoot root = facesContext.getViewRoot();
513         vdl.buildView(facesContext, root, "testValidateBeanEmptyInput.xhtml");
514         
515         // get the component instances
516         UIInput input = (UIInput) root.findComponent("form:input");
517         
518         // the UIInput must not have the BeanValidator installed,
519         // because bean validation is not available
520         Assert.assertFalse(_hasValidator(input, BeanValidator.class));
521     }
522 
523     /**
524      * 
525      * @throws IOException
526      */
527     @Test
528     @SuppressWarnings("unchecked")
529     public void testValidateBeanValidationDisabledDoubleNesting() throws IOException
530     {
531         final String validationGroupsOuter = "org.apache.myfaces.beanvalidation.OuterGroup";
532         final String validationGroupsInner = "org.apache.myfaces.beanvalidation.InnerGroup";
533         
534         // add the BeanValidator as default-validator
535         application.addDefaultValidatorId(BeanValidator.VALIDATOR_ID);
536         
537         // put the validationGroups on the request scope
538         externalContext.getRequestMap().put("validationGroupsOuter", validationGroupsOuter);
539         externalContext.getRequestMap().put("validationGroupsInner", validationGroupsInner);
540         
541         // build testValidateBeanDoubleNesting.xhtml
542         UIViewRoot root = facesContext.getViewRoot();
543         vdl.buildView(facesContext, root, "testValidateBeanDisableDoubleNesting.xhtml");
544         
545         // get the component instances
546         UIInput nestedinput = (UIInput) root.findComponent("form:nestedinput");
547         UIInput doublenestedinput = (UIInput) root.findComponent("form:doublenestedinput");
548         UIInput nonnestedinput = (UIInput) root.findComponent("form:nonnestedinput");
549         UIInput nesteouterdisabledinput = (UIInput) root.findComponent("form:nesteouterdisabledinput");
550         
551         // the nested component must get the outer wrapping validationGroups,
552         // the double-nested component must get the inner wrapping validationGroups
553         // and the non-nested component must get the Default validationGroups.
554         Assert.assertEquals(validationGroupsOuter, _getValidationGroups(nestedinput));
555         Assert.assertFalse(_hasValidator(doublenestedinput, BeanValidator.class));
556         Assert.assertEquals(validationGroupsInner, _getValidationGroups(nesteouterdisabledinput));
557         Assert.assertEquals(javax.validation.groups.Default.class.getName(),
558                 _getValidationGroups(nonnestedinput));
559     }
560 
561 }