All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.beanvalidation.tck.util.TestUtil Maven / Gradle / Ivy

/*
* JBoss, Home of Professional Open Source
* Copyright 2009, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hibernate.beanvalidation.tck.util;

import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.validation.Configuration;
import javax.validation.ConstraintViolation;
import javax.validation.ElementKind;
import javax.validation.MessageInterpolator;
import javax.validation.Path;
import javax.validation.Path.Node;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.bootstrap.ProviderSpecificBootstrap;
import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.ConstructorDescriptor;
import javax.validation.metadata.ElementDescriptor;
import javax.validation.metadata.MethodDescriptor;
import javax.validation.metadata.PropertyDescriptor;
import javax.validation.spi.ValidationProvider;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.FileAssert.fail;

/**
 * @author Hardy Ferentschik
 * @author Gunnar Morling
 */
public final class TestUtil {

	/**
	 * Expected name for return value nodes.
	 */
	public static final String BEAN_NODE_NAME = null;

	/**
	 * Expected name for return value nodes.
	 */
	public static final String RETURN_VALUE_NODE_NAME = "";

	/**
	 * Expected name for cross-parameter nodes.
	 */
	public static final String CROSS_PARAMETER_NODE_NAME = "";

	private static final String VALIDATION_PROVIDER_TEST_CLASS = "validation.provider";

	private static ValidationProvider validationProviderUnderTest;

	private TestUtil() {
	}

	public static Validator getValidatorUnderTest() {
		return getValidatorFactoryUnderTest().getValidator();
	}

	public static ValidationProvider getValidationProviderUnderTest() {
		if ( validationProviderUnderTest == null ) {
			instantiateValidationProviderUnderTest();
		}
		return validationProviderUnderTest;
	}

	public static ValidatorFactory getValidatorFactoryUnderTest() {
		Configuration config = getConfigurationUnderTest();
		return config.buildValidatorFactory();
	}

	public static Configuration getConfigurationUnderTest() {
		if ( validationProviderUnderTest == null ) {
			instantiateValidationProviderUnderTest();
		}

		@SuppressWarnings("unchecked")
		ProviderSpecificBootstrap bootstrap = Validation.byProvider( validationProviderUnderTest.getClass() );
		return bootstrap.configure();
	}

	public static MessageInterpolator getDefaultMessageInterpolator() {
		Configuration config = getConfigurationUnderTest();
		return config.getDefaultMessageInterpolator();
	}

	public static  void assertCorrectNumberOfViolations(Set> violations, int expectedViolations) {
		assertEquals(
				violations.size(),
				expectedViolations,
				"Wrong number of constraint violations. Actual violations: " + violations
		);
	}

	public static  void assertCorrectConstraintViolationMessages(Set> violations, String... messages) {
		List actualMessages = new ArrayList();
		for ( ConstraintViolation violation : violations ) {
			actualMessages.add( violation.getMessage() );
		}

		List expectedMessages = Arrays.asList( messages );

		Collections.sort( actualMessages );
		Collections.sort( expectedMessages );

		assertEquals(
				actualMessages,
				expectedMessages,
				String.format( "Expected messages %s, but got %s.", expectedMessages, actualMessages )
		);
	}

	public static void assertCorrectConstraintTypes(Set> violations, Class... expectedConstraintTypes) {
		List actualConstraintTypeNames = new ArrayList();
		for ( ConstraintViolation violation : violations ) {
			actualConstraintTypeNames.add(
					violation.getConstraintDescriptor()
							.getAnnotation()
							.annotationType()
							.getName()
			);
		}

		List expectedConstraintTypeNames = new ArrayList();
		for ( Class expectedConstraintType : expectedConstraintTypes ) {
			expectedConstraintTypeNames.add( expectedConstraintType.getName() );
		}

		Collections.sort( actualConstraintTypeNames );
		Collections.sort( expectedConstraintTypeNames );

		assertEquals(
				actualConstraintTypeNames,
				expectedConstraintTypeNames,
				String.format( "Expected %s, but got %s", expectedConstraintTypeNames, actualConstraintTypeNames )
		);
	}

	public static  void assertCorrectPropertyPaths(Set> violations, String... propertyPaths) {
		List propertyPathsOfViolations = new ArrayList();
		for ( ConstraintViolation violation : violations ) {
			propertyPathsOfViolations.add( violation.getPropertyPath() );
		}

		assertEquals(
				propertyPathsOfViolations.size(),
				propertyPaths.length,
				"Wrong number of property paths."
		);

		for ( String propertyPath : propertyPaths ) {
			Path expectedPath = PathImpl.createPathFromString( propertyPath );
			boolean containsPath = false;
			for ( Path actualPath : propertyPathsOfViolations ) {
				if ( assertEqualPaths( expectedPath, actualPath ) ) {
					containsPath = true;
					break;
				}
			}
			if ( !containsPath ) {
				fail( expectedPath + " is not in the list of path instances contained in the actual constraint violations: " + propertyPathsOfViolations );
			}
		}
	}

	public static void assertCorrectPathNodeKinds(Set> violations, PathNodeKinds... kinds) {
		List actualDescriptorKinds = getPathDescriptorKinds( violations );
		List expectedDescriptorKinds = Arrays.asList( kinds );

		Collections.sort( actualDescriptorKinds );
		Collections.sort( expectedDescriptorKinds );

		assertEquals( actualDescriptorKinds, expectedDescriptorKinds );
	}

	public static void assertCorrectPathNodeNames(Set> violations, PathNodeNames... names) {
		List actualNodeNames = getPathNodeNames( violations );
		List expectedNodeNames = Arrays.asList( names );

		Collections.sort( actualNodeNames );
		Collections.sort( expectedNodeNames );

		assertEquals(
				actualNodeNames,
				expectedNodeNames,
				String.format( "Expected path node names %s, but found %s.", expectedNodeNames, actualNodeNames )
		);
	}

	public static  void assertConstraintViolation(ConstraintViolation violation, Class rootBean, Object invalidValue, String propertyPath) {
		Path expectedPath = PathImpl.createPathFromString( propertyPath );
		if ( !assertEqualPaths( violation.getPropertyPath(), expectedPath ) ) {
			fail( "Property paths differ. Actual: " + violation.getPropertyPath() + " Expected: " + expectedPath );
		}

		assertEquals(
				violation.getRootBeanClass(),
				rootBean,
				"Wrong root bean."
		);
		assertEquals(
				violation.getInvalidValue(),
				invalidValue,
				"Wrong invalid value."
		);
	}

	public static void assertDescriptorKinds(Path path, ElementKind... kinds) {
		Iterator pathIterator = path.iterator();

		for ( ElementKind kind : kinds ) {
			assertTrue( pathIterator.hasNext() );
			assertEquals( pathIterator.next().getKind(), kind );
		}

		assertFalse( pathIterator.hasNext() );
	}

	public static void assertNodeNames(Path path, String... names) {
		Iterator pathIterator = path.iterator();

		for ( String name : names ) {
			assertTrue( pathIterator.hasNext() );
			assertEquals( pathIterator.next().getName(), name );
		}

		assertFalse( pathIterator.hasNext() );
	}

	public static boolean assertEqualPaths(Path p1, Path p2) {
		Iterator p1Iterator = p1.iterator();
		Iterator p2Iterator = p2.iterator();
		while ( p1Iterator.hasNext() ) {
			Path.Node p1Node = p1Iterator.next();
			if ( !p2Iterator.hasNext() ) {
				return false;
			}
			Path.Node p2Node = p2Iterator.next();

			// do the comparison on the node values
			if ( p2Node.getName() == null ) {
				if ( p1Node.getName() != null ) {
					return false;
				}
			}
			else if ( !p2Node.getName().equals( p1Node.getName() ) ) {
				return false;
			}

			if ( p2Node.isInIterable() != p1Node.isInIterable() ) {
				return false;
			}

			if ( p2Node.getIndex() == null ) {
				if ( p1Node.getIndex() != null ) {
					return false;
				}
			}
			else if ( !p2Node.getIndex().equals( p1Node.getIndex() ) ) {
				return false;
			}

			if ( p2Node.getKey() == null ) {
				if ( p1Node.getKey() != null ) {
					return false;
				}
			}
			else if ( !p2Node.getKey().equals( p1Node.getKey() ) ) {
				return false;
			}
		}

		return !p2Iterator.hasNext();
	}

	/**
	 * Retrieves the parameter names from the given set of constraint
	 * violations, which must represent method or constructor constraint
	 * violations.
	 *
	 * @param constraintViolations The violations to retrieve the names from.
	 *
	 * @return The parameter names.
	 */
	public static Set getParameterNames(Set> constraintViolations) {
		Set parameterNames = new HashSet();

		for ( ConstraintViolation constraintViolation : constraintViolations ) {
			parameterNames.add( getParameterName( constraintViolation.getPropertyPath() ) );
		}

		return parameterNames;
	}

	public static  ConstraintViolation getConstraintViolationForParameter(Set> constraintViolations, String parameterName) {
		for ( ConstraintViolation constraintViolation : constraintViolations ) {
			if ( parameterName.equals( getParameterName( constraintViolation.getPropertyPath() ) ) ) {
				return constraintViolation;
			}
		}

		throw new IllegalArgumentException( "Found no constraint violation for parameter " + parameterName );
	}

	public static  ConstraintViolation getConstraintViolationForConstraintType(Set> constraintViolations,
																					 Class constraint) {
		for ( ConstraintViolation constraintViolation : constraintViolations ) {
			if ( constraintViolation.getConstraintDescriptor().getAnnotation().annotationType().equals( constraint ) ) {
				return constraintViolation;
			}
		}

		throw new IllegalArgumentException( "Found no constraint violation for constraint " + constraint.getSimpleName() );
	}

	public static String getParameterName(Path path) {
		Iterator nodes = path.iterator();

		assertTrue( nodes.hasNext() );
		nodes.next();

		assertTrue( nodes.hasNext() );
		return nodes.next().getName();
	}

	public static  Set asSet(T... ts) {
		return new HashSet( Arrays.asList( ts ) );
	}

	public static PathNodeKinds kinds(ElementKind... kinds) {
		return new PathNodeKinds( kinds );
	}

	public static PathNodeNames names(String... names) {
		return new PathNodeNames( names );
	}

	public static PropertyDescriptor getPropertyDescriptor(Class clazz, String property) {
		Validator validator = getValidatorUnderTest();
		return validator.getConstraintsForClass( clazz ).getConstraintsForProperty( property );
	}

	public static MethodDescriptor getMethodDescriptor(Class clazz, String name, Class... parameterTypes) {
		Validator validator = getValidatorUnderTest();
		return validator.getConstraintsForClass( clazz )
				.getConstraintsForMethod( name, parameterTypes );
	}

	public static ConstructorDescriptor getConstructorDescriptor(Class clazz, Class... parameterTypes) {
		Validator validator = getValidatorUnderTest();
		return validator.getConstraintsForClass( clazz ).getConstraintsForConstructor(
				parameterTypes
		);
	}

	public static Set> getConstraintDescriptorsFor(Class clazz, String property) {
		ElementDescriptor elementDescriptor = getPropertyDescriptor( clazz, property );
		return elementDescriptor.getConstraintDescriptors();
	}

	public static InputStream getInputStreamForPath(String path) {
		// try the context class loader first
		InputStream inputStream = Thread.currentThread()
				.getContextClassLoader()
				.getResourceAsStream( path );

		// try the current class loader
		if ( inputStream == null ) {
			inputStream = TestUtil.class.getResourceAsStream( path );
		}
		return inputStream;
	}

	private static > void instantiateValidationProviderUnderTest() {
		String validatorProviderClassName = System.getProperty( VALIDATION_PROVIDER_TEST_CLASS );
		if ( validatorProviderClassName == null ) {
			throw new RuntimeException(
					"The test harness must specify the class name of the validation provider under test. Set system property '" + VALIDATION_PROVIDER_TEST_CLASS + "'"
			);
		}

		Class providerClass;
		try {
			@SuppressWarnings("unchecked")
			Class tmpClazz = (Class) TestUtil.class.getClassLoader()
					.loadClass( validatorProviderClassName );
			providerClass = tmpClazz;
		}
		catch ( ClassNotFoundException e ) {
			throw new RuntimeException( "Unable to load " + validatorProviderClassName );
		}

		try {
			validationProviderUnderTest = providerClass.newInstance();
		}
		catch ( Exception e ) {
			throw new RuntimeException( "Unable to instantiate " + validatorProviderClassName );
		}
	}

	private static List getPathDescriptorKinds(Set> violations) {
		List descriptorKindsOfAllPaths = new ArrayList();

		for ( ConstraintViolation violation : violations ) {
			descriptorKindsOfAllPaths.add( new PathNodeKinds( violation.getPropertyPath() ) );
		}

		return descriptorKindsOfAllPaths;
	}

	private static List getPathNodeNames(Set> violations) {
		List nodeNamesOfAllPaths = new ArrayList();

		for ( ConstraintViolation violation : violations ) {
			nodeNamesOfAllPaths.add( new PathNodeNames( violation.getPropertyPath() ) );
		}

		return nodeNamesOfAllPaths;
	}

	public static class PathImpl implements Path {
		/**
		 * Regular expression used to split a string path into its elements.
		 *
		 * @see Regular expression tester
		 */
		private static final Pattern pathPattern = Pattern.compile(
				"(\\w+)(\\[(\\w*)\\])?(\\.(.*))*"
		);
		private static final int PROPERTY_NAME_GROUP = 1;
		private static final int INDEXED_GROUP = 2;
		private static final int INDEX_GROUP = 3;
		private static final int REMAINING_STRING_GROUP = 5;

		private static final String PROPERTY_PATH_SEPARATOR = ".";
		private static final String INDEX_OPEN = "[";
		private static final String INDEX_CLOSE = "]";

		private final List nodeList;

		/**
		 * Returns a {@code Path} instance representing the path described by the given string. To create a root node the empty string should be passed.
		 *
		 * @param propertyPath the path as string representation.
		 *
		 * @return a {@code Path} instance representing the path described by the given string.
		 *
		 * @throws IllegalArgumentException in case {@code property == null} or {@code property} cannot be parsed.
		 */
		public static PathImpl createPathFromString(String propertyPath) {
			if ( propertyPath == null ) {
				throw new IllegalArgumentException( "null is not allowed as property path." );
			}

			if ( propertyPath.length() == 0 ) {
				return createNewPath( null );
			}

			return parseProperty( propertyPath );
		}

		public static PathImpl createNewPath(String name) {
			PathImpl path = new PathImpl();
			NodeImpl node = new NodeImpl( name );
			path.addNode( node );
			return path;
		}

		private PathImpl() {
			nodeList = new ArrayList();
		}

		public void addNode(Node node) {
			nodeList.add( node );
		}

		@Override
		public Iterator iterator() {
			return nodeList.iterator();
		}

		@Override
		public String toString() {
			StringBuilder builder = new StringBuilder();
			Iterator iter = iterator();
			boolean first = true;
			while ( iter.hasNext() ) {
				Node node = iter.next();
				if ( node.isInIterable() ) {
					appendIndex( builder, node );
				}
				if ( node.getName() != null ) {
					if ( !first ) {
						builder.append( PROPERTY_PATH_SEPARATOR );
					}
					builder.append( node.getName() );
				}
				first = false;
			}
			return builder.toString();
		}

		private void appendIndex(StringBuilder builder, Node node) {
			builder.append( INDEX_OPEN );
			if ( node.getIndex() != null ) {
				builder.append( node.getIndex() );
			}
			else if ( node.getKey() != null ) {
				builder.append( node.getKey() );
			}
			builder.append( INDEX_CLOSE );
		}

		private static PathImpl parseProperty(String property) {
			PathImpl path = new PathImpl();
			String tmp = property;
			boolean indexed = false;
			String indexOrKey = null;
			do {
				Matcher matcher = pathPattern.matcher( tmp );
				if ( matcher.matches() ) {
					String value = matcher.group( PROPERTY_NAME_GROUP );

					NodeImpl node = new NodeImpl( value );
					path.addNode( node );

					// need to look backwards!!
					if ( indexed ) {
						updateNodeIndexOrKey( indexOrKey, node );
					}

					indexed = matcher.group( INDEXED_GROUP ) != null;
					indexOrKey = matcher.group( INDEX_GROUP );

					tmp = matcher.group( REMAINING_STRING_GROUP );
				}
				else {
					throw new IllegalArgumentException( "Unable to parse property path " + property );
				}
			} while ( tmp != null );

			// check for a left over indexed node
			if ( indexed ) {
				NodeImpl node = new NodeImpl( (String) null );
				updateNodeIndexOrKey( indexOrKey, node );
				path.addNode( node );
			}
			return path;
		}

		private static void updateNodeIndexOrKey(String indexOrKey, NodeImpl node) {
			node.setInIterable( true );
			if ( indexOrKey != null && indexOrKey.length() > 0 ) {
				try {
					Integer i = Integer.parseInt( indexOrKey );
					node.setIndex( i );
				}
				catch ( NumberFormatException e ) {
					node.setKey( indexOrKey );
				}
			}
		}
	}

	public static class NodeImpl implements Path.Node {
		private final String name;
		private boolean isInIterable;
		private Integer index;
		private Object key;

		public NodeImpl(String name) {
			this.name = name;
		}

		@Override
		public String getName() {
			return name;
		}

		@Override
		public boolean isInIterable() {
			return isInIterable;
		}

		public void setInIterable(boolean inIterable) {
			isInIterable = inIterable;
		}

		@Override
		public Integer getIndex() {
			return index;
		}

		public void setIndex(Integer index) {
			isInIterable = true;
			this.index = index;
		}

		@Override
		public Object getKey() {
			return key;
		}

		@Override
		public ElementKind getKind() {
			throw new UnsupportedOperationException();
		}

		@Override
		public  T as(Class nodeType) {
			throw new UnsupportedOperationException();
		}

		public void setKey(Object key) {
			isInIterable = true;
			this.key = key;
		}

		@Override
		public String toString() {
			final StringBuilder sb = new StringBuilder();
			sb.append( "NodeImpl" );
			sb.append( "{index=" ).append( index );
			sb.append( ", name='" ).append( name ).append( '\'' );
			sb.append( ", isInIterable=" ).append( isInIterable );
			sb.append( ", key=" ).append( key );
			sb.append( '}' );
			return sb.toString();
		}
	}
}