/*
* Copyright 2008-2014 the original author or authors
*
* 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.kaleidofoundry.core.lang.aop;
import static org.kaleidofoundry.core.util.AspectjHelper.debugJoinPoint;
import java.lang.annotation.Annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.ConstructorSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.kaleidofoundry.core.lang.NotNullException;
import org.kaleidofoundry.core.lang.annotation.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <b>Technical notes : </b> <br/>
* <br/>
* 1. Handle parameter annotation since aspectj 1.5.x :
* <ul>
* <li>execution(* *(@org.group.project.module.YourAnnotation *)) execution of any method, where the first parameter @YourAnnotation
* annotated
* <li>execution(* *(@org.group.project.module.YourAnnotation (*))) execution of any method, where one of the parameter @YourAnnotation
* annotated
* </ul>
* 2. Handle matching parameter annotations (in multiple static locations) since aspectj 1.6.8 :
* <ul>
* <li>@Pointcut("execution(public * (@ClassAnnotation *).*(@YourAnnotation (*),..))) // 1st param has annotation
* <li>@Pointcut("execution(public * (@ClassAnnotation *).*(*,@YourAnnotation (*),..))) // 2nd param has annotation
* <li>@Pointcut("execution(public * (@ClassAnnotation *).*(*,*, @YourAnnotation (*),..))) // 3rd param has annotation
* </ul>
* If you want to bind the annotated parameter you need the corresponding binding clause too : <br/>
* <ul>
* <li>@Pointcut("execution(public * (@ClassAnnotation *).*(@YourAnnotation (*),..) && args(x,..))) // 1st param has annotation
* <li>@Pointcut("execution(public * (@ClassAnnotation *).*(*,@YourAnnotation (*),..) && args(*,x,..))) // 2nd param has annotation
* <li>@Pointcut("execution(public * (@ClassAnnotation *).*(*,*, @YourAnnotation (*),..) && args(*,*,x,..))) // 3rd param has annotation
* </ul>
* <b>args(..,x,..) is not allowed...</b> <br/>
* <br/>
* 3. Handle matching parameter annotations in multiple dynamic locations since aspectj 1.6.9 :
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=233718
*
* @author jraduget
* @see NotNull
*/
@Aspect
public class NotNullAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(NotNullAspect.class);
/**
* Pointcut for constructor arguments annotated @NotNull
*
* @param jp
* @param esjp
* @return true to enable advice
*/
@Pointcut("execution(*.new(..,@org.kaleidofoundry.core.lang.annotation.NotNull (*),..)) && if()")
public static boolean constructorArgs(final JoinPoint jp, final JoinPoint.EnclosingStaticPart esjp) {
LOGGER.debug("@Pointcut NotNullAspect - constructorArgs match");
return true;
}
/**
* Pointcut for method arguments annotated @NotNull
*
* @param jp
* @param esjp
* @return true to enable advice
*/
@Pointcut("execution(* *(..,@org.kaleidofoundry.core.lang.annotation.NotNull (*),..)) && if()")
public static boolean methodArgs(final JoinPoint jp, final JoinPoint.EnclosingStaticPart esjp) {
LOGGER.debug("@Pointcut NotNullAspect - methodArgs match");
return true;
}
/**
* Pointcut for method annotated @NotNull result
*
* @param jp
* @param esjp
* @return true to enable advice
*/
@Pointcut("call(@org.kaleidofoundry.core.lang.annotation.NotNull * *(..)) && if()")
public static boolean methodResult(final JoinPoint jp, final JoinPoint.EnclosingStaticPart esjp) {
LOGGER.debug("@Pointcut NotNullAspect - methodResult match");
return true;
}
@Before("constructorArgs(jp, esjp)")
public void beforeConstructorArgs(final JoinPoint jp, final JoinPoint.EnclosingStaticPart esjp) {
LOGGER.debug("@Before NotNullAspect - constructorArgs - {}", jp);
handleArgument(jp, esjp);
}
@Before("methodArgs(jp, esjp)")
public void beforeMethodArgs(final JoinPoint jp, final JoinPoint.EnclosingStaticPart esjp) {
LOGGER.debug("@Before NotNullAspect - methodArgs - {}", jp);
handleArgument(jp, esjp);
}
@Around("methodResult(jp, esjp)")
public Object afterMethodResult(final JoinPoint jp, final JoinPoint.EnclosingStaticPart esjp, final ProceedingJoinPoint thisJoinPoint) throws Throwable {
LOGGER.debug("@After NotNullAspect - afterMethodResult");
Object result = thisJoinPoint.proceed();
if (result == null) {
throw new NotNullException(jp.getStaticPart().getSignature().toString(), jp.getSignature().toString(), jp.getSourceLocation().toString());
} else {
return result;
}
}
/**
* introspect constructor or method argument, in order to detect if parameter annotated {@link NotNull} is null or not. <br/>
* add useful debug informations too
*
* @param jp
* @param esjp
*/
void handleArgument(final JoinPoint jp, final JoinPoint.EnclosingStaticPart esjp) {
debugJoinPoint(LOGGER, jp);
Annotation[][] parametersAnnotations = null;
String[] parametersName = null;
// 1. extract method / constructor parameter static informations
if (jp.getSignature() instanceof ConstructorSignature) {
parametersName = ((ConstructorSignature) jp.getSignature()).getParameterNames();
parametersAnnotations = ((ConstructorSignature) jp.getSignature()).getConstructor().getParameterAnnotations();
}
if (jp.getSignature() instanceof MethodSignature) {
parametersName = ((MethodSignature) jp.getSignature()).getParameterNames();
parametersAnnotations = ((MethodSignature) jp.getSignature()).getMethod().getParameterAnnotations();
}
// 2. introspect input parameters
if (parametersName != null && parametersAnnotations != null) {
for (int paramCpt = 0; paramCpt < parametersName.length; paramCpt++) {
Annotation[] parameterAnnotations = parametersAnnotations[paramCpt];
if (parameterAnnotations != null) {
for (Annotation parameterAnnotation : parameterAnnotations) {
// parameter annotated with @NotNull + null value
if (NotNull.class == parameterAnnotation.annotationType() && jp.getArgs()[paramCpt] == null) {
throw new NotNullException(
jp.getStaticPart().getSignature().toString(),
parametersName[paramCpt],
jp.getSignature().toString(),
jp.getSourceLocation().toString()
);
}
}
}
}
}
}
}