View Javadoc

1   package org.apache.onami.validation;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static java.lang.String.format;
23  import static java.util.Arrays.deepToString;
24  
25  import java.lang.reflect.Constructor;
26  import java.lang.reflect.Method;
27  import java.util.Arrays;
28  import java.util.HashSet;
29  import java.util.Set;
30  
31  import javax.inject.Inject;
32  import javax.validation.ConstraintViolation;
33  import javax.validation.ConstraintViolationException;
34  import javax.validation.Validator;
35  import javax.validation.ValidatorFactory;
36  
37  import org.aopalliance.intercept.MethodInterceptor;
38  import org.aopalliance.intercept.MethodInvocation;
39  import org.apache.bval.jsr303.extensions.MethodValidator;
40  
41  /**
42   * Method interceptor for {@link Validate} annotation.
43   */
44  final class ValidateMethodInterceptor
45      implements MethodInterceptor
46  {
47  
48      private static final Class<?>[] CAUSE_TYPES = new Class[] { Throwable.class };
49  
50      private static final Class<?>[] MESSAGE_CAUSE_TYPES = new Class[] { String.class, Throwable.class };
51  
52      /**
53       * The {@link ValidatorFactory} reference.
54       */
55      @Inject
56      private ValidatorFactory validatorFactory;
57  
58      /**
59       * {@inheritDoc}
60       */
61      public Object invoke( MethodInvocation invocation )
62          throws Throwable
63      {
64          Validate validate = invocation.getMethod().getAnnotation( Validate.class );
65  
66          Validator validator = validatorFactory.getValidator();
67          MethodValidator methodValidator = validator.unwrap( MethodValidator.class );
68  
69          Set<ConstraintViolation<?>> constraintViolations = new HashSet<ConstraintViolation<?>>();
70          Class<?> clazz = invocation.getMethod().getDeclaringClass();
71          Method method = invocation.getMethod();
72          Object[] arguments = invocation.getArguments();
73          Class<?>[] groups = validate.groups();
74  
75          constraintViolations.addAll( methodValidator.validateParameters( clazz, method, arguments, groups ) );
76  
77          if ( !constraintViolations.isEmpty() )
78          {
79              throw getException( new ConstraintViolationException( format( "Validation error when calling method '%s' with arguments %s",
80                                                                            method,
81                                                                            deepToString( arguments ) ),
82                                                                    constraintViolations ),
83                                  validate.rethrowExceptionsAs(),
84                                  validate.exceptionMessage(),
85                                  arguments );
86          }
87  
88          Object returnedValue = invocation.proceed();
89  
90          if ( validate.validateReturnedValue() )
91          {
92              constraintViolations.addAll( methodValidator.validateReturnedValue( clazz, method, returnedValue, groups ) );
93  
94              if ( !constraintViolations.isEmpty() )
95              {
96                  throw getException( new ConstraintViolationException( format( "Method '%s' returned a not valid value %s",
97                                                                                method,
98                                                                                returnedValue ),
99                                                                        constraintViolations ),
100                                     validate.rethrowExceptionsAs(),
101                                     validate.exceptionMessage(),
102                                     arguments );
103             }
104         }
105 
106         return returnedValue;
107     }
108 
109     /**
110      * Define the {@link Throwable} has to be thrown when a validation error occurs and the user defined the custom
111      * error wrapper.
112      *
113      * @param exception the occurred validation error.
114      * @param exceptionWrapperClass the user defined custom error wrapper.
115      * @return the {@link Throwable} has o be thrown.
116      */
117     private static Throwable getException( ConstraintViolationException exception,
118                                            Class<? extends Throwable> exceptionWrapperClass,
119                                            String exceptionMessage,
120                                            Object[] arguments )
121     {
122         // check the thrown exception is of same re-throw type
123         if ( exceptionWrapperClass == ConstraintViolationException.class )
124         {
125             return exception;
126         }
127 
128         // re-throw the exception as new exception
129         Throwable rethrowEx = null;
130 
131         String errorMessage;
132         Object[] initargs;
133         Class<?>[] initargsType;
134 
135         if ( exceptionMessage.length() != 0 )
136         {
137             errorMessage = format( exceptionMessage, arguments );
138             initargs = new Object[] { errorMessage, exception };
139             initargsType = MESSAGE_CAUSE_TYPES;
140         }
141         else
142         {
143             initargs = new Object[] { exception };
144             initargsType = CAUSE_TYPES;
145         }
146 
147         Constructor<? extends Throwable> exceptionConstructor = getMatchingConstructor( exceptionWrapperClass,
148                                                                                         initargsType );
149         if ( exceptionConstructor != null )
150         {
151             try
152             {
153                 rethrowEx = exceptionConstructor.newInstance( initargs );
154             }
155             catch ( Exception e )
156             {
157                 errorMessage = format( "Impossible to re-throw '%s', it needs the constructor with %s argument(s).",
158                                        exceptionWrapperClass.getName(),
159                                        Arrays.toString( initargsType ) );
160                 rethrowEx = new RuntimeException( errorMessage, e );
161             }
162         }
163         else
164         {
165             errorMessage = format( "Impossible to re-throw '%s', it needs the constructor with %s or %s argument(s).",
166                                    exceptionWrapperClass.getName(), Arrays.toString( CAUSE_TYPES ),
167                                    Arrays.toString( MESSAGE_CAUSE_TYPES ) );
168             rethrowEx = new RuntimeException( errorMessage );
169         }
170 
171         return rethrowEx;
172     }
173 
174     @SuppressWarnings( "unchecked" )
175     private static <E extends Throwable> Constructor<E> getMatchingConstructor( Class<E> type, Class<?>[] argumentsType )
176     {
177         Class<? super E> currentType = type;
178         while ( Object.class != currentType )
179         {
180             for ( Constructor<?> constructor : currentType.getConstructors() )
181             {
182                 if ( Arrays.equals( argumentsType, constructor.getParameterTypes() ) )
183                 {
184                     return (Constructor<E>) constructor;
185                 }
186             }
187             currentType = currentType.getSuperclass();
188         }
189         return null;
190     }
191 
192 }