I need to ensure that, in specific classes (e.g. all classes extending some other class), fields annotated with e.g. #Deprecated are also annotated with #ThisOtherAnnotationMustBeHere.
#Deprecated
#ThisOtherAnnotationMustBeHere // this must be present if #Deprecated is also present; otherwise build should fail
private String field;
I need in general something to check for the presence of annotations.
I guess I could write a JUnit test for this using reflection, but I was wondering if there was a Maven solution to this.
Following #khmarbaise suggestion (thanks!) I've used archunit.org to write a unit test for this. In my case I needed to verify that join fields in JPA entities were annotated with a specific custom JsonAdapter
class CodeChecksTest {
#ArchTest
public static final ArchRule persistenceIdAnnotationRule = fields().that()
.areDeclaredInClassesThat().areAnnotatedWith(Entity.class).and()
.areAnnotatedWith(OneToOne.class).or()
.areAnnotatedWith(OneToMany.class).or()
.areAnnotatedWith(ManyToOne.class).or()
.areAnnotatedWith(ManyToMany.class)
.should(beAnnotatedForMyCustomAdapter());
private static ArchCondition<? super JavaField> beAnnotatedForMyCustomAdapter() {
return new ArchCondition<JavaField>("annotated with #JsonAdapter(MyCustomAdapter.class)") {
#Override
public void check(JavaField item, ConditionEvents events) {
final Optional<JsonAdapter> annotation = item.tryGetAnnotationOfType(JsonAdapter.class);
final boolean satisfied = annotation.isPresent() && annotation.get().value() == MyCustomAdapter.class;
// createMessage is a utility method
String message = createMessage(item,
(satisfied ? "is " : "is not ") + getDescription());
events.add(new SimpleConditionEvent(item, satisfied, message));
}
};
}
}
Related
I am using JUNIT's #categories and want to check in a method which category I am in.
for example
if (category.name == "sanity")
//do something
Is there any way to do that?
I want to avoid having to pass a parameter to this method because I have over 800 calls to it in the project
I believe you can do that the same way that can be used to determine if any other class has specific annotation and its values - use Java reflection mechanism.
As a quick example for your specific case you can make it like this:
#Category(Sanity.class)
public class MyTest {
#Test
public void testWhatever() {
if (isOfCategory(Sanity.class)) {
// specific actions needed for any tests that falls into Sanity category:
System.out.println("Running Sanity Test");
}
// test whatever you need...
}
private boolean isOfCategory(Class<?> categoryClass) {
Class<? extends MyTest> thisClass = getClass();
if (thisClass.isAnnotationPresent(Category.class)) {
Category category = thisClass.getAnnotation(Category.class);
List<Class<?>> values = Arrays.asList(category.value());
return values.contains(categoryClass);
}
return false;
}
}
I have an #Audit annotation, it has many optional attributes, I need to enforce the use of one boolean attribute useAccount = true for certain packages.
I am trying to use archunit to accomplish this validation, that way whenever a developer commits code that breaks the rule the CI will break and inform the team.
This would break the build:
#Audit
public myMethod(...) {
...
}
This is the right way:
#Audit(useAccount = true)
public myMethod(...) {
...
}
The problem is that Archunit doesn't currently support asserting over methods. I was expecting to do something like:
methods().that().resideInAnyPackage("..controllers..", "..service..").and().areAnnotatedWith(Audit.class).should(attributeCheckCondition)
Then my custom condition attributeCheckCondition would take care of looking into the attribute value.
Is there a way of retrieving methods as we retrieve classes? Without having to write a more complicated predicate and condition?
Update
Since ArchUnit 0.10.0 it is possible to create rules for members.
methods().that()
.areDeclaredInClassesThat()
.resideInAnyPackage("..controllers..", "..service..")
.and()
.areAnnotatedWith(Audit.class)
.should(attributeCheckCondition)
See also Composing Member Rules in the User Guide.
Original Answer
Since there are currently no basic rule definitions available for methods, an intermediate step is necessary. ArchUnit has a ClassesTransformer to transform JavaClasses into a collection of other types.
ClassesTransformer<JavaMethod> methods = new AbstractClassesTransformer<JavaMethod>("methods") {
#Override
public Iterable<JavaMethod> doTransform(JavaClasses javaClasses) {
Set<JavaMethod> allMethods = new HashSet<>();
for (JavaClass javaClass : javaClasses) {
allMethods.addAll(javaClass.getMethods());
}
return allMethods;
}
};
This ClassesTransformer can then be used as a base for custom rule definitions.
ArchRule rule = ArchRuleDefinition.all(methods)
.that(owner(resideInAnyPackage("..controllers..", "..service..")))
.and(annotatedWith(Audit.class))
.should(haveAttributeValue());
rule.check(javaClasses);
See also Rules with Custom Concepts in the User Guide and this issue.
I found a way of doing it with custom predicate and condition over classes, when I did that I was not aware of Roland's response which seems to be better, as it provides a way to express the rule assertion from the methods perspective which is why I was asking for.
However I wanted to post the solution here so it can be useful for others.
DescribedPredicate<JavaClass> HAVE_A_METHOD_ANNOTATED_WITH_AUDIT =
new DescribedPredicate<JavaClass>("have a method annotated with #Audit")
{
#Override
public boolean apply(JavaClass input)
{
return input.getMethods().stream().anyMatch(method -> method.isAnnotatedWith(Audit.class));
}
};
ArchCondition<JavaClass> ONLY_SET_ATTRIBUTE_USE_ACCOUNT_SET_TO_TRUE =
new ArchCondition<JavaClass>("only set useAccount attribute to true")
{
#Override
public void check(JavaClass item, ConditionEvents events)
{
item.getMethods().stream().filter(method ->
method.isAnnotatedWith(Audit.class) && !method.getAnnotationOfType(Audit.class)
.useAccount()
)
.forEach(method -> {
String message = String.format(
"Method %s is annotated with #Audit but useAccount is not set to true",
method.getFullName());
events.add(SimpleConditionEvent.violated(method, message));
});
}
};
Then the rule is expressed as:
ArchRule ANNOTATION_RULE = classes()
.that()
.resideInAnyPackage("..controller..", "..service..")
.and(HAVE_A_METHOD_ANNOTATED_WITH_AUDIT)
.should(ONLY_SET_ATTRIBUTE_USE_ACCOUNT_SET_TO_TRUE);
Here is another custom example in addition to #raspacorp (who inspired me!).
To check #Secured(ROLE) method annotation, I've implemented the following rule:
public static class SecuredByRoleArchCondition extends ArchCondition<JavaMethod> {
private final String[] expectedRoles;
public SecuredByRoleArchCondition(String[] expectedRoles) {
super(String.format("accessed by #Secured methods with roles %s", Arrays.toString(expectedRoles)));
this.expectedRoles = expectedRoles;
}
public static SecuredByRoleArchCondition haveSecuredAnnotationWithRoles(String... expectedRoles) {
return new SecuredByRoleArchCondition(expectedRoles);
}
#Override
public void check(JavaMethod javaMethod, ConditionEvents events) {
if (!javaMethod.isAnnotatedWith(Secured.class)) {
String message = String.format("Method %s annotation #Secured(%s) is missing",
javaMethod.getFullName(), Arrays.toString(expectedRoles));
events.add(SimpleConditionEvent.violated(javaMethod, message));
return;
}
String[] annotationRoleValues = javaMethod.getAnnotationOfType(Secured.class).value();
if (!Arrays.equals(annotationRoleValues, expectedRoles)) {
String message = String.format("Method %s #Secured with %s has wrong roles, expected %s instead",
javaMethod.getFullName(), Arrays.toString(annotationRoleValues), Arrays.toString(expectedRoles));
events.add(SimpleConditionEvent.violated(javaMethod, message));
}
}
}
Here is a sample usage of this archCondition:
#ArchTest
static ArchRule admin_actions_with_post_mapping_should_be_secured_by_ADMIN_WRITE_role =
methods()
.that().areDeclaredInClassesThat().resideInAnyPackage(ADMIN_PACKAGES)
.and().areAnnotatedWith(PostMapping.class)
.should(haveSecuredAnnotationWithRoles("ADMIN_WRITE"));
I am using this custom annotation for logging execution time, annotation could be present on method or class in which all public methods have it. Everything works fine, except in case of method level "LogExecutionTime logExecutionTime" comes null. This throws an NPE.
#Around("#annotation(logExecutionTime) || #within(logExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
final Logger logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass());
final String name = joinPoint.toShortString();
final StopWatch stopWatch = new StopWatch(name);
stopWatch.start(name);
try {
return joinPoint.proceed();
} finally {
stopWatch.stop();
if (logExecutionTime.value()) {
logger.info(joinPoint.getSignature().getName() + ".time=", stopWatch.getTotalTimeSeconds());
}
}
}
if I reverse the order-
#Around("#within(logExecutionTime) || #annotation(logExecutionTime)")
the behavior reverses and I get a valid object at method level and null at class level annotated methods.
I have worked around this by having 2 explicit methods and separating the two-
#Around("#within(logExecutionTime)")
public Object logExecutionTimeClassLevel(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
return logExecutionTimeMethodLevel(joinPoint, logExecutionTime);
}
#Around("#annotation(logExecutionTime)")
public Object logExecutionTimeMethodLevel(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
final Logger logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass());
final String name = joinPoint.toShortString();
final StopWatch stopWatch = new StopWatch(name);
stopWatch.start(name);
try {
return joinPoint.proceed();
} finally {
stopWatch.stop();
if (logExecutionTime.value()) {
logger.info(joinPoint.getSignature().getName() + ".time=", stopWatch.getTotalTimeMillis());
}
}
Was hoping to understand this behavior, when we use OR '||' with two pointcuts.
class level
#LogExecutionTime
#Component
public class CleanUpService implements ICleanUpService { ... }
method level
#Scheduled(fixedDelay = 100)
#LogExecutionTime(false)
public void processMessageQueue() { ... }
I came to run you example, and reproduce the same example as yours, when it come to runtime expression is same weird because when you specify the annotation on class level and you write this expression
#Around(" #within(logExecutionTime) || #annotation(logExecutionTime) ")
The point cut will evaluate to true for you class (event you annotation its available in joinPoint.getTarget().getClass().getAnnotations(), )
Now when it come to binding the variable the compiler check all your expressions that mean binding #within(logExecutionTime) to variable logExecutionTime and #annotation(logExecutionTime) to the same variable , if the method is not annotated it will ge null, => override the initial with, that cause all senarios you mention.
Try to put this expression #within(logExecutionTime) || #annotation(logExecutionTime) || #within(logExecutionTime)
and you'll get you variable not null which prove what i said, last #within(logExecutionTime) override what precedent
The key here is that the logic applied to select the point cut matching not the same when it come context-binding
Now when it come to AOP point-cut you must be careful and follow best practice as the spring team they mention here to avoid weird runtime results
Cheers
This cannot work, it does not even compile with the AspectJ compiler. Maybe in your IDE and with Spring AOP you do not see any warnings or errors, but I see:
ambiguous binding of parameter(s) logExecutionTime across '||' in pointcut
This means that it is not clear which annotation should be selected if e.g. both the class and the method contain an instance of that annotation. It is, as the error message said, ambiguous. But ambiguous parameter bindings across || are not permitted. They can also happen if you try to bind values from different "or" branches to a single parameter in an args() list.
I had the same problem. What you want is exactly same as Spring #Transcriptional behaves (I mean, class level or method level annotation with parameters). I used your solution but to get the class level parameter value (as the annotation object received null), I used reflection. I know it is a dirty solution! But I tried other solutions and couldn't find!
Her is the full code. This will call the advice code either the annotation is used on a class or a method. If the annotation is placed on both (class and method), the method takes the precedence.
#Aspect
#Configurable
#Component
public class DynamicValueAspect {
#Around(" #within(dynamicValueAnnotation) || #annotation(dynamicValueAnnotation))")
public Object process(ProceedingJoinPoint joinPoint, DynamicValue dynamicValueAnnotation) throws Throwable {
String annotationParameter;
if (dynamicValueAnnotation == null) { //class level annotation
annotationParameter = extractClassLevelAnnotationParameterValue(joinPoint);
} else {
annotationParameter = dynamicValueAnnotation.myAnnotationParameter();
}
System.out.println(" " + annotationParameter);//get the annotation parameter value
return joinPoint.proceed();
}
private String extractClassLevelAnnotationParameterValue(ProceedingJoinPoint joinPoint) {
Annotation[] classAnnotations = joinPoint.getTarget().getClass().getAnnotations();
for (Annotation annotation : classAnnotations) {
if (annotation.annotationType() == DynamicValue.class) {
return ((DynamicValue) annotation).myAnnotationParameter();
}
}
throw new RuntimeException("No DynamicValue value annotation was found");
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface DynamicValue {
String myAnnotationParameter();
}
Let's know if you got a cleaner solution!
The problem with your workaround appears when you annotate both a class and a method with the annotation, resulting in triggering both of them.
To prevent it declare the class level advice as:
#Around("!#annotation(LogExecutionTime) && #within(logExecutionTime)")
I want to declare a warning on all fields Annotated with #org.jboss.weld.context.ejb.Ejb in AspectJ.
But I do not find a way how to select that field.
I guess the aspect should be something like that:
public aspect WrongEjbAnnotationWarningAspect {
declare warning :
within(com.queomedia..*) &&
??? (#org.jboss.weld.context.ejb.Ejb)
: "WrongEjbAnnotationErrorAspect: use javax.ejb.EJB instead of weld Ejb!";
}
Or is it impossible to declare warnings on fields at all?
The only field pointcuts I see are for get and set. This makes sense because aspects are primarily about executing code. Declaring compiler warnings is sortof a nice side benefit. If we just talk about a field, independent of the use of that field, when would the pointcut be hit? I think you should be able to do what you want with the Annotation Processing Tool instead of AspectJ. Here is a first stab at it, mostly copied from the example on the tool's web page linked above.
public class EmitWarningsForEjbAnnotations implements AnnotationProcessorFactory {
// Process any set of annotations
private static final Collection<String> supportedAnnotations
= unmodifiableCollection(Arrays.asList("*"));
// No supported options
private static final Collection<String> supportedOptions = emptySet();
public Collection<String> supportedAnnotationTypes() {
return supportedAnnotations;
}
public Collection<String> supportedOptions() {
return supportedOptions;
}
public AnnotationProcessor getProcessorFor(
Set<AnnotationTypeDeclaration> atds,
AnnotationProcessorEnvironment env) {
return new EjbAnnotationProcessor(env);
}
private static class EjbAnnotationProcessor implements AnnotationProcessor {
private final AnnotationProcessorEnvironment env;
EjbAnnotationProcessor(AnnotationProcessorEnvironment env) {
this.env = env;
}
public void process() {
for (TypeDeclaration typeDecl : env.getSpecifiedTypeDeclarations())
typeDecl.accept(new ListClassVisitor());
}
private static class ListClassVisitor extends SimpleDeclarationVisitor {
public void visitClassDeclaration(ClassDeclaration d) {
for (FieldDeclaration fd : d.getFields()) {
fd.getAnnotation(org.jboss.weld.context.ejb.Ejb.class);
}
}
}
}
}
Sort of agree with #JohnWatts, but also feel that get() will work for you:
declare warning :
within(com.queomedia..*) &&
get(#org.jboss.weld.context.ejb.Ejb * *.*)
: "WrongEjbAnnotationErrorAspect: use javax.ejb.EJB instead of weld Ejb!";
This will show a warning at any code that tries to use the fields annotated with #org.jboss.weld.context.ejb.Ejb and not the field itself, but should suffice as a compile time warning?
I have integration tests (load context) and unit tests running together. My code does aspectj compile time weaving using spring.
My problem is that my declared advises also run during some of my unit tests. This kills the notion of a unit test, which is why I would like to disable them.
Is there something I can put on the pointcut declaration, some method I can call, some spring configuration, or maven command that disables these advises for something like all *UnitTest.java?
Thanks for the help.
example:
I have the following unit test:
#RunWith(MockitoJUnitRunner.class)
public class CompanyServiceImplTest {
#Test
public void createCampaignTest() throws Exception {
when(companyDaoMock.saveCompany(any(Campaign.class))).thenReturn(77L);
Long campaignId = companyService.createCampaign(campaignMock);
assertEquals(Long.valueOf(77L), Long.valueOf(campaignId));
}
}
and following service method:
#Override
#Transactional
#EventJournal(type = EventType.CAMPAIGN_CREATE, owner = EventOwner.TERMINAL_USER)
public Long createCampaign(Campaign campaign) {
return companyDao.saveCompany(campaign);
}
aspect:
#Aspect
public class EventJournalAspect {
#Autowired
private EventJournalService eventJournalService;
#Pointcut(value="execution(public * *(..))")
public void anyPublicMethod() {}
#Pointcut("within(com.terminal.service..*)")
private void inService() {}
#AfterReturning(pointcut = "anyPublicMethod() && inService() && #annotation(eventJournal) && args(entity,..)", returning = "id")
public void process(Object id, EventJournal eventJournal, AbstractDomainEntity entity)
throws Throwable {
if (eventJournal.type() != EventType.CAMPAIGN_PAYMENT || id != null) {
saveEvent(eventJournal, EventStatus.SUCCESS, entity, (Long) id);
}
}
#AfterThrowing(pointcut = "anyPublicMethod() && inService() && #annotation(eventJournal) && args(entity,..)", throwing="ex")
public void processException(EventJournal eventJournal, AbstractDomainEntity entity, Exception ex) throws Throwable {
saveEvent(eventJournal, EventStatus.FAILURE, entity, null);
}
private void saveEvent(EventJournal eventJournal, EventStatus status, AbstractDomainEntity entity, Long persistentId) {
EventType type = eventJournal.type();
EventOwner owner = eventJournal.owner();
eventJournalService.saveEvent(type, owner, EventStatus.SUCCESS, entity, persistentId);
}
}
When test executes - eventJournalService is null. Thus I see NullPointerException
The answer is simple: You want to use an if() pointcut expression.
Update (after the question has also been updated): The originally provided link above should contain enough information, but for what it is worth, a short explanation and a simple example:
An if() pointcut is a static aspect method returning a boolean. If the return value is true, it means that any combined pointcut like myPointcut() && if() matches as long as myPointcut() matches. For a return value of false the whole combined pointcut does not match, effectively deactivating any advice connected to the pointcut.
So what can you do in a static if() pointcut?
evaluate a static boolean member of some tool class like TestMode.ACTIVE which is only true during unit or integration testing
evaluate an environment variable which is only set during testing
evaluate a Java system property which is only set during testing
and many more things
If you want to do something fancier (and trickier) and performance is not so important, you can also try to dynamically determine whether the auto-wired aspect member variable equals null or not and only activate your pointcuts if the injected object is actually present. The only problem here is how to determine a member variable from a static method. I have no idea about Spring AOP, but in plain AspectJ there is the helper class Aspects with several overloaded methods named aspectOf(..). Assuming that your aspect is instantiated as a singleton, you could do something like this:
#Pointcut("if()")
public static boolean isActive() {
return Aspects.aspectOf(PerformanceMonitorAspect.class).eventJournalService != null;
}
// ...
#AfterReturning(pointcut = "isActive() && anyPublicMethod() && inService() && #annotation(eventJournal) && args(entity,..)", returning = "id")
// ...
#AfterThrowing(pointcut = "isActive() && anyPublicMethod() && inService() && #annotation(eventJournal) && args(entity,..)", throwing="ex")
// ...
I can only guess:
The first thing is to have a separate Spring applicationContext-test.xml,
without component-scan;
In maven you can add a phase runtime excluding weaving jars for test.
Compile time weaving would inline the advice calls in the targeted methods identified by the pointcuts that you have. I personally feel that it is good to unit test with the compile time weaving in place, because at runtime your unit does include the class with the advice inlined?
The thought I have to not include advice would be to have two different compile targets, one with compile time weaving, and one without, you should be able to do this through maven profiles, a dev profile not weaving advice in and a prod profile to weave the aspects in.
You can write a method that returns if current execution was launched using JUnit framework.
The method can check stack trace with Thread.currentThread().getStackTrace() and search for MockitoJUnitRunner presence.
I tested this solution using a SpringJUnit4ClassRunner but I think could work with MockitoJUnitRunner.
Also, you can have got a static boolean field like:
private static boolean TEST_ENVIRONMENT = false;
In a class present in your project (not in your tests) and check the value in the control method instead of use stack trace.
When you run your tests, you can use #BeforeClass annotation to set TEST_ENVIRONMENT = true.
This solution only gives you a way to know if your code is running from a test or not.