Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explore constructor/method validation using AspectJ or ByteBuddy #937

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions executable-validation/aspectj-validation/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Hibernate Validator, declare and validate application constraints
~
~ License: Apache License, Version 2.0
~ See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-executable-validation</artifactId>
<version>6.0.9-SNAPSHOT</version>
</parent>
<artifactId>hibernate-validator-aspectj-validation</artifactId>

<name>Hibernate Validator AspectJ Validation</name>

<properties>
<hibernate-validator-parent.path>../..</hibernate-validator-parent.path>
<automatic.module.name>org.hibernate.validator.aspectj.validation</automatic.module.name>
</properties>

<build>
<plugins>
<plugin>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
<plugin>
<groupId>de.thetaphi</groupId>
<artifactId>forbiddenapis</artifactId>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>${automatic.module.name}</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
<excludes>
<exclude>**/logging/*.*</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

<dependencies>
<!-- Compile time dependencies -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>

<!-- Provided dependencies -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-processor</artifactId>
<!--
HV-963
This is actually not a dependency which is needed at runtime, however,
Maven does not have a compile time only scope. The dependency is needed to
run the JBoss Logging annotation processor as part of the main compilation.
Trying different setups via compiler plugin local dependencies or extensions
all fail. See also http://stackoverflow.com/questions/14322904/maven-3-how-to-add-annotation-processor-dependency
-->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-annotations</artifactId>
<scope>provided</scope>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>hibernate-validator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>hibernate-validator-test-utils</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.aspectj.validation;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.groups.Default;

/**
* Marker annotation to filter the methods/constructors validation of
* which should be performed.
*
* @author Marko Bekhta
*/
@Target({ METHOD, /*TYPE,*/ CONSTRUCTOR })
@Retention(RUNTIME)
public @interface Validate {

/**
* @return an array of validation groups for which validation should be applied.
* Is empty by default, hence validation will run for {@link Default} group.
*/
Class<?>[] groups() default { };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.aspectj.validation.internal;

import java.lang.invoke.MethodHandles;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.List;
import java.util.ServiceLoader;

import javax.validation.Validation;
import javax.validation.ValidatorFactory;

import org.hibernate.validator.aspectj.validation.internal.util.logging.Log;
import org.hibernate.validator.aspectj.validation.internal.util.logging.LoggerFactory;
import org.hibernate.validator.aspectj.validation.internal.util.privilegedactions.GetValidatorProviderFactoryFromServiceLoader;
import org.hibernate.validator.aspectj.validation.spi.ValidatorFactoryProducer;

/**
* Uses {@link ServiceLoader} mechanism to look up user provided {@link ValidatorFactoryProducer}.
* If none are found will provide a default {@link ValidatorFactory}.
*
* @author Marko Bekhta
*/
class ServiceLoaderBasedValidatorFactoryProducer {

private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );

private final ValidatorFactory validatorFactory;

ServiceLoaderBasedValidatorFactoryProducer() {
List<ValidatorFactoryProducer> validatorFactoryProducers = run( GetValidatorProviderFactoryFromServiceLoader.action() );
if ( validatorFactoryProducers.isEmpty() ) {
validatorFactory = Validation.buildDefaultValidatorFactory();
LOG.defaultValidatorUsage();
}
else {
ValidatorFactoryProducer producer = validatorFactoryProducers.get( 0 );
try {
validatorFactory = producer.getConfiguredValidatorFactory();
if ( validatorFactoryProducers.size() > 1 ) {
LOG.multipleValidatorFactoryProducers( producer.getClass() );
}
}
catch (Exception e) {
throw LOG.errorCreatingValidatorFactoryFromProducer( producer.getClass(), e );
}
}
}

public ValidatorFactory getValidatorFactory() {
return validatorFactory;
}

/**
* Runs the given privileged action, using a privileged block if required.
* <p>
* <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary
* privileged actions within HV's protection domain.
*/
private static <T> T run(PrivilegedAction<T> action) {
return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.aspectj.validation.internal;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;

import org.hibernate.validator.aspectj.validation.Validate;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.ConstructorSignature;
import org.aspectj.lang.reflect.MethodSignature;

/**
* An aspect that contains all the pointcuts and validation advices for method/constructor
* validation. Uses {@link ServiceLoaderBasedValidatorFactoryProducer} to get an instance
* of {@link Validator}.
*
* @author Marko Bekhta
*/
@Aspect
public aspect ValidationAspect {

private final ExecutableValidator executableValidator;

{
// initialize an executable validator from a loaded validator factory:
ServiceLoaderBasedValidatorFactoryProducer producer = new ServiceLoaderBasedValidatorFactoryProducer();
executableValidator = producer.getValidatorFactory().getValidator().forExecutables();
}

/**
* Defines a pointcut that looks for {@code @Validate} annotated elements. Used to build up advices.
*
* @param validate a reference to the annotation
*/
pointcut annotationPointCutDefinition(Validate validate): @annotation(validate);

/**
* Defines a pointcut that looks for any method executions. Used to build up advices.
*/
pointcut atMethodExecution(): execution(* *(..));

/**
* Defines a pointcut that looks for any constructor executions. Used to build up advices.
* @param jp a joint point of constructor execution
*/
pointcut atConstructorExecution(): execution(*.new(..));

/**
* Defines an advice for validation of method parameters
*/
before(Validate validate): annotationPointCutDefinition(validate) && atMethodExecution() {
Set<ConstraintViolation<Object>> violations = executableValidator.validateParameters(
thisJoinPoint.getTarget(),
( (MethodSignature) thisJoinPoint.getSignature() ).getMethod(),
thisJoinPoint.getArgs(),
validate.groups()
);

// if there are any violations - throw a ConstraintViolationException
if ( !violations.isEmpty() ) {
throw new ConstraintViolationException( violations );
}
}

/**
* Defines an advice for validation of method return value
*/
after(Validate validate) returning(Object returnValue): annotationPointCutDefinition(validate) && atMethodExecution() {
Set<ConstraintViolation<Object>> violations = executableValidator.validateReturnValue(
thisJoinPoint.getTarget(),
( (MethodSignature) thisJoinPoint.getSignature() ).getMethod(),
returnValue,
validate.groups()
);

// if there are any violations - throw a ConstraintViolationException
if ( !violations.isEmpty() ) {
throw new ConstraintViolationException( violations );
}
}

/**
* Defines an advice for validation of constructor parameters
*/
before(Validate validate): annotationPointCutDefinition(validate) && atConstructorExecution() {
Set<ConstraintViolation<Object>> violations = executableValidator.validateConstructorParameters(
( (ConstructorSignature) thisJoinPoint.getSignature() ).getConstructor(),
thisJoinPoint.getArgs(),
validate.groups()
);

// if there are any violations - throw a ConstraintViolationException
if ( !violations.isEmpty() ) {
throw new ConstraintViolationException( violations );
}
}


/**
* Defines an advice for validation of method return value
*/
after(Validate validate): annotationPointCutDefinition(validate) && atConstructorExecution() {
Set<ConstraintViolation<Object>> violations = executableValidator.validateConstructorReturnValue(
( (ConstructorSignature) thisJoinPoint.getSignature() ).getConstructor(),
thisJoinPoint.getTarget(),
validate.groups()
);

// if there are any violations - throw a ConstraintViolationException
if ( !violations.isEmpty() ) {
throw new ConstraintViolationException( violations );
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
/**
* This package is part of the private Hibernate Validator API.
*/
package org.hibernate.validator.aspectj.validation.internal;
Loading