I want to proposal a new annotation for using Hibernate Validation on service layer.
Similar to @NonNull but use javax.validation API
public class Model {
@NotNull
private String text;
@Max(10)
private Integer number;
//
}
public void process(@BeanValidation Model model) {
//
}
public void process(@BeanValidation Model model) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Model>> violations = validator.validate(model);
if (!violations.isEmpty()) {
throw new ConstraintViolationException("......");
}
}
To be honest this is a great idea! Aaaand it seems like lombok lacks of new impressive ideas last years.
So, this one is really great I would say as Lombok anyways does a lot of non-usual stuff and adds many custom code to class-files - this one is really great (Can't wait for @SuperBuilder if it happens anytime soon @rzwitserloot)
Now people have to use AspectJ or more modern ByteBuddy agent to enable executable validation for example.
BUT
It should be something like
@BeanValidation(uses = ValidatorFactoryProvider.class)
Where it could be one and only one method returning ValidatorFactory.class or smth like that..
Because building ValidatorFactory should occur once. It is expensive. You should never build ValidatorFactory for each, even method / constructor validation.
@andrelugomes I'm lost. Validation is surely a nice thing, but what code should Lombok generate? Your process method takes Model as an argument, so I guess, it doesn't belong to Model at all. The Validator probably uses reflection to access the fields, so again, no Lombok needed.
AFAIK the only thing Lombok could meaningfully do is calling a validating method from the generated constructors and/or setters. Or simply just call a method.
Lombok itself won't probably ever do any advanced validation itself as this is just too complicated at this level. Letting it call something from the generated code is a long wished feature. AFAIK the last feedback we got on it is this comment.
@Maaartinus
but what code should Lombok generate?
This code
@BeanValidation(uses = ValidatorFactoryProvider.class)
/*public*/ class Stub {
/*@Tolerate*/
@ParametersScriptAssert(script = "arg0.before(arg1)", lang = "javascript")
@SomeCustomCrossParameterAnnotation("...")
/*public*/ Stub(@NotNull Localdate d1, @NotNull LocalDate) {
this.d1 = d1;
this.d2 = d2;
}
...
/*@Tolerate*/
@ParametersScriptAssert(script = "arg0 > arg1)", lang = "javascript")
@SomeAnotherCustomCrossParameterAnnotation("...")
/*public*/ Result method(@NotBlank String s1, @NotBlank String s2) {
// do stuff
return result;
}
}
or to just validate particular methods / constructors
@RequiredArgsConstructor(onConstructor_ = {@BeanValidation(uses = ValidatorFactoryProvider.class)}
class Stub { /*fields etc*/ }
generates
/*public*/ class Stub {
@ParametersScriptAssert(script = "arg0.before(arg1)", lang = "javascript")
@SomeCustomCrossParameterAnnotation("...")
/*public*/ Stub(@NotNull Localdate d1, @NotNull LocalDate) {
final Constructor<?> constructor =
Stub.class.getDeclaredConstructor(LocalDate.class, LocalDate.class);
final Object[] parameters = new Object[]{ d1, d2 };
final Set<ConstraintViolation<Object>> constraintViolations =
// see my comment above
ValidatorFactoryProvider.getExecutableValidator()
.validateConstructorParameters(constructor, parameters);
if (!constraintViolations.isEmpty()) {
throw new ConstraintViolationException(constraintViolations);
}
this.d1 = d1;
this.d2 = d2;
}
....
@ParametersScriptAssert(script = "arg0 > arg1)", lang = "javascript")
@SomeAnotherCustomCrossParameterAnnotation("...")
/*public*/ Result method(@NotBlank String s1, @NotBlank String s2) {
final Method method = ... //getExec method,
final Object[] parameters = new Object[]{ s1, s2 };
final Set<ConstraintViolation<Object>> constraintViolations =
ValidatorFactoryProvider.getExecutableValidator()
.validateParameters(this, method, parameters);
if (!constraintViolations.isEmpty()) {
throw new ConstraintViolationException(constraintViolations);
}
// do stuff
return result;
}
}
@rzwitserloot Is this realistic?
@soberich, I will get your code and give it some tweaks.
First, let't start with an approach that, although attractive, won't work:
package lombok;
import java.annotation.Retention;
import java.annotation.RetentionPolicy;
import java.annotation.Target;
import java.annotation.ElementType;
import javax.validation.executable.ExecutableValidator;
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface BeanValidation {
public Class<? extends BeanValidatorProvider> getExecutableValidator();
public static interface BeanValidatorProvider {
public ExecutableValidator getValidator();
}
}
This don't work and there is no easy way to fix it due to several reasons:
Lombok has a policy to not add any runtime dependencies to its users, and that is exactly what that BeanValidatorProvider is. So, we will need some approach that do not depends on a custom interface.
Trying to work around that by using java.util.function.Supplier or a method reference is another no-no because lombok should still be runnable with (nowadays ancient) Java 6. Switching to Java 7 will not really improve the situation here, at least Java 8 would be needed.
Using Class<?> will allow the class type be anything, including non-sense things in this context like String.class, int.class and void.class.
Using Class<? extends javax.validation.executable.ExecutableValidator> means that the user would be responsible for creating a class that implements ExecutableValidator. It would need to be instantiable with the default parameterless constructor, should be very light (likely an implementation of the flyweight pattern) and also encapsulate all the complicated validation logic (likely to be a implementation of the decorator pattern). On other words, it would be a heavy burden for the user.
Using Class<? extends javax.validation.Validator> or Class<? extends javax.validation.ValidatorFactory> would only make the burden for the user still heavier.
Using the name of a static parameterless method as a String works, but is a brittle approach. A refactoring operation that renames the method would forget the annotation untouched. Changing the method signature would also cause bad things. At least, since lombok is a compile-time tool, if the given method name is bad, you'll still get a compiler error instead of having it compiled and then getting a runtime error.
That is quite unfortunate, if we could either add runtime dependencies or use at least Java 8 for sure, we would be able to work out this easily.
But we need to choose some approach anyway, even if it is not as good as we would it like to be. So I'll choose for the method name as a String. With that, the annotation would be:
package lombok;
import java.annotation.Retention;
import java.annotation.RetentionPolicy;
import java.annotation.Target;
import java.annotation.ElementType;
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface BeanValidation {
public String methodName();
}
That value is the name of a parameterless static method that should be present to the declaring class and should return a javax.validation.executable.ExecutableValidator. Ugh, I hate to simulate method references as strings, but didn't found a way to work around that without having some other gotcha.
We would also have this:
package lombok;
import java.annotation.Retention;
import java.annotation.RetentionPolicy;
import java.annotation.Target;
import java.annotation.ElementType;
// This is implied by @Tolerate.
// But differently than @Tolerate this makes the method invisible
// only to @BeanValidation instead of lombok as a whole.
@Retention(value = RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface DisableBeanValidation {
}
So, let's go to the user code:
package com.example;
import acme.somepackage.CustomFancyValidation;
import acme.somepackage.CustomMamboJambo;
import acme.somepackage.Result;
import acme.somepackage.SomeAnotherCustomCrossParameterAnnotation;
import java.time.LocalDate;
import javax.validation.executable.ExecutableValidator;
import lombok.BeanValidation;
import lombok.DisableBeanValidation;
import lombok.NonNull;
import org.hibernate.validator.constraints.ParametersScriptAssert;
import org.hibernate.validator.constraints.NotBlank;
@BeanValidation(methodName = "myCustomMethod")
/*public*/ class MyCustomClass {
@NonNull
private final LocalDate d1;
@NonNull
private final LocalDate d2;
@ParametersScriptAssert(script = "arg0.before(arg1)", lang = "javascript")
@SomeCustomCrossParameterAnnotation("...")
/*public*/ MyCustomClass(@NotNull LocalDate d1, @NotNull LocalDate d2) {
this.d1 = d1;
this.d2 = d2;
}
@ParametersScriptAssert(script = "arg0 > arg1)", lang = "javascript")
@SomeAnotherCustomCrossParameterAnnotation("...")
/*public*/ Result method(@NotBlank String s1, @NotBlank String s2) {
// do stuff
return result;
}
@BeanValidation(methodName = "myOtherCustomMethod")
@CustomFancyValidation("...")
/*public*/ Result otherMethod(@CustomMamboJambo String s1) {
// do stuff
return result;
}
@DisableBeanValidation
/*public*/ Result defaultValidationMethod(@NonNull String s1) {
// do stuff
return result;
}
private static ExecutableValidator myCustomMethod() {
// do some sort of dance to get an ExecutableValidator.
return validator;
}
private static ExecutableValidator myOtherCustomMethod() {
// do some other sort of dance to get an ExecutableValidator.
return validator;
}
}
Could be delombokized onto this:
package com.example;
import acme.somepackage.CustomFancyValidation;
import acme.somepackage.CustomMamboJambo;
import acme.somepackage.Result;
import acme.somepackage.SomeAnotherCustomCrossParameterAnnotation;
import java.time.LocalDate;
import javax.validation.executable.ExecutableValidator;
import lombok.NonNull;
import org.hibernate.validator.constraints.ParametersScriptAssert;
import org.hibernate.validator.constraints.NotBlank;
/*public*/ class MyCustomClass {
private static abstract class Lombok$ValidationHelper {
public void validateConstructor(javax.validation.executable.ExecutableValidator validator, java.lang.Object... params) {
check(validator.validateConstructorParameters(getClass().getEnclosingConstructor(), params));
}
public void validateMethod(javax.validation.executable.ExecutableValidator validator, java.lang.Object... params) {
check(validator.validateParameters(getClass().getEnclosingMethod(), params));
}
private void check(java.util.Set<javax.validation.ConstraintViolation<?>> set) {
if (!set.isEmpty()) throw new javax.validation.ConstraintViolationException(set);
}
}
@ParametersScriptAssert(script = "arg0.before(arg1)", lang = "javascript")
@SomeCustomCrossParameterAnnotation("...")
/*public*/ MyCustomClass(@NotNull LocalDate d1, @NotNull LocalDate d2) {
(new Lombok$ValidationHelper() {}).validateConstructor(myCustomMethod(), d1, d2);
this.d1 = d1;
this.d2 = d2;
}
@ParametersScriptAssert(script = "arg0 > arg1)", lang = "javascript")
@SomeAnotherCustomCrossParameterAnnotation("...")
/*public*/ Result method(@NotBlank String s1, @NotBlank String s2) {
(new Lombok$ValidationHelper() {}).validateMethod(myCustomMethod(), s1, s2);
// do stuff
return result;
}
@CustomFancyValidation("...")
/*public*/ Result otherMethod(@CustomMamboJambo String s1) {
(new Lombok$ValidationHelper() {}).validateMethod(myOtherCustomMethod(), s1);
// do stuff
return result;
}
/*public*/ Result defaultValidationMethod(@NonNull String s1) {
if (s1 == null) throw new NullPointerException("s1 is marked @NonNull but is null");
// i.e. this uses the traditional simple lombok @NonNull validation.
// do stuff
return result;
}
private static ExecutableValidator myCustomMethod() {
// do some sort of dance to get an ExecutableValidator.
return validator;
}
private static ExecutableValidator myOtherCustomMethod() {
// do some other sort of dance to get an ExecutableValidator.
return validator;
}
}
The first downside is that the @BeanValidation annotation uses a string as the method name instead something more refactoring-proof.
The second downside is that there is no default method for getting an ExecutableValidator. The user has to write it all. Lombok could generate one like this:
private static javax.validation.executable.ExecutableValidator lombok$defaultValidator() {
return javax.validation.Validation.buildDefaultValidatorFactory().getValidator().forExecutables();
}
But this would not be a good idea because it is a very heavy object to be produced that way for each method call requiring validation. Caching it won't work either because lombok can't tell clearly how, when or where to cache it. So, I see no way other than forcing the user to write a method for getting that somehow.
The third downside is that we would have a Lombok$ValidationHelper class for each top-level class that uses bean validation. This is somewhat wasteful because they all have the very same code that works independently of the enclosing class, resulting in a bunch of compiled classes that all does the exact same thing (plus all the anonymous subclasses). This would be solved if lombok could provide it as a runtime dependency, but it can't.
The first downside is that the
@BeanValidationannotation uses a string as the method name instead something more refactoring-proof.
This could be solved by placing the annotation on the method instead:
@BeanValidationSupplier
private static ExecutableValidator myCustomMethod() {
// do some sort of dance to get an ExecutableValidator.
return validator;
}
This wouldn't allow things like myOtherCustomMethod, but do we need it?
But this would not be a good idea because it is a very heavy object to be produced that way for each method call requiring validation.
Right, this is the user's responsibility and decision. I don't know much about how it exactly works and why it's heavyweight, what parts are thread-safe and so on. But someone surely does and could provide a good example...
The third downside is that we would have a
Lombok$ValidationHelper
There must be a better solution. Yours would generate an anonymous class for every method, which would add quite some memory and runtime overhead, probably sufficient to kill Android. Maybe something like
private static final Method[] lombok$methodArray = MyCustomClass.class.getDeclaredMethods();
private static final Constructor[] lombok$constructorArray = MyCustomClass.class.getDeclaredConstructors();
private void lombok$check(java.util.Set<javax.validation.ConstraintViolation<?>> set) {
if (!set.isEmpty()) throw new javax.validation.ConstraintViolationException(set);
}
@ParametersScriptAssert(script = "arg0 > arg1)", lang = "javascript")
@SomeAnotherCustomCrossParameterAnnotation("...")
Result method(@NotBlank String s1, @NotBlank String s2) {
lombok$check(myCustomMethod().validateParameters(
lombok$methodArray[###theMagicallyRightIndex###], s1, s2));
// do stuff
return result;
}
could work. The hard part would be the determination of ###theMagicallyRightIndex### as there's no guarantee of reflection returning methods in the right order. This would probably require a static initializer block possibly reordering the arrays so that they match the source code order.
I guess, the best solution would be another project providing all the necessary utilities and making validation easy to use. Lombok would then just generate the calls of helpers from the other project.
@Maaartinus Very clever about putting the annotation in the method and using ###theMagicallyRightIndex###. So, I will try again:
package lombok;
import java.annotation.Retention;
import java.annotation.RetentionPolicy;
import java.annotation.Target;
import java.annotation.ElementType;
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface BeanValidation {}
[Keep the @DisableBeanValidation from my previous message as is]
package lombok;
import java.annotation.Retention;
import java.annotation.RetentionPolicy;
import java.annotation.Target;
import java.annotation.ElementType;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface BeanValidationSupplier {}
The user class:
package com.example;
import acme.somepackage.CustomFancyValidation;
import acme.somepackage.CustomMamboJambo;
import acme.somepackage.Result;
import acme.somepackage.SomeAnotherCustomCrossParameterAnnotation;
import java.lang.reflect.Executable;
import java.time.LocalDate;
import javax.validation.executable.ExecutableValidator;
import lombok.BeanValidation;
import lombok.BeanValidationSupplier;
import lombok.DisableBeanValidation;
import lombok.NonNull;
import org.hibernate.validator.constraints.ParametersScriptAssert;
import org.hibernate.validator.constraints.NotBlank;
@BeanValidation
/*public*/ class MyCustomClass {
@NonNull
private final LocalDate d1;
@NonNull
private final LocalDate d2;
@ParametersScriptAssert(script = "arg0.before(arg1)", lang = "javascript")
@SomeCustomCrossParameterAnnotation("...")
/*public*/ MyCustomClass(@NotNull LocalDate d1, @NotNull LocalDate d2) {
this.d1 = d1;
this.d2 = d2;
}
@ParametersScriptAssert(script = "arg0 > arg1)", lang = "javascript")
@SomeAnotherCustomCrossParameterAnnotation("...")
/*public*/ Result method(@NotBlank String s1, @NotBlank String s2) {
// do stuff
return result;
}
@CustomFancyValidation("...")
/*public*/ Result otherMethod(@CustomMamboJambo String s1) {
// do stuff
return result;
}
@DisableBeanValidation
/*public*/ Result defaultValidationMethod(@NonNull String s1) {
// do stuff
return result;
}
@BeanValidationSupplier
private static ExecutableValidator myCustomMethod(Executable where) {
// do some sort of dance to get an ExecutableValidator.
return validator;
}
}
That myCustomMethod might either not have any parameter or have a parameter representing the method or constructor. This gives a chance to myCustomMethod to inspect the method or constructor in which it is applied to selectively choose or configure the returned ExecutableValidator. In the majority of the cases, where this isn't needed, that method would simply have no parameters. Java 7 and below do not have java.lang.reflect.Executable, but in this case, java.lang.reflect.Member, java.lang.reflect.AnnotatedElement or even java.lang.Object could be used instead, as long it is some type compatible both with Method and Constructor.
That would be delomboked into:
package com.example;
import acme.somepackage.CustomFancyValidation;
import acme.somepackage.CustomMamboJambo;
import acme.somepackage.Result;
import acme.somepackage.SomeAnotherCustomCrossParameterAnnotation;
import java.lang.reflect.Executable;
import java.time.LocalDate;
import javax.validation.executable.ExecutableValidator;
import lombok.NonNull;
import org.hibernate.validator.constraints.ParametersScriptAssert;
import org.hibernate.validator.constraints.NotBlank;
/*public*/ class MyCustomClass {
private static final java.lang.reflect.Method[] lombok$methodArray = new java.lang.reflect.Method[2];
private static final java.lang.reflect.Constructor<?>[] lombok$constructorArray = new java.lang.reflect.Constructor<?>[1];
static {
try {
lombok$methodArray[0] = MyCustomClass.class.getDeclaredMethod("method", java.lang.String.class, java.lang.String.class);
lombok$methodArray[1] = MyCustomClass.class.getDeclaredMethod("otherMethod", java.lang.String.class);
lombok$constructorArray[0] = MyCustomClass.class.getDeclaredConstructor(java.time.LocalDate.class, java.time.LocalDate.class);
} catch (java.lang.Exception e) {
throw new java.lang.ExceptionInInitializerError(e);
}
}
@NonNull
private final LocalDate d1;
@NonNull
private final LocalDate d2;
@ParametersScriptAssert(script = "arg0.before(arg1)", lang = "javascript")
@SomeCustomCrossParameterAnnotation("...")
/*public*/ MyCustomClass(@NotNull LocalDate d1, @NotNull LocalDate d2) {
java.util.Set<javax.validation.ConstraintViolation<?>> $temp0 = myCustomMethod(lombok$constructorArray[0]).validateConstructorParameters(lombok$constructorArray[0], new Object[] {d1, d2});
if (!$temp0.isEmpty()) throw new javax.validation.ConstraintViolationException($temp0);
this.d1 = d1;
this.d2 = d2;
}
@ParametersScriptAssert(script = "arg0 > arg1)", lang = "javascript")
@SomeAnotherCustomCrossParameterAnnotation("...")
/*public*/ Result method(@NotBlank String s1, @NotBlank String s2) {
java.util.Set<javax.validation.ConstraintViolation<?>> $temp0 = myCustomMethod(lombok$methodArray[0]).validateParameters(lombok$methodArray[0], new Object[] {s1, s2});
if (!$temp0.isEmpty()) throw new javax.validation.ConstraintViolationException($temp0);
// do stuff
return result;
}
@CustomFancyValidation("...")
/*public*/ Result otherMethod(@CustomMamboJambo String s1) {
java.util.Set<javax.validation.ConstraintViolation<?>> $temp0 = myCustomMethod(lombok$methodArray[1]).validateParameters(lombok$methodArray[1], new Object[] {s1});
if (!$temp0.isEmpty()) throw new javax.validation.ConstraintViolationException($temp0);
// do stuff
return result;
}
/*public*/ Result defaultValidationMethod(@NonNull String s1) {
if (s1 == null) throw new NullPointerException("s1 is marked @NonNull but is null");
// i.e. this uses the traditional simple lombok @NonNull validation.
// do stuff
return result;
}
private static ExecutableValidator myCustomMethod(Executable where) {
// do some sort of dance to get an ExecutableValidator.
return validator;
}
}
I guess, the best solution would be another project providing all the necessary utilities and making validation easy to use. Lombok would then just generate the calls of helpers from the other project.
Not sure yet what to think about that.
I think this whole thing already exists in another project. I mean, as far
as I am aware implementing validations nowadays is a matter of annotating
your fields properly and then supplying spring with a validator that can
handle the annotations. Autowiring does the rest.
So I don't quite understand what this functionality is supposed to achieve.
Maybe that's just me, but this thing already exists as far as I know.
Without requiring any help from lombok.
On Thu, Jun 7, 2018 at 6:09 PM, Victor Williams Stafusa da Silva <
[email protected]> wrote:
@Maaartinus https://github.com/Maaartinus Very clever about putting the
annotation in the method and using ###theMagicallyRightIndex###. So, I
will try again:package lombok;
import java.annotation.Retention;import java.annotation.RetentionPolicy;import java.annotation.Target;import java.annotation.ElementType;
@Retention(RetentionPolicy.SOURCE)@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})public @interface BeanValidation {}[Keep the @DisableBeanValidation from my previous message as is]
package lombok;
import java.annotation.Retention;import java.annotation.RetentionPolicy;import java.annotation.Target;import java.annotation.ElementType;
@Retention(RetentionPolicy.SOURCE)@Target(ElementType.METHOD)public @interface BeanValidationSupplier {}The user class:
package com.example;
import acme.somepackage.CustomFancyValidation;import acme.somepackage.CustomMamboJambo;import acme.somepackage.Result;import acme.somepackage.SomeAnotherCustomCrossParameterAnnotation;import java.lang.reflect.Executable;import java.time.LocalDate;import javax.validation.executable.ExecutableValidator;import lombok.BeanValidation;import lombok.BeanValidationSupplier;import lombok.DisableBeanValidation;import lombok.NonNull;import org.hibernate.validator.constraints.ParametersScriptAssert;import org.hibernate.validator.constraints.NotBlank;
@BeanValidation/public/ class MyCustomClass {@NonNull private final LocalDate d1; @NonNull private final LocalDate d2; @ParametersScriptAssert(script = "arg0.before(arg1)", lang = "javascript") @SomeCustomCrossParameterAnnotation("...") /*public*/ MyCustomClass(@NotNull LocalDate d1, @NotNull LocalDate d2) { this.d1 = d1; this.d2 = d2; } @ParametersScriptAssert(script = "arg0 > arg1)", lang = "javascript") @SomeAnotherCustomCrossParameterAnnotation("...") /*public*/ Result method(@NotBlank String s1, @NotBlank String s2) { // do stuff return result; } @CustomFancyValidation("...") /*public*/ Result otherMethod(@CustomMamboJambo String s1) { // do stuff return result; } @DisableBeanValidation /*public*/ Result defaultValidationMethod(@NonNull String s1) { // do stuff return result; } @BeanValidationSupplier private static ExecutableValidator myCustomMethod(Executable where) { // do some sort of dance to get an ExecutableValidator. return validator; }}
That myCustomMethod might either not have any parameter or have a
parameter representing the method or constructor. This gives a chance to
myCustomMethod to inspect the method or constructor in which it is
applied to selectively choose or configure the returned
ExecutableValidator. In the majority of the cases, where this isn't
needed, that method would simply have no parameters. Java 7 and below do
not have java.lang.reflect.Executable, but in this case,
java.lang.reflect.Member, java.lang.reflect.AnnotatedElement or even
java.lang.Object could be used instead, as long it is some type
compatible both with Method and Constructor.That would be delomboked into:
package com.example;
import acme.somepackage.CustomFancyValidation;import acme.somepackage.CustomMamboJambo;import acme.somepackage.Result;import acme.somepackage.SomeAnotherCustomCrossParameterAnnotation;import java.lang.reflect.Executable;import java.time.LocalDate;import javax.validation.executable.ExecutableValidator;import lombok.NonNull;import org.hibernate.validator.constraints.ParametersScriptAssert;import org.hibernate.validator.constraints.NotBlank;
/public/ class MyCustomClass {private static final java.lang.reflect.Method[] lombok$methodArray = new java.lang.reflect.Method[2]; private static final java.lang.reflect.Constructor<?>[] lombok$constructorArray = new java.lang.reflect.Constructor<?>[2]; static { try { lombok$methodArray[0] = MyCustomClass.getDeclaredMethod("method", java.lang.String.class, java.lang.String.class); lombok$methodArray[1] = MyCustomClass.getDeclaredMethod("otherMethod", java.lang.String.class); lombok$constructorArray[0] = MyCustomClass.getDeclaredConstructor(java.time.LocalDate.class, java.time.LocalDate.class); } catch (java.lang.Exception e) { throw new java.lang.ExceptionInInitializerError(e); } } @NonNull private final LocalDate d1; @NonNull private final LocalDate d2; @ParametersScriptAssert(script = "arg0.before(arg1)", lang = "javascript") @SomeCustomCrossParameterAnnotation("...") /*public*/ MyCustomClass(@NotNull LocalDate d1, @NotNull LocalDate d2) { java.util.Set<javax.validation.ConstraintViolation<?>> $temp0 = myCustomMethod(lombok$constructorArray[0]).validateConstructorParameters(lombok$constructorArray[0], new Object[] {d1, d2}); if (!$temp0.isEmpty()) throw new javax.validation.ConstraintViolationException($temp0); this.d1 = d1; this.d2 = d2; } @ParametersScriptAssert(script = "arg0 > arg1)", lang = "javascript") @SomeAnotherCustomCrossParameterAnnotation("...") /*public*/ Result method(@NotBlank String s1, @NotBlank String s2) { java.util.Set<javax.validation.ConstraintViolation<?>> $temp0 = myCustomMethod(lombok$methodArray[0]).validateParameters(lombok$methodArray[0], new Object[] {s1, s2}); if (!$temp0.isEmpty()) throw new javax.validation.ConstraintViolationException($temp0); // do stuff return result; } @CustomFancyValidation("...") /*public*/ Result otherMethod(@CustomMamboJambo String s1) { java.util.Set<javax.validation.ConstraintViolation<?>> $temp0 = myCustomMethod(lombok$methodArray[1]).validateParameters(lombok$methodArray[1], new Object[] {s1}); if (!$temp0.isEmpty()) throw new javax.validation.ConstraintViolationException($temp0); // do stuff return result; } /*public*/ Result defaultValidationMethod(@NonNull String s1) { if (s1 == null) throw new NullPointerException("s1 is marked @NonNull but is null"); // i.e. this uses the traditional simple lombok @NonNull validation. // do stuff return result; } private static ExecutableValidator myCustomMethod(Executable where) { // do some sort of dance to get an ExecutableValidator. return validator; }}
I guess, the best solution would be another project providing all the
necessary utilities and making validation easy to use. Lombok would then
just generate the calls of helpers from the other project.Not sure yet what to think about that.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/rzwitserloot/lombok/issues/1701#issuecomment-395477319,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAKCRTtE7S8yrfS3V-pGifTOfzqwSk0nks5t6VAvgaJpZM4UO4V1
.
--
"Don't only practice your art, but force your way into it's secrets, for it
and knowledge can raise men to the divine."
-- Ludwig von Beethoven
@randakar
talk is about executable validation. Explore.
@victorwss maybe at least push to avoid static initializer block (if possible) at, again, at least "design time". I mean this makes class to be not usable for instrumentation (with some agent) which is quite limiting overall. But you better know about ASM and relevant stuff here.
Well, that's quite the helpful explanation there.
So, you want to put validation annotations on method arguments and have
those automatically run through a validator. Does this include
lombok-generated method arguments, or not? If it doesn't, what is lombok
supposed to do? Produce a factory for the validator itself and hook the
validator up in such a way that it is automatically applied properly? Why
can't this be be solved with spring, exactly?
Why is it common enough that you want lombok to do that?
On Fri, Jun 8, 2018 at 12:58 AM, soberich notifications@github.com wrote:
@randakar https://github.com/randakar
talk is about executable validation. Explore.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rzwitserloot/lombok/issues/1701#issuecomment-395591754,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAKCRRTa279psypCRCbyo68AJ0ql3Waiks5t6bASgaJpZM4UO4V1
.
--
"Don't only practice your art, but force your way into it's secrets, for it
and knowledge can raise men to the divine."
-- Ludwig von Beethoven
@soberich
I mean this makes class to be not usable for instrumentation (with some agent)
How exactly does this happen? The above initializer block could be trivially replaced by
private static final java.lang.reflect.Method[] lombok$methodArray = {
MyCustomClass.getDeclaredMethod("method", java.lang.String.class, java.lang.String.class),
MyCustomClass.getDeclaredMethod("otherMethod", java.lang.String.class),
};
private static final java.lang.reflect.Constructor<?>[] lombok$constructorArray = {
MyCustomClass.getDeclaredConstructor(java.time.LocalDate.class, java.time.LocalDate.class),
};
if there weren't those stupid checked exceptions (a helper method would help). Anyway, I can't imagine that it'd change anything w.r.t. the instrumentation.
@randakar
Note that parameter validation and bean validation are different things and we are so far only talking about parameter validation. IMO, bean validation might come in later, specially when combined with @Data and @Value, but let's take one step at a time. At least about bean validation, as you said, there is a lot of frameworks that do that.
The idea is to validate the parameters in the same way that lombok validates @NonNull. The programmer just puts the annotations on the parameters (or in the method or the class if needed) and voila, its done. No need to write the boilerplate to actually do that validation. Most parameter validation code is boilerplate and lombok's purpose is to eliminate boilerplate code, so it's a match.
See this page about spring. The code for parameter validation there is this:
ReservationManagement object = new ReservationManagement();
Method method = ReservationManagement.class
.getMethod("createReservation", LocalDate.class, int.class, Customer.class);
Object[] parameterValues = { LocalDate.now(), 0, null };
Set<ConstraintViolation<ReservationManagement>> violations
= executableValidator.validateParameters(object, method, parameterValues);
This is a boilerplate code that is extremely cumbersome and error-prone to be written manually over and over again. For this reason, it is recommended to use AOP or proxies to handle that transparently. Lombok would be a third approach that won't need neither AOP nor proxies for that stuff. Nor dependency injection. Nor relying on some container that magically manages beans. Nor demanding strange codification practices or imposing restrictions on the code like EJB 2 did. Further, it should be usable on android.
Parameter validation is a very frequent case of programmers having to manually write boilerplate code, be it with javax.validation or not, so this is something interesting for lombok. However, autogenerating that boilerplate code is something hard because there is too much variability in them. Anyway, the proposal should be able to solve this problem at least partially, generating the boilerplate code that invokes some validator, which would be responsible for actually handling validation. If it could also generate the validator itself, it would be a big win, but let's take one step at a time.
@soberich I didn't understand what is really the problem with the static block and instrumentation agents. Can you elaborate more on that?
Alright. Thanks for this explanation. It makes sense. Given those
constraints.
For the kind of projects I work on, it usually doesn't really apply though.
And that example.. For basic web endpoints at least, none of that is
necessy given spring boot. In that context it's a matter of some
annotations and writing your own validator for anything that isn't covered
by the javax.validation annotations.
But fine, if that approach does not work for other people, feel free.
My main fear here is that people will slowly turn lombok from a small
targeted single purpose library into soms massive monster people use only a
fraction of, in the name of killing boilerplate. So if it seems to be the
case that a specific piece is already covered by soms other (small,
ideally) library or framework, I prefer Lombok to not reinvent it.
Op za 9 jun. 2018 09:22 schreef Victor Williams Stafusa da Silva <
[email protected]>:
@randakar https://github.com/randakar
Note that parameter validation and bean validation are different things
and we are so far only talking about parameter validation. IMO, bean
validation might come in later, specially when combined with @Data and
@Value, but let's take one step at a time. At least about bean
validation, as you said, there is a lot of frameworks that do that.The idea is to validate the parameters in the same way that lombok
validates @NonNull. The programmer just puts the annotations on the
parameters (or in the method or the class if needed) and voila, its done.
No need to write the boilerplate to actually do that validation. Most
parameter validation code is boilerplate and lombok's purpose is to
eliminate boilerplate code, so it's a match.See this page about spring
http://www.baeldung.com/javax-validation-method-constraints. The code
for parameter validation there is this:ReservationManagement object = new ReservationManagement();Method method = ReservationManagement.class
.getMethod("createReservation", LocalDate.class, int.class, Customer.class);Object[] parameterValues = { LocalDate.now(), 0, null };Set> violations
= executableValidator.validateParameters(object, method, parameterValues);This is a boilerplate code that is extremely cumbersome and error-prone to
be written manually over and over again. For this reason, it is recommended
to use AOP or proxies to handle that transparently. Lombok would be a third
approach that won't need neither AOP nor proxies for that stuff. Nor
dependency injection. Nor relying on some container that magically manages
beans. Nor demanding strange codification practices or imposing
restrictions on the code like EJB 2 did. Further, it should be usable on
android.Parameter validation is a very frequent case of programmers having to
manually write boilerplate code, be it with javax.validation or not, so
this is something interesting for lombok. However, autogenerating that
boilerplate code is something hard because there is too much variability in
them. Anyway, the proposal should be able to solve this problem at least
partially, generating the boilerplate code that invokes some validator,
which would be responsible for actually handling validation. If it could
also generate the validator itself, it would be a big win, but let's take
one step at a time.@soberich https://github.com/soberich I didn't understand what is
really the problem with the static block and instrumentation agents. Can
you elaborate more on that?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rzwitserloot/lombok/issues/1701#issuecomment-395947304,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAKCRZ5MWodSGhHMldI8iwxyDFeSgnCaks5t63fOgaJpZM4UO4V1
.
@victorwss
If someone (like me) aims to transform loaded classes redefining them (which btw ALL monitoring tools / libraries do - that's the point) it will fail completely because such transformation HAVE TO keep class schema unchanged, but as already said this can be achieved only with redefinition of the class which CAN NOT happen if Lombok did put static block there, because this block needs to be copied into an extra method as @Maaartinus said. (see legendary JEP 159)
@soberich So, what about replacing this:
private static final java.lang.reflect.Method[] lombok$methodArray = new java.lang.reflect.Method[2];
private static final java.lang.reflect.Constructor<?>[] lombok$constructorArray = new java.lang.reflect.Constructor<?>[1];
static {
try {
lombok$methodArray[0] = MyCustomClass.class.getDeclaredMethod("method", java.lang.String.class, java.lang.String.class);
lombok$methodArray[1] = MyCustomClass.class.getDeclaredMethod("otherMethod", java.lang.String.class);
lombok$constructorArray[0] = MyCustomClass.class.getDeclaredConstructor(java.time.LocalDate.class, java.time.LocalDate.class);
} catch (java.lang.Exception e) {
throw new java.lang.ExceptionInInitializerError(e);
}
}
By this:
private static final java.lang.reflect.Method[] lombok$methodArray = lombok$getMethodArray();
private static final java.lang.reflect.Constructor<?>[] lombok$constructorArray = lombok$getConstructorArray();
private static java.lang.reflect.Method[] lombok$getMethodArray() {
try {
return new java.lang.reflect.Method[] {
MyCustomClass.class.getDeclaredMethod("method", java.lang.String.class, java.lang.String.class),
MyCustomClass.class.getDeclaredMethod("otherMethod", java.lang.String.class)
}
} catch (java.lang.Exception e) {
throw new java.lang.ExceptionInInitializerError(e);
}
}
private static java.lang.reflect.Constructor<?>[] lombok$getConstructorArray() {
try {
return new java.lang.reflect.Constructor<?>[] {
MyCustomClass.class.getDeclaredConstructor(java.time.LocalDate.class, java.time.LocalDate.class)
};
} catch (java.lang.Exception e) {
throw new java.lang.ExceptionInInitializerError(e);
}
}
Would it be ok now?
@victorwss yes. Only static initializer block have such an "honor".
@Maaartinus, the inicial idea were makes lombok generate static validation of Hibernate Validatior as @BeanValidation provider using:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
javax.validation.Validator validator = factory.getValidator();
Set<ConstraintViolation<T>> violations = validator.validate(model);
if ( violations.size() > 0) {
throw new ConstraintViolationException("......");
}
We use and mainteing a opensource projet that do the same thing, but using Aspect.