Coverage Report - de.smartics.properties.spi.core.constraint.jsr303.GenericPropertyConstraint
 
Classes in this File Line Coverage Branch Coverage Complexity
GenericPropertyConstraint
0%
0/95
0%
0/68
3.526
 
 1  
 /*
 2  
  * Copyright 2012-2013 smartics, Kronseder & Reiner GmbH
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package de.smartics.properties.spi.core.constraint.jsr303;
 17  
 
 18  
 import static de.smartics.util.lang.StaticAnalysis.UNCHECKED;
 19  
 
 20  
 import java.io.IOException;
 21  
 import java.io.ObjectInputStream;
 22  
 import java.lang.annotation.Annotation;
 23  
 import java.lang.reflect.Method;
 24  
 import java.util.List;
 25  
 import java.util.Locale;
 26  
 
 27  
 import javax.validation.ConstraintValidator;
 28  
 import javax.validation.ConstraintValidatorContext;
 29  
 import javax.validation.MessageInterpolator;
 30  
 import javax.validation.MessageInterpolator.Context;
 31  
 import javax.validation.Validation;
 32  
 import javax.validation.ValidatorFactory;
 33  
 import javax.validation.constraints.NotNull;
 34  
 import javax.validation.groups.Default;
 35  
 import javax.validation.metadata.ConstraintDescriptor;
 36  
 
 37  
 import org.hibernate.validator.constraints.NotBlank;
 38  
 import org.hibernate.validator.internal.engine.MessageInterpolatorContext;
 39  
 import org.hibernate.validator.internal.metadata.core.ConstraintHelper;
 40  
 
 41  
 import de.smartics.properties.spi.core.constraint.AbstractPropertyConstraint;
 42  
 import de.smartics.util.lang.Arg;
 43  
 import de.smartics.util.lang.NullArgumentException;
 44  
 
 45  
 /**
 46  
  * Generic wrapper for JSR-303 constraints.
 47  
  *
 48  
  * @param <A> the constraint annotation.
 49  
  * @param <T> the type of the property that is validated.
 50  
  */
 51  0
 public class GenericPropertyConstraint<A extends Annotation, T> extends
 52  
     AbstractPropertyConstraint<T>
 53  
 {
 54  
   // ********************************* Fields *********************************
 55  
 
 56  
   // --- constants ------------------------------------------------------------
 57  
 
 58  
   /**
 59  
    * The class version identifier.
 60  
    * <p>
 61  
    * The value of this constant is {@value}.
 62  
    */
 63  
   private static final long serialVersionUID = 1L;
 64  
 
 65  
   /**
 66  
    * Helper to fetch validators for default constraints.
 67  
    */
 68  0
   private static final ConstraintHelper CONSTRAINTS = new ConstraintHelper();
 69  
 
 70  
   // --- members --------------------------------------------------------------
 71  
 
 72  
   /**
 73  
    * The validator factory to access for message interpolation.
 74  
    */
 75  0
   private static final ValidatorFactory FACTORY = Validation
 76  
       .buildDefaultValidatorFactory();
 77  
 
 78  
   /**
 79  
    * The validator to validate values.
 80  
    */
 81  
   private transient ConstraintValidator<A, T> validator;
 82  
 
 83  
   // FIXME: Support list of validators.
 84  
 
 85  
   /**
 86  
    * The JSR-303 constraint annotation.
 87  
    *
 88  
    * @serial
 89  
    */
 90  
   private final A constraintAnnotation;
 91  
 
 92  
   /**
 93  
    * Stores the validator class for serialization.
 94  
    *
 95  
    * @serial
 96  
    */
 97  
   private final Class<? extends ConstraintValidator<A, T>> validatorClass;
 98  
 
 99  
   // ****************************** Initializer *******************************
 100  
 
 101  
   // ****************************** Constructors ******************************
 102  
 
 103  
   /**
 104  
    * Convenience constructor if the validator can be derived from the
 105  
    * {@code constraintAnnotation}.
 106  
    *
 107  
    * @param constraintAnnotation the JSR-303 constraint annotation.
 108  
    * @param propertyType the type of the property selects the validator if more
 109  
    *          than on validator is referenced by the constraint annotation.
 110  
    * @throws NullArgumentException if {@code constraintAnnotation} is
 111  
    *           <code>null</code>.
 112  
    */
 113  
   @SuppressWarnings(UNCHECKED)
 114  
   public GenericPropertyConstraint(final A constraintAnnotation,
 115  
       final Class<?> propertyType) throws NullArgumentException
 116  
   {
 117  0
     this((ConstraintValidator<A, T>) createValidator(constraintAnnotation,
 118  
         propertyType), constraintAnnotation);
 119  0
   }
 120  
 
 121  
   /**
 122  
    * Default constructor.
 123  
    *
 124  
    * @param validator the validator to validate values.
 125  
    * @param constraintAnnotation the JSR-303 constraint annotation.
 126  
    * @throws NullArgumentException if either {@code validator} or
 127  
    *           {@code constraintAnnotation} is <code>null</code>.
 128  
    */
 129  
   @SuppressWarnings(UNCHECKED)
 130  
   public GenericPropertyConstraint(final ConstraintValidator<A, T> validator,
 131  
       final A constraintAnnotation) throws NullArgumentException
 132  0
   {
 133  0
     this.validator = Arg.checkNotNull("validator", validator);
 134  0
     this.constraintAnnotation =
 135  
         Arg.checkNotNull("constraintAnnotation", constraintAnnotation);
 136  0
     this.validatorClass =
 137  
         (Class<? extends ConstraintValidator<A, T>>) validator.getClass();
 138  
 
 139  0
     validator.initialize(constraintAnnotation);
 140  0
   }
 141  
 
 142  
   // ****************************** Inner Classes *****************************
 143  
 
 144  
   // ********************************* Methods ********************************
 145  
 
 146  
   // --- init -----------------------------------------------------------------
 147  
 
 148  
   private static <A extends Annotation, T> ConstraintValidator<A, T> createValidator(
 149  
       final A constraintAnnotation, final Class<?> propertyType)
 150  
     throws IllegalArgumentException
 151  
   {
 152  0
     final javax.validation.Constraint annotation =
 153  
         constraintAnnotation.annotationType().getAnnotation(
 154  
             javax.validation.Constraint.class);
 155  0
     if (annotation != null)
 156  
     {
 157  0
       final Class<? extends ConstraintValidator<?, ?>>[] validatorClass =
 158  
           annotation.validatedBy();
 159  0
       if (validatorClass.length == 1)
 160  
       {
 161  0
         return selectValidator(validatorClass);
 162  
       }
 163  0
       if (validatorClass.length > 0)
 164  
       {
 165  0
         return selectValidator(validatorClass, propertyType);
 166  
       }
 167  
       else
 168  
       {
 169  0
         final Class<? extends Annotation> annotationType =
 170  
             constraintAnnotation.annotationType();
 171  0
         final List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> validators =
 172  
             CONSTRAINTS.getBuiltInConstraints(annotationType);
 173  0
         if (validators.size() == 1)
 174  
         {
 175  0
           return selectValidator(validators);
 176  
         }
 177  0
         if (!validators.isEmpty())
 178  
         {
 179  0
           return selectValidator(validators, propertyType);
 180  
         }
 181  
       }
 182  
     }
 183  
 
 184  0
     throw new IllegalArgumentException(
 185  
         "Cannot derive constraint validator from annotation: "
 186  
             + constraintAnnotation);
 187  
   }
 188  
 
 189  
   @SuppressWarnings({ UNCHECKED })
 190  
   private static <A extends Annotation, T> ConstraintValidator<A, T> selectValidator(
 191  
       final Class<? extends ConstraintValidator<?, ?>>[] validatorClass)
 192  
   {
 193  0
     final Class<? extends ConstraintValidator<A, T>> type =
 194  
         (Class<? extends ConstraintValidator<A, T>>) validatorClass[0];
 195  0
     final ConstraintValidator<A, T> validator = createValidator(type);
 196  0
     return validator;
 197  
   }
 198  
 
 199  
   @SuppressWarnings({ UNCHECKED, "rawtypes" })
 200  
   private static <T, A extends Annotation> ConstraintValidator<A, T> selectValidator(
 201  
       final List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> validators)
 202  
   {
 203  0
     final Class<? extends ConstraintValidator<A, T>> type =
 204  
         (Class<? extends ConstraintValidator<A, T>>) validators.get(0);
 205  0
     final ConstraintValidator validator = createValidator(type);
 206  0
     return validator;
 207  
   }
 208  
 
 209  
   @SuppressWarnings(UNCHECKED)
 210  
   private static <A extends Annotation, T> ConstraintValidator<A, T> selectValidator(
 211  
       final List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> validators,
 212  
       final Class<?> propertyType)
 213  
   {
 214  0
     final Class<? extends ConstraintValidator<?, ?>>[] x =
 215  
         validators.toArray(new Class[validators.size()]);
 216  0
     return selectValidator(x, propertyType);
 217  
   }
 218  
 
 219  
   @SuppressWarnings(UNCHECKED)
 220  
   private static <A extends Annotation, T> ConstraintValidator<A, T> selectValidator(
 221  
       final Class<? extends ConstraintValidator<?, ?>>[] validatorClass,
 222  
       final Class<?> propertyType)
 223  
   {
 224  0
     Class<? extends ConstraintValidator<A, T>> bestMatch =
 225  
         (Class<? extends ConstraintValidator<A, T>>) validatorClass[0];
 226  
 
 227  0
     for (final Class<? extends ConstraintValidator<A, T>> type : (Class<ConstraintValidator<A, T>>[]) validatorClass)
 228  
     {
 229  0
       for (final Method method : type.getMethods())
 230  
       {
 231  0
         if ("isValid".equals(method.getName()))
 232  
         {
 233  0
           final Class<?>[] types = method.getParameterTypes();
 234  0
           if (types.length > 0)
 235  
           {
 236  0
             final Class<?> paramType = types[0];
 237  0
             if (isArrayOrPrimitive(propertyType, paramType)
 238  
                 || isObjectButNotAnArray(propertyType, paramType))
 239  
             {
 240  0
               final ConstraintValidator<A, T> validator = createValidator(type);
 241  0
               return validator;
 242  
             }
 243  
             else
 244  
             {
 245  0
               if (Object.class.equals(paramType.getComponentType()))
 246  
               {
 247  0
                 bestMatch = type;
 248  
               }
 249  
             }
 250  
           }
 251  
         }
 252  
       }
 253  
     }
 254  
 
 255  0
     final ConstraintValidator<A, T> validator = createValidator(bestMatch);
 256  0
     return validator;
 257  
   }
 258  
 
 259  
   private static boolean isObjectButNotAnArray(final Class<?> propertyType,
 260  
       final Class<?> paramType)
 261  
   {
 262  0
     return !propertyType.isArray() && paramType.isAssignableFrom(propertyType);
 263  
   }
 264  
 
 265  
   private static boolean isArrayOrPrimitive(final Class<?> propertyType,
 266  
       final Class<?> paramType)
 267  
   {
 268  0
     return (propertyType.isArray() || propertyType.isPrimitive())
 269  
            && paramType.equals(propertyType);
 270  
   }
 271  
 
 272  
   private static <A extends Annotation, T> ConstraintValidator<A, T> createValidator(
 273  
       final Class<? extends ConstraintValidator<A, T>> validatorClass)
 274  
     throws IllegalArgumentException
 275  
   {
 276  0
     Throwable cause = null;
 277  
     try
 278  
     {
 279  0
       final ConstraintValidator<A, T> validator = validatorClass.newInstance();
 280  0
       return validator;
 281  
     }
 282  0
     catch (final InstantiationException e)
 283  
     {
 284  0
       cause = e;
 285  
       // Fail at the end
 286  
     }
 287  0
     catch (final IllegalAccessException e)
 288  
     {
 289  0
       cause = e;
 290  
       // Fail at the end
 291  0
     }
 292  
 
 293  0
     throw new IllegalArgumentException(
 294  
         "Cannot instantiate constraint validator: " + validatorClass.getName(),
 295  
         cause);
 296  
   }
 297  
 
 298  
   // --- get&set --------------------------------------------------------------
 299  
 
 300  
   // --- business -------------------------------------------------------------
 301  
 
 302  
   /**
 303  
    * {@inheritDoc}
 304  
    * <p>
 305  
    * Override to provide more specific information the the string representation
 306  
    * of the constraint annotation.
 307  
    * </p>
 308  
    *
 309  
    * @see de.smartics.properties.api.core.domain.PropertyConstraint#getDescription(java.util.Locale)
 310  
    */
 311  
   // CHECKSTYLE:OFF
 312  
   @Override
 313  
   public String getDescription(final Locale locale) // CHECKSTYLE:ON
 314  
   {
 315  0
     return new ConstraintPrettifier(constraintAnnotation).toString();
 316  
   }
 317  
 
 318  
   @Override
 319  
   public String getViolationMessage(final Locale locale,
 320  
       final Object actualValue)
 321  
   {
 322  0
     final MessageInterpolator mi = FACTORY.getMessageInterpolator();
 323  0
     final ConstraintDescriptor<A> desc =
 324  
         new PropertyConstraintDescriptor<A>(constraintAnnotation);
 325  0
     final Context messageContext =
 326  
         new MessageInterpolatorContext(desc, actualValue);
 327  0
     final String messageTemplate = extractMessageTemplate();
 328  0
     final String message =
 329  
         mi.interpolate(messageTemplate, messageContext, locale);
 330  0
     return message;
 331  
   }
 332  
 
 333  
   @Override
 334  
   public final boolean isValid(final T value, final Class<?> group)
 335  
   {
 336  0
     if (isConstraintActivated(group))
 337  
     {
 338  0
       final ConstraintValidatorContext context =
 339  
           new PropertyConstraintValidatorContext();
 340  0
       final boolean valid = validator.isValid(value, context);
 341  0
       return valid;
 342  
     }
 343  0
     return true;
 344  
   }
 345  
 
 346  
   private boolean isConstraintActivated(final Class<?> group)
 347  
   {
 348  
     try
 349  
     {
 350  0
       final Class<?>[] groups =
 351  
           (Class<?>[]) constraintAnnotation.annotationType()
 352  
               .getMethod("groups").invoke(constraintAnnotation);
 353  0
       return ((group == null || group == Default.class) && hasNoGroups(groups))
 354  
              || (group != null && group != Default.class && isInGroup(groups,
 355  
                  group));
 356  
     }
 357  0
     catch (final Exception e)
 358  
     {
 359  0
       return true;
 360  
     }
 361  
   }
 362  
 
 363  
   private boolean hasNoGroups(final Class<?>[] groups)
 364  
   {
 365  
     try
 366  
     {
 367  0
       return groups == null || groups.length == 0
 368  
              || (groups.length == 1 && groups[0] == Default.class);
 369  
     }
 370  0
     catch (final Exception e)
 371  
     {
 372  0
       return true;
 373  
     }
 374  
   }
 375  
 
 376  
   private boolean isInGroup(final Class<?>[] groups, final Class<?> group)
 377  
   {
 378  0
     assert (group != null);
 379  
     try
 380  
     {
 381  0
       for (final Class<?> current : groups)
 382  
       {
 383  0
         if (current.isAssignableFrom(group))
 384  
         {
 385  0
           return true;
 386  
         }
 387  
       }
 388  
     }
 389  0
     catch (final Exception e)
 390  
     {
 391  
       // Defaults to returning false.
 392  0
     }
 393  0
     return false;
 394  
   }
 395  
 
 396  
   private String extractMessageTemplate()
 397  
   {
 398  
     try
 399  
     {
 400  0
       return String.valueOf(constraintAnnotation.annotationType()
 401  
           .getMethod("message").invoke(constraintAnnotation));
 402  
     }
 403  0
     catch (final Exception e)
 404  
     {
 405  0
       return "";
 406  
     }
 407  
   }
 408  
 
 409  
   @Override
 410  
   public final boolean isMandatoryConstraint()
 411  
   {
 412  0
     final Class<? extends Annotation> type =
 413  
         constraintAnnotation.annotationType();
 414  0
     return NotNull.class == type || NotBlank.class == type;
 415  
   }
 416  
 
 417  
   // --- object basics --------------------------------------------------------
 418  
 
 419  
   /**
 420  
    * Reads the object from the given stream.
 421  
    *
 422  
    * @param in the stream to read from.
 423  
    * @throws IOException on read problems.
 424  
    * @throws ClassNotFoundException if a class cannot be found.
 425  
    */
 426  
   private void readObject(final ObjectInputStream in) throws IOException,
 427  
     ClassNotFoundException
 428  
   {
 429  0
     in.defaultReadObject();
 430  
 
 431  
     try
 432  
     {
 433  0
       this.validator = validatorClass.newInstance();
 434  
     }
 435  0
     catch (final Exception e)
 436  
     {
 437  0
       throw new IOException("Cannot deserialize validator.", e);
 438  0
     }
 439  0
   }
 440  
 }