Created
August 16, 2021 02:11
-
-
Save kriegaex/7fb3ff5313fe089a503f24363849557e to your computer and use it in GitHub Desktop.
Related to https://stackoverflow.com/a/68788243/1082681
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.scrum_master.spring.q68785567; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.context.ConfigurableApplicationContext; | |
import org.springframework.context.annotation.Configuration; | |
import java.io.IOException; | |
import java.util.concurrent.ExecutionException; | |
@SpringBootApplication | |
@Configuration | |
public class DemoApplication { | |
private static final Logger log = LoggerFactory.getLogger(DemoApplication.class.getName()); | |
public static void main(String[] args) throws InterruptedException, IOException, ExecutionException { | |
try (ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args)) { | |
doStuff(appContext); | |
} | |
} | |
private static void doStuff(ConfigurableApplicationContext appContext) { | |
TestDummyController controller = appContext.getBean(TestDummyController.class); | |
TestDummyValueHolder.setValue("value: non-null"); | |
TestDummyValueHolder.setAnotherValue("another value: non-null"); | |
controller.direct(); | |
controller.directRepeat(); | |
controller.meta(); | |
controller.meta2(); | |
controller.mixed(); | |
controller.mixedRepeat(); | |
log.info("----------------------------------------------------------------------"); | |
TestDummyValueHolder.setValue(null); | |
TestDummyValueHolder.setAnotherValue(null); | |
try { controller.direct(); } catch (Exception e) { log.info("Caught exception: " + e); } | |
try { controller.directRepeat(); } catch (Exception e) { log.info("Caught exception: " + e); } | |
try { controller.meta(); } catch (Exception e) { log.info("Caught exception: " + e); } | |
try { controller.meta2(); } catch (Exception e) { log.info("Caught exception: " + e); } | |
try { controller.mixed(); } catch (Exception e) { log.info("Caught exception: " + e); } | |
try { controller.mixedRepeat(); } catch (Exception e) { log.info("Caught exception: " + e); } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.scrum_master.spring.q68785567; | |
import java.lang.annotation.Documented; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Repeatable; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(value = { ElementType.METHOD, ElementType.ANNOTATION_TYPE }) | |
@Repeatable(RequiresNonBlanks.class) | |
@Documented | |
public @interface RequiresNonBlank { | |
String value(); | |
Class<? extends StuffException> throwIfInvalid(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.scrum_master.spring.q68785567; | |
import org.aspectj.lang.JoinPoint; | |
import org.aspectj.lang.annotation.Aspect; | |
import org.aspectj.lang.annotation.Before; | |
import org.aspectj.lang.reflect.MethodSignature; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.expression.Expression; | |
import org.springframework.expression.ExpressionParser; | |
import org.springframework.expression.spel.standard.SpelExpressionParser; | |
import org.springframework.stereotype.Component; | |
import org.springframework.util.StringUtils; | |
import java.lang.annotation.Annotation; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.Arrays; | |
import java.util.HashSet; | |
import java.util.Objects; | |
import java.util.Set; | |
@Component | |
@Aspect | |
//@Slf4j | |
public class RequiresNonBlankAspect { | |
private static final Logger log = LoggerFactory.getLogger(RequiresNonBlankAspect.class.getName()); | |
private static final String REQUIRES_NON_BLANK_FQPN = | |
"de.scrum_master.spring.q68785567.RequiresNonBlank"; | |
private ExpressionParser elParser = new SpelExpressionParser(); | |
@Before("execution(@" + REQUIRES_NON_BLANK_FQPN + " * *(..)) && @annotation(requiresNonBlank)") | |
public void directAnnotation(JoinPoint joinPoint, RequiresNonBlank requiresNonBlank) { | |
log.info("[DIRECT] " + joinPoint + " -> " + requiresNonBlank); | |
evaluatePrecondition(joinPoint, requiresNonBlank); | |
} | |
@Before("execution(@" + REQUIRES_NON_BLANK_FQPN + "s * *(..)) && @annotation(requiresNonBlanks)") | |
public void directAnnotationRepeated(JoinPoint joinPoint, RequiresNonBlanks requiresNonBlanks) { | |
for (RequiresNonBlank requiresNonBlank : requiresNonBlanks.value()) { | |
log.info("[DIRECT-REPEAT] " + joinPoint + " -> " + requiresNonBlank); | |
evaluatePrecondition(joinPoint, requiresNonBlank); | |
} | |
} | |
@Before("execution(@(@" + REQUIRES_NON_BLANK_FQPN + " *) * *(..))") | |
public void metaAnnotations(JoinPoint joinPoint) { | |
for (RequiresNonBlank requiresNonBlank : getMetaAnnotations(joinPoint)) { | |
log.info("[META] " + joinPoint + " -> " + requiresNonBlank); | |
evaluatePrecondition(joinPoint, requiresNonBlank); | |
} | |
} | |
private Set<RequiresNonBlank> getMetaAnnotations(JoinPoint joinPoint) { | |
Set<RequiresNonBlank> foundMetaAnnotations = new HashSet<>(); | |
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); | |
Annotation[] annotations = method.getAnnotations(); | |
for (Annotation annotation : annotations) { | |
foundMetaAnnotations.addAll( | |
Arrays.asList( | |
annotation.annotationType().getAnnotationsByType(RequiresNonBlank.class) | |
) | |
); | |
} | |
return foundMetaAnnotations; | |
} | |
public void evaluatePrecondition(JoinPoint joinPoint, RequiresNonBlank requiresNonBlank) { | |
try { | |
Objects.requireNonNull(requiresNonBlank); | |
} | |
catch (NullPointerException e) { | |
log.error("No annotation found!", e); | |
} | |
Expression expression = elParser.parseExpression(requiresNonBlank.value()); | |
String expressionToEvaluate = (String) expression.getValue(joinPoint.getArgs()); | |
log.info("Evaluated expression: " + expressionToEvaluate); | |
if (StringUtils.isEmpty(expressionToEvaluate)) { | |
try { | |
throw requiresNonBlank.throwIfInvalid().getConstructor().newInstance(); | |
} | |
catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { | |
log.error("Could not throw the exception configured!", e); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.scrum_master.spring.q68785567; | |
import java.lang.annotation.Documented; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(value = { ElementType.METHOD }) | |
@Documented | |
@RequiresNonBlank( | |
value = "T(de.scrum_master.spring.q68785567.TestDummyValueHolder).value", | |
throwIfInvalid = TestDummyStuffException.class | |
) | |
public @interface RequiresNonBlankMeta {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.scrum_master.spring.q68785567; | |
import java.lang.annotation.Documented; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(value = { ElementType.METHOD }) | |
@Documented | |
@RequiresNonBlank( | |
value = "T(de.scrum_master.spring.q68785567.TestDummyValueHolder).anotherValue", | |
throwIfInvalid = TestDummyStuffException.class | |
) | |
public @interface RequiresNonBlankMeta2 {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.scrum_master.spring.q68785567; | |
import java.lang.annotation.Documented; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Repeatable; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target(value = { ElementType.METHOD, ElementType.ANNOTATION_TYPE }) | |
@Documented | |
public @interface RequiresNonBlanks { | |
RequiresNonBlank[] value(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.scrum_master.spring.q68785567; | |
public class StuffException extends RuntimeException { | |
public StuffException() { | |
super(); | |
} | |
public StuffException(String message) { | |
super(message); | |
} | |
public StuffException(String message, Throwable cause) { | |
super(message, cause); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.scrum_master.spring.q68785567; | |
import org.springframework.stereotype.Component; | |
@Component | |
public class TestDummyController { | |
@RequiresNonBlank( | |
value = "T(de.scrum_master.spring.q68785567.TestDummyValueHolder).value", | |
throwIfInvalid = TestDummyStuffException.class | |
) | |
public boolean direct() { | |
return true; | |
} | |
@RequiresNonBlank( | |
value = "T(de.scrum_master.spring.q68785567.TestDummyValueHolder).value", | |
throwIfInvalid = TestDummyStuffException.class | |
) | |
@RequiresNonBlank( | |
value = "T(de.scrum_master.spring.q68785567.TestDummyValueHolder).anotherValue", | |
throwIfInvalid = TestDummyStuffException.class | |
) | |
@RequiresNonBlank( | |
value = "T(de.scrum_master.spring.q68785567.TestDummyValueHolder).anotherValue", | |
throwIfInvalid = TestDummyStuffException.class | |
) | |
public boolean directRepeat() { | |
return true; | |
} | |
@RequiresNonBlankMeta | |
public boolean meta() { | |
return true; | |
} | |
@RequiresNonBlankMeta2 | |
public boolean meta2() { | |
return true; | |
} | |
@RequiresNonBlankMeta | |
@RequiresNonBlank( | |
value = "T(de.scrum_master.spring.q68785567.TestDummyValueHolder).anotherValue", | |
throwIfInvalid = TestDummyStuffException.class | |
) | |
public boolean mixed() { | |
return true; | |
} | |
@RequiresNonBlankMeta | |
@RequiresNonBlankMeta2 | |
@RequiresNonBlank( | |
value = "T(de.scrum_master.spring.q68785567.TestDummyValueHolder).value", | |
throwIfInvalid = TestDummyStuffException.class | |
) | |
@RequiresNonBlank( | |
value = "T(de.scrum_master.spring.q68785567.TestDummyValueHolder).anotherValue", | |
throwIfInvalid = TestDummyStuffException.class | |
) | |
public boolean mixedRepeat() { | |
return true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.scrum_master.spring.q68785567; | |
public class TestDummyStuffException extends StuffException { | |
public TestDummyStuffException() { | |
super(); | |
} | |
public TestDummyStuffException(String message) { | |
super(message); | |
} | |
public TestDummyStuffException(String message, Throwable cause) { | |
super(message, cause); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.scrum_master.spring.q68785567; | |
public class TestDummyValueHolder { | |
private static String value; | |
private static String anotherValue; | |
public static String getValue() { | |
return value; | |
} | |
public static void setValue(String newValue) { | |
value = newValue; | |
} | |
public static String getAnotherValue() { | |
return anotherValue; | |
} | |
public static void setAnotherValue(String newAnotherValue) { | |
anotherValue = newAnotherValue; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment